Merge "[Search]Implement search session cache in AllApps" into sc-dev

This commit is contained in:
Samuel Fufa 2021-03-15 19:23:06 +00:00 committed by Android (Google) Code Review
commit 6547d098c6
3 changed files with 7 additions and 339 deletions

View File

@ -113,7 +113,6 @@ import com.android.launcher3.allapps.AllAppsContainerView;
import com.android.launcher3.allapps.AllAppsStore;
import com.android.launcher3.allapps.AllAppsTransitionController;
import com.android.launcher3.allapps.DiscoveryBounce;
import com.android.launcher3.allapps.search.LiveSearchManager;
import com.android.launcher3.anim.PropertyListBuilder;
import com.android.launcher3.compat.AccessibilityManagerCompat;
import com.android.launcher3.config.FeatureFlags;
@ -277,8 +276,6 @@ public class Launcher extends StatefulActivity<LauncherState> implements Launche
private Configuration mOldConfig;
private LiveSearchManager mLiveSearchManager;
@Thunk
Workspace mWorkspace;
@Thunk
@ -401,8 +398,6 @@ public class Launcher extends StatefulActivity<LauncherState> implements Launche
mAllAppsController = new AllAppsTransitionController(this);
mStateManager = new StateManager<>(this, NORMAL);
mLiveSearchManager = new LiveSearchManager(this);
mOnboardingPrefs = createOnboardingPrefs(mSharedPrefs);
mAppWidgetManager = new WidgetManagerHelper(this);
@ -490,10 +485,6 @@ public class Launcher extends StatefulActivity<LauncherState> implements Launche
}
}
public LiveSearchManager getLiveSearchManager() {
return mLiveSearchManager;
}
protected LauncherOverlayManager getDefaultOverlay() {
return new LauncherOverlayManager() { };
}
@ -1594,7 +1585,6 @@ public class Launcher extends StatefulActivity<LauncherState> implements Launche
mOverlayManager.onActivityDestroyed(this);
mUserChangedCallbackCloseable.close();
mLiveSearchManager.stop();
}
public LauncherAccessibilityDelegate getAccessibilityDelegate() {

View File

@ -1,325 +0,0 @@
/*
* Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.launcher3.allapps.search;
import static com.android.launcher3.LauncherState.ALL_APPS;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.launcher3.util.Executors.THREAD_POOL_EXECUTOR;
import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
import static com.android.launcher3.widget.WidgetHostViewLoader.getDefaultOptionsForWidget;
import android.app.Activity;
import android.app.Application.ActivityLifecycleCallbacks;
import android.appwidget.AppWidgetHost;
import android.appwidget.AppWidgetHostView;
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProviderInfo;
import android.content.ComponentName;
import android.content.Context;
import android.net.Uri;
import android.os.Bundle;
import android.os.UserHandle;
import android.util.Log;
import androidx.annotation.Nullable;
import androidx.annotation.UiThread;
import androidx.annotation.WorkerThread;
import androidx.lifecycle.Observer;
import androidx.slice.Slice;
import androidx.slice.SliceViewManager;
import androidx.slice.SliceViewManager.SliceCallback;
import com.android.launcher3.Alarm;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherState;
import com.android.launcher3.statemanager.StateManager.StateListener;
import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.SafeCloseable;
import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
import com.android.launcher3.widget.PendingAddWidgetInfo;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.function.Consumer;
/**
* Manages Lifecycle for Live search results
*/
public class LiveSearchManager implements StateListener<LauncherState> {
private static final String TAG = "LiveSearchManager";
private static final long SLICE_TIMEOUT_MS = 50;
public static final int SEARCH_APPWIDGET_HOST_ID = 2048;
private final Launcher mLauncher;
private final HashMap<Uri, SliceLifeCycle> mUriSliceMap = new HashMap<>();
private final HashMap<ComponentKey, SearchWidgetInfoContainer> mWidgetPlaceholders =
new HashMap<>();
private SearchWidgetHost mSearchWidgetHost;
public LiveSearchManager(Launcher launcher) {
mLauncher = launcher;
mLauncher.getStateManager().addStateListener(this);
}
/**
* Creates new {@link AppWidgetHostView} from {@link AppWidgetProviderInfo}. Caches views for
* quicker result within the same search session
*/
public SearchWidgetInfoContainer getPlaceHolderWidget(AppWidgetProviderInfo providerInfo) {
if (mSearchWidgetHost == null) {
mSearchWidgetHost = new SearchWidgetHost(mLauncher);
mSearchWidgetHost.startListening();
}
ComponentName provider = providerInfo.provider;
UserHandle userHandle = providerInfo.getProfile();
ComponentKey key = new ComponentKey(provider, userHandle);
if (mWidgetPlaceholders.containsKey(key)) {
return mWidgetPlaceholders.get(key);
}
LauncherAppWidgetProviderInfo pinfo = LauncherAppWidgetProviderInfo.fromProviderInfo(
mLauncher, providerInfo);
PendingAddWidgetInfo pendingAddWidgetInfo = new PendingAddWidgetInfo(pinfo);
Bundle options = getDefaultOptionsForWidget(mLauncher, pendingAddWidgetInfo);
int appWidgetId = mSearchWidgetHost.allocateAppWidgetId();
boolean success = AppWidgetManager.getInstance(mLauncher)
.bindAppWidgetIdIfAllowed(appWidgetId, userHandle, provider, options);
if (!success) {
mSearchWidgetHost.deleteAppWidgetId(appWidgetId);
mWidgetPlaceholders.put(key, null);
return null;
}
SearchWidgetInfoContainer view = (SearchWidgetInfoContainer) mSearchWidgetHost.createView(
mLauncher, appWidgetId, providerInfo);
view.setTag(pendingAddWidgetInfo);
mWidgetPlaceholders.put(key, view);
return view;
}
/**
* Stop search session
*/
public void stop() {
clearWidgetHost();
}
private void clearWidgetHost() {
if (mSearchWidgetHost != null) {
mSearchWidgetHost.stopListening();
mSearchWidgetHost.clearViews();
mSearchWidgetHost.deleteHost();
mWidgetPlaceholders.clear();
mSearchWidgetHost = null;
}
}
@Override
public void onStateTransitionComplete(LauncherState finalState) {
if (finalState != ALL_APPS) {
// Clear all search session related objects
mUriSliceMap.values().forEach(SliceLifeCycle::destroy);
mUriSliceMap.clear();
clearWidgetHost();
}
}
/**
* Adds a new observer for the provided uri and returns a callback to cancel this observer
*/
public SafeCloseable addObserver(Uri uri, Observer<Slice> listener,
Consumer<Uri> timeoutConsumer) {
SliceLifeCycle slc = mUriSliceMap.get(uri);
if (slc == null) {
slc = new SliceLifeCycle(uri, mLauncher);
mUriSliceMap.put(uri, slc);
}
if (slc.mLastValue != null) {
listener.onChanged(slc.mLastValue);
}
// Use a listener wrapper to handle error timeout.
Observer<Slice> listenerWrapper = new Observer<Slice>() {
final Alarm mErrorTimeout = new Alarm();
{
mErrorTimeout.setOnAlarmListener(alarm -> {
alarm.cancelAlarm();
timeoutConsumer.accept(uri);
});
mErrorTimeout.setAlarm(SLICE_TIMEOUT_MS);
}
@Override
public void onChanged(Slice slice) {
if (slice == null) {
return;
}
if (mErrorTimeout.alarmPending()) {
mErrorTimeout.cancelAlarm();
}
if (mUriSliceMap.get(uri) != null) {
mUriSliceMap.get(uri).mLastValue = slice;
}
listener.onChanged(slice);
}
};
slc.addListener(listenerWrapper);
final SliceLifeCycle sliceLifeCycle = slc;
return () -> sliceLifeCycle.removeListener(listenerWrapper);
}
static class SearchWidgetHost extends AppWidgetHost {
SearchWidgetHost(Context context) {
super(context, SEARCH_APPWIDGET_HOST_ID);
}
@Override
protected AppWidgetHostView onCreateView(Context context, int appWidgetId,
AppWidgetProviderInfo appWidget) {
return new SearchWidgetInfoContainer(context);
}
@Override
public void clearViews() {
super.clearViews();
}
}
private static class SliceLifeCycle
implements ActivityLifecycleCallbacks, SliceCallback {
private final Uri mUri;
private final Launcher mLauncher;
private final SliceViewManager mSliceViewManager;
private final ArrayList<Observer<Slice>> mListeners = new ArrayList<>();
private boolean mDestroyed = false;
private boolean mWasListening = false;
Slice mLastValue;
SliceLifeCycle(Uri uri, Launcher launcher) {
mUri = uri;
mLauncher = launcher;
mSliceViewManager = SliceViewManager.getInstance(launcher);
launcher.registerActivityLifecycleCallbacks(this);
if (launcher.isDestroyed()) {
onActivityDestroyed(launcher);
} else if (launcher.isStarted()) {
onActivityStarted(launcher);
}
}
@Override
public void onActivityDestroyed(Activity activity) {
destroy();
}
@Override
public void onActivityStarted(Activity activity) {
updateListening();
}
@Override
public void onActivityStopped(Activity activity) {
updateListening();
}
private void updateListening() {
boolean isListening = mDestroyed
? false
: (mLauncher.isStarted() && !mListeners.isEmpty());
UI_HELPER_EXECUTOR.execute(() -> uploadListeningBg(isListening));
}
@WorkerThread
private void uploadListeningBg(boolean isListening) {
if (mWasListening != isListening) {
mWasListening = isListening;
if (isListening) {
mSliceViewManager.registerSliceCallback(mUri, MAIN_EXECUTOR, this);
// Update slice one-time on the different thread so that we can display
// multiple slices in parallel
THREAD_POOL_EXECUTOR.execute(this::updateSlice);
} else {
mSliceViewManager.unregisterSliceCallback(mUri, this);
}
}
}
@UiThread
private void addListener(Observer<Slice> listener) {
mListeners.add(listener);
updateListening();
}
@UiThread
private void removeListener(Observer<Slice> listener) {
mListeners.remove(listener);
updateListening();
}
@WorkerThread
private void updateSlice() {
try {
Slice s = mSliceViewManager.bindSlice(mUri);
MAIN_EXECUTOR.execute(() -> onSliceUpdated(s));
} catch (Exception e) {
Log.d(TAG, "Error fetching slice", e);
}
}
@UiThread
@Override
public void onSliceUpdated(@Nullable Slice s) {
mListeners.forEach(l -> l.onChanged(s));
}
private void destroy() {
if (mDestroyed) {
return;
}
mDestroyed = true;
mLauncher.unregisterActivityLifecycleCallbacks(this);
mListeners.clear();
}
@Override
public void onActivityCreated(Activity activity, Bundle bundle) { }
@Override
public void onActivityPaused(Activity activity) { }
@Override
public void onActivityResumed(Activity activity) { }
@Override
public void onActivitySaveInstanceState(Activity activity, Bundle bundle) { }
}
}

View File

@ -131,6 +131,7 @@ public class IconCache extends BaseIconCache {
/**
* Fetches high-res icon for the provided ItemInfo and updates the caller when done.
*
* @return a request ID that can be used to cancel the request.
*/
public HandlerRunnable updateIconInBackground(final ItemInfoUpdateReceiver caller,
@ -139,7 +140,7 @@ public class IconCache extends BaseIconCache {
if (mPendingIconRequestCount <= 0) {
MODEL_EXECUTOR.setThreadPriority(Process.THREAD_PRIORITY_FOREGROUND);
}
mPendingIconRequestCount ++;
mPendingIconRequestCount++;
HandlerRunnable<ItemInfoWithIcon> request = new HandlerRunnable<>(mWorkerHandler,
() -> {
@ -158,7 +159,7 @@ public class IconCache extends BaseIconCache {
}
private void onIconRequestEnd() {
mPendingIconRequestCount --;
mPendingIconRequestCount--;
if (mPendingIconRequestCount <= 0) {
MODEL_EXECUTOR.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
}
@ -289,7 +290,8 @@ public class IconCache extends BaseIconCache {
@NonNull Supplier<LauncherActivityInfo> activityInfoProvider,
boolean usePkgIcon, boolean useLowResIcon) {
CacheEntry entry = cacheLocked(infoInOut.getTargetComponent(), infoInOut.user,
activityInfoProvider, mLauncherActivityInfoCachingLogic, usePkgIcon, useLowResIcon);
activityInfoProvider, mLauncherActivityInfoCachingLogic, usePkgIcon,
useLowResIcon);
applyCacheEntry(entry, infoInOut);
}
@ -315,7 +317,8 @@ public class IconCache extends BaseIconCache {
}
public void updateSessionCache(PackageUserKey key, PackageInstaller.SessionInfo info) {
cachePackageInstallInfo(key.mPackageName, key.mUser, info.getAppIcon(), info.getAppLabel());
cachePackageInstallInfo(key.mPackageName, key.mUser, info.getAppIcon(),
info.getAppLabel());
}
@Override