Moving slice loading on a background thread
> Also fixing slice icon loading, such that the request is cancelled if views are recycled quickly > Loading widget label on dg thread and cancelling request if views are recycled quickly. Bug: 179068415 Test: verified locally Change-Id: Id5a524e2bf596862330a8170394aef9ffd708544
This commit is contained in:
@@ -15,28 +15,22 @@
|
||||
*/
|
||||
package com.android.launcher3.search;
|
||||
|
||||
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
|
||||
import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
|
||||
|
||||
import android.app.search.SearchTarget;
|
||||
import android.app.search.SearchTargetEvent;
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.Log;
|
||||
import android.widget.LinearLayout;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.slice.Slice;
|
||||
import androidx.slice.SliceItem;
|
||||
import androidx.slice.widget.EventInfo;
|
||||
import androidx.slice.widget.SliceView;
|
||||
|
||||
import com.android.launcher3.Launcher;
|
||||
import com.android.launcher3.LauncherAppState;
|
||||
import com.android.launcher3.R;
|
||||
import com.android.launcher3.model.data.PackageItemInfo;
|
||||
import com.android.launcher3.util.SafeCloseable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
@@ -47,13 +41,11 @@ import java.util.List;
|
||||
public class SearchResultIconSlice extends LinearLayout implements SearchTargetHandler,
|
||||
SliceView.OnSliceActionListener {
|
||||
|
||||
private static final String TAG = "SearchSliceController";
|
||||
|
||||
private final Launcher mLauncher;
|
||||
|
||||
private SliceView mSliceView;
|
||||
private SearchResultIcon mIcon;
|
||||
private LiveData<Slice> mSliceLiveData;
|
||||
private SafeCloseable mSliceSession;
|
||||
private String mTargetId;
|
||||
|
||||
public SearchResultIconSlice(Context context) {
|
||||
@@ -87,26 +79,20 @@ public class SearchResultIconSlice extends LinearLayout implements SearchTargetH
|
||||
mTargetId = parentTarget.getId();
|
||||
reset();
|
||||
updateIcon(parentTarget, children);
|
||||
try {
|
||||
mSliceLiveData = mLauncher.getLiveSearchManager().getSliceForUri(
|
||||
parentTarget.getSliceUri());
|
||||
mSliceLiveData.observe(mLauncher, mSliceView);
|
||||
} catch (Exception ex) {
|
||||
Log.e(TAG, "unable to bind slice", ex);
|
||||
}
|
||||
mSliceSession = mLauncher.getLiveSearchManager()
|
||||
.addObserver(parentTarget.getSliceUri(), mSliceView);
|
||||
}
|
||||
|
||||
private void updateIcon(SearchTarget parentTarget, List<SearchTarget> children) {
|
||||
if (children.size() == 1) {
|
||||
mIcon.apply(children.get(0), new ArrayList<>());
|
||||
} else {
|
||||
LauncherAppState appState = LauncherAppState.getInstance(getContext());
|
||||
MODEL_EXECUTOR.post(() -> {
|
||||
PackageItemInfo pkgItem = new PackageItemInfo(parentTarget.getPackageName());
|
||||
pkgItem.user = parentTarget.getUserHandle();
|
||||
appState.getIconCache().getTitleAndIconForApp(pkgItem, false);
|
||||
MAIN_EXECUTOR.post(() -> mIcon.applyFromItemInfoWithIcon(pkgItem));
|
||||
});
|
||||
PackageItemInfo pkgItem = new PackageItemInfo(parentTarget.getPackageName());
|
||||
pkgItem.user = parentTarget.getUserHandle();
|
||||
if (!pkgItem.equals(mIcon.getTag())) {
|
||||
// The icon will load and apply high res icon automatically
|
||||
mIcon.applyFromItemInfoWithIcon(pkgItem);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -124,8 +110,8 @@ public class SearchResultIconSlice extends LinearLayout implements SearchTargetH
|
||||
|
||||
private void reset() {
|
||||
mSliceView.setOnSliceActionListener(null);
|
||||
if (mSliceLiveData != null) {
|
||||
mSliceLiveData.removeObservers(mLauncher);
|
||||
if (mSliceSession != null) {
|
||||
mSliceSession.close();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -15,6 +15,9 @@
|
||||
*/
|
||||
package com.android.launcher3.search;
|
||||
|
||||
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
|
||||
import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
|
||||
|
||||
import android.app.search.SearchTarget;
|
||||
import android.app.search.SearchTargetEvent;
|
||||
import android.appwidget.AppWidgetHostView;
|
||||
@@ -36,16 +39,19 @@ import com.android.launcher3.BubbleTextView;
|
||||
import com.android.launcher3.CheckLongPressHelper;
|
||||
import com.android.launcher3.DeviceProfile;
|
||||
import com.android.launcher3.Launcher;
|
||||
import com.android.launcher3.LauncherAppState;
|
||||
import com.android.launcher3.LauncherAppWidgetProviderInfo;
|
||||
import com.android.launcher3.R;
|
||||
import com.android.launcher3.Utilities;
|
||||
import com.android.launcher3.allapps.search.SearchWidgetInfoContainer;
|
||||
import com.android.launcher3.dragndrop.DraggableView;
|
||||
import com.android.launcher3.icons.cache.HandlerRunnable;
|
||||
import com.android.launcher3.model.data.PackageItemInfo;
|
||||
import com.android.launcher3.touch.ItemLongClickListener;
|
||||
import com.android.launcher3.widget.PendingAddWidgetInfo;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
|
||||
/**
|
||||
* displays live version of a widget upon receiving {@link AppWidgetProviderInfo} from Search
|
||||
* provider
|
||||
@@ -63,6 +69,7 @@ public class SearchResultWidget extends LinearLayout implements SearchTargetHand
|
||||
private final float mScaleToFit;
|
||||
|
||||
private SearchWidgetInfoContainer mInfoContainer;
|
||||
private HandlerRunnable mLabelRequest;
|
||||
private BubbleTextView mWidgetProvider;
|
||||
private TextView mWidgetLabel;
|
||||
|
||||
@@ -124,11 +131,18 @@ public class SearchResultWidget extends LinearLayout implements SearchTargetHand
|
||||
}
|
||||
|
||||
private void showWidgetInfo(AppWidgetProviderInfo providerInfo) {
|
||||
String title = providerInfo.loadLabel(mLauncher.getPackageManager());
|
||||
PackageItemInfo pinfo = new PackageItemInfo(providerInfo.provider.getPackageName());
|
||||
pinfo.user = providerInfo.getProfile();
|
||||
mWidgetProvider.applyFromItemInfoWithIcon(pinfo);
|
||||
mWidgetLabel.setText(title);
|
||||
|
||||
mLabelRequest = new HandlerRunnable<>(
|
||||
MODEL_EXECUTOR.getHandler(),
|
||||
() -> LauncherAppState.getInstance(mLauncher).getIconCache()
|
||||
.getTitleNoCache(LauncherAppWidgetProviderInfo
|
||||
.fromProviderInfo(mLauncher, providerInfo)),
|
||||
MAIN_EXECUTOR,
|
||||
mWidgetLabel::setText);
|
||||
Utilities.postAsyncCallback(MODEL_EXECUTOR.getHandler(), mLabelRequest);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -137,7 +151,18 @@ public class SearchResultWidget extends LinearLayout implements SearchTargetHand
|
||||
public void removeListener() {
|
||||
if (mInfoContainer != null) {
|
||||
mInfoContainer.detachWidget(mHostView);
|
||||
mInfoContainer = null;
|
||||
}
|
||||
if (mLabelRequest != null) {
|
||||
mLabelRequest.cancel();
|
||||
mLabelRequest = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDetachedFromWindow() {
|
||||
super.onDetachedFromWindow();
|
||||
removeListener();
|
||||
}
|
||||
|
||||
private void reportEvent(int eventType) {
|
||||
|
||||
@@ -66,9 +66,9 @@ import com.android.launcher3.graphics.IconShape;
|
||||
import com.android.launcher3.graphics.PlaceHolderIconDrawable;
|
||||
import com.android.launcher3.graphics.PreloadIconDrawable;
|
||||
import com.android.launcher3.icons.DotRenderer;
|
||||
import com.android.launcher3.icons.IconCache.IconLoadRequest;
|
||||
import com.android.launcher3.icons.IconCache.ItemInfoUpdateReceiver;
|
||||
import com.android.launcher3.icons.LauncherIcons;
|
||||
import com.android.launcher3.icons.cache.HandlerRunnable;
|
||||
import com.android.launcher3.model.data.AppInfo;
|
||||
import com.android.launcher3.model.data.ItemInfo;
|
||||
import com.android.launcher3.model.data.ItemInfoWithIcon;
|
||||
@@ -171,7 +171,7 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver,
|
||||
@ViewDebug.ExportedProperty(category = "launcher")
|
||||
private boolean mDisableRelayout = false;
|
||||
|
||||
private IconLoadRequest mIconLoadRequest;
|
||||
private HandlerRunnable mIconLoadRequest;
|
||||
|
||||
private boolean mEnableIconUpdateAnimation = false;
|
||||
|
||||
|
||||
@@ -98,13 +98,9 @@ import android.widget.ImageView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.CallSuper;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.StringRes;
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
import androidx.lifecycle.Lifecycle;
|
||||
import androidx.lifecycle.LifecycleOwner;
|
||||
import androidx.lifecycle.LifecycleRegistry;
|
||||
|
||||
import com.android.launcher3.DropTarget.DragObject;
|
||||
import com.android.launcher3.accessibility.LauncherAccessibilityDelegate;
|
||||
@@ -211,8 +207,7 @@ import java.util.stream.Stream;
|
||||
* Default launcher application.
|
||||
*/
|
||||
public class Launcher extends StatefulActivity<LauncherState> implements LauncherExterns,
|
||||
Callbacks, InvariantDeviceProfile.OnIDPChangeListener, PluginListener<OverlayPlugin>,
|
||||
LifecycleOwner {
|
||||
Callbacks, InvariantDeviceProfile.OnIDPChangeListener, PluginListener<OverlayPlugin> {
|
||||
public static final String TAG = "Launcher";
|
||||
|
||||
public static final ActivityTracker<Launcher> ACTIVITY_TRACKER = new ActivityTracker<>();
|
||||
@@ -275,8 +270,6 @@ public class Launcher extends StatefulActivity<LauncherState> implements Launche
|
||||
private LauncherAppTransitionManager mAppTransitionManager;
|
||||
private Configuration mOldConfig;
|
||||
|
||||
private LifecycleRegistry mLifecycleRegistry;
|
||||
|
||||
private LiveSearchManager mLiveSearchManager;
|
||||
|
||||
@Thunk
|
||||
@@ -392,12 +385,12 @@ public class Launcher extends StatefulActivity<LauncherState> implements Launche
|
||||
mIconCache = app.getIconCache();
|
||||
mAccessibilityDelegate = new LauncherAccessibilityDelegate(this);
|
||||
|
||||
mLiveSearchManager = new LiveSearchManager(this);
|
||||
|
||||
mDragController = new DragController(this);
|
||||
mAllAppsController = new AllAppsTransitionController(this);
|
||||
mStateManager = new StateManager<>(this, NORMAL);
|
||||
|
||||
mLiveSearchManager = new LiveSearchManager(this);
|
||||
|
||||
mOnboardingPrefs = createOnboardingPrefs(mSharedPrefs);
|
||||
|
||||
mAppWidgetManager = new WidgetManagerHelper(this);
|
||||
@@ -486,15 +479,6 @@ public class Launcher extends StatefulActivity<LauncherState> implements Launche
|
||||
if (Utilities.ATLEAST_R) {
|
||||
getWindow().setSoftInputMode(LayoutParams.SOFT_INPUT_ADJUST_NOTHING);
|
||||
}
|
||||
|
||||
mLifecycleRegistry = new LifecycleRegistry(this);
|
||||
mLifecycleRegistry.setCurrentState(Lifecycle.State.CREATED);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Lifecycle getLifecycle() {
|
||||
return mLifecycleRegistry;
|
||||
}
|
||||
|
||||
public LiveSearchManager getLiveSearchManager() {
|
||||
@@ -913,7 +897,6 @@ public class Launcher extends StatefulActivity<LauncherState> implements Launche
|
||||
|
||||
@Override
|
||||
protected void onStop() {
|
||||
mLifecycleRegistry.setCurrentState(Lifecycle.State.CREATED);
|
||||
super.onStop();
|
||||
if (mDeferOverlayCallbacks) {
|
||||
checkIfOverlayStillDeferred();
|
||||
@@ -937,7 +920,6 @@ public class Launcher extends StatefulActivity<LauncherState> implements Launche
|
||||
|
||||
mAppWidgetHost.setListenIfResumed(true);
|
||||
TraceHelper.INSTANCE.endSection(traceToken);
|
||||
mLifecycleRegistry.setCurrentState(Lifecycle.State.STARTED);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -1091,7 +1073,6 @@ public class Launcher extends StatefulActivity<LauncherState> implements Launche
|
||||
}
|
||||
|
||||
TraceHelper.INSTANCE.endSection(traceToken);
|
||||
mLifecycleRegistry.setCurrentState(Lifecycle.State.RESUMED);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -1099,7 +1080,6 @@ public class Launcher extends StatefulActivity<LauncherState> implements Launche
|
||||
// Ensure that items added to Launcher are queued until Launcher returns
|
||||
ItemInstallQueue.INSTANCE.get(this).pauseModelPush(FLAG_ACTIVITY_PAUSED);
|
||||
|
||||
mLifecycleRegistry.setCurrentState(Lifecycle.State.STARTED);
|
||||
super.onPause();
|
||||
mDragController.cancelDrag();
|
||||
mLastTouchUpTime = -1;
|
||||
@@ -1598,7 +1578,6 @@ public class Launcher extends StatefulActivity<LauncherState> implements Launche
|
||||
mAppTransitionManager.unregisterRemoteAnimations();
|
||||
mAppTransitionManager.unregisterRemoteTransitions();
|
||||
mUserChangedCallbackCloseable.close();
|
||||
mLifecycleRegistry.setCurrentState(Lifecycle.State.DESTROYED);
|
||||
mLiveSearchManager.stop();
|
||||
}
|
||||
|
||||
|
||||
@@ -268,13 +268,10 @@ public class AllAppsTransitionController implements StateHandler<LauncherState>,
|
||||
&& !FeatureFlags.DISABLE_INITIAL_IME_IN_ALLAPPS.get() && BuildCompat.isAtLeastR()) {
|
||||
mInsetController.onAnimationEnd(mProgress);
|
||||
if (Float.compare(mProgress, 0f) == 0) {
|
||||
mLauncher.getLiveSearchManager().start();
|
||||
EditText editText = mAppsView.getSearchUiManager().getEditText();
|
||||
if (editText != null && !mInsetController.showSearchEduIfNecessary()) {
|
||||
editText.requestFocus();
|
||||
}
|
||||
} else {
|
||||
mLauncher.getLiveSearchManager().stop();
|
||||
}
|
||||
// TODO: should make the controller hide synchronously
|
||||
}
|
||||
|
||||
@@ -15,8 +15,14 @@
|
||||
*/
|
||||
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;
|
||||
@@ -26,39 +32,49 @@ import android.content.Context;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.UserHandle;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.UiThread;
|
||||
import androidx.annotation.WorkerThread;
|
||||
import androidx.lifecycle.Observer;
|
||||
import androidx.slice.Slice;
|
||||
import androidx.slice.widget.SliceLiveData;
|
||||
import androidx.slice.SliceViewManager;
|
||||
import androidx.slice.SliceViewManager.SliceCallback;
|
||||
|
||||
import com.android.launcher3.Launcher;
|
||||
import com.android.launcher3.LauncherAppWidgetProviderInfo;
|
||||
import com.android.launcher3.LauncherState;
|
||||
import com.android.launcher3.logging.InstanceId;
|
||||
import com.android.launcher3.logging.InstanceIdSequence;
|
||||
import com.android.launcher3.statemanager.StateManager.StateListener;
|
||||
import com.android.launcher3.util.ComponentKey;
|
||||
import com.android.launcher3.util.SafeCloseable;
|
||||
import com.android.launcher3.widget.PendingAddWidgetInfo;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Manages Lifecycle for Live search results
|
||||
*/
|
||||
public class LiveSearchManager {
|
||||
public class LiveSearchManager implements StateListener<LauncherState> {
|
||||
|
||||
private static final String TAG = "LiveSearchManager";
|
||||
|
||||
public static final int SEARCH_APPWIDGET_HOST_ID = 2048;
|
||||
|
||||
private final Launcher mLauncher;
|
||||
private final AppWidgetManager mAppWidgetManger;
|
||||
private final HashMap<Uri, SliceLifeCycle> mUriSliceMap = new HashMap<>();
|
||||
|
||||
private final HashMap<ComponentKey, SearchWidgetInfoContainer> mWidgetPlaceholders =
|
||||
new HashMap<>();
|
||||
private final HashMap<Uri, LiveData<Slice>> mUriSliceMap = new HashMap<>();
|
||||
private SearchWidgetHost mSearchWidgetHost;
|
||||
private InstanceId mLogInstanceId;
|
||||
|
||||
public LiveSearchManager(Launcher launcher) {
|
||||
mLauncher = launcher;
|
||||
mAppWidgetManger = AppWidgetManager.getInstance(launcher);
|
||||
mLauncher.getStateManager().addStateListener(this);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -67,78 +83,80 @@ public class LiveSearchManager {
|
||||
*/
|
||||
public SearchWidgetInfoContainer getPlaceHolderWidget(AppWidgetProviderInfo providerInfo) {
|
||||
if (mSearchWidgetHost == null) {
|
||||
throw new RuntimeException("AppWidgetHost has not been created yet");
|
||||
mSearchWidgetHost = new SearchWidgetHost(mLauncher);
|
||||
mSearchWidgetHost.startListening();
|
||||
}
|
||||
|
||||
ComponentName provider = providerInfo.provider;
|
||||
UserHandle userHandle = providerInfo.getProfile();
|
||||
|
||||
ComponentKey key = new ComponentKey(provider, userHandle);
|
||||
SearchWidgetInfoContainer view = mWidgetPlaceholders.getOrDefault(key, null);
|
||||
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 = mAppWidgetManger.bindAppWidgetIdIfAllowed(appWidgetId, userHandle,
|
||||
provider, options);
|
||||
boolean success = AppWidgetManager.getInstance(mLauncher)
|
||||
.bindAppWidgetIdIfAllowed(appWidgetId, userHandle, provider, options);
|
||||
if (!success) {
|
||||
mSearchWidgetHost.deleteAppWidgetId(appWidgetId);
|
||||
mWidgetPlaceholders.put(key, null);
|
||||
return null;
|
||||
}
|
||||
|
||||
view = (SearchWidgetInfoContainer) mSearchWidgetHost.createView(mLauncher, appWidgetId,
|
||||
providerInfo);
|
||||
SearchWidgetInfoContainer view = (SearchWidgetInfoContainer) mSearchWidgetHost.createView(
|
||||
mLauncher, appWidgetId, providerInfo);
|
||||
view.setTag(pendingAddWidgetInfo);
|
||||
mWidgetPlaceholders.put(key, view);
|
||||
return view;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates {@link LiveData<Slice>} from Slice Uri. Caches created live data to be reused
|
||||
* within the same search session. Removes previous observers when new SliceView request a
|
||||
* live data for observation.
|
||||
*/
|
||||
public LiveData<Slice> getSliceForUri(Uri sliceUri) {
|
||||
LiveData<Slice> sliceLiveData = mUriSliceMap.getOrDefault(sliceUri, null);
|
||||
if (sliceLiveData == null) {
|
||||
sliceLiveData = SliceLiveData.fromUri(mLauncher, sliceUri);
|
||||
mUriSliceMap.put(sliceUri, sliceLiveData);
|
||||
}
|
||||
return sliceLiveData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Start search session
|
||||
*/
|
||||
public void start() {
|
||||
stop();
|
||||
mLogInstanceId = new InstanceIdSequence().newInstanceId();
|
||||
mSearchWidgetHost = new SearchWidgetHost(mLauncher);
|
||||
mSearchWidgetHost.startListening();
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop search session
|
||||
*/
|
||||
public void stop() {
|
||||
clearWidgetHost();
|
||||
}
|
||||
|
||||
private void clearWidgetHost() {
|
||||
if (mSearchWidgetHost != null) {
|
||||
mSearchWidgetHost.stopListening();
|
||||
mSearchWidgetHost.clearViews();
|
||||
mSearchWidgetHost.deleteHost();
|
||||
for (SearchWidgetInfoContainer placeholder : mWidgetPlaceholders.values()) {
|
||||
placeholder.clearListeners();
|
||||
}
|
||||
mWidgetPlaceholders.clear();
|
||||
mSearchWidgetHost = null;
|
||||
}
|
||||
for (LiveData<Slice> liveData : mUriSliceMap.values()) {
|
||||
liveData.removeObservers(mLauncher);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStateTransitionComplete(LauncherState finalState) {
|
||||
if (finalState != ALL_APPS) {
|
||||
// Clear all search session related objects
|
||||
mUriSliceMap.values().forEach(SliceLifeCycle::destroy);
|
||||
mUriSliceMap.clear();
|
||||
|
||||
clearWidgetHost();
|
||||
}
|
||||
mUriSliceMap.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a new observer for the provided uri and returns a callback to cancel this observer
|
||||
*/
|
||||
public SafeCloseable addObserver(Uri uri, Observer<Slice> listener) {
|
||||
SliceLifeCycle slc = mUriSliceMap.get(uri);
|
||||
if (slc == null) {
|
||||
slc = new SliceLifeCycle(uri, mLauncher);
|
||||
mUriSliceMap.put(uri, slc);
|
||||
}
|
||||
slc.addListener(listener);
|
||||
|
||||
final SliceLifeCycle sliceLifeCycle = slc;
|
||||
return () -> sliceLifeCycle.removeListener(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -159,5 +177,121 @@ public class LiveSearchManager {
|
||||
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;
|
||||
|
||||
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) { }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,10 +70,4 @@ public class SearchWidgetInfoContainer extends AppWidgetHostView {
|
||||
mListeners.remove(hostView);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all AppWidgetHost update listeners
|
||||
*/
|
||||
public void clearListeners() {
|
||||
mListeners.clear();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,7 +31,6 @@ import android.content.pm.PackageManager;
|
||||
import android.content.pm.PackageManager.NameNotFoundException;
|
||||
import android.content.pm.ShortcutInfo;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Handler;
|
||||
import android.os.Process;
|
||||
import android.os.UserHandle;
|
||||
import android.util.Log;
|
||||
@@ -134,7 +133,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 IconLoadRequest updateIconInBackground(final ItemInfoUpdateReceiver caller,
|
||||
public HandlerRunnable updateIconInBackground(final ItemInfoUpdateReceiver caller,
|
||||
final ItemInfoWithIcon info) {
|
||||
Preconditions.assertUIThread();
|
||||
if (mPendingIconRequestCount <= 0) {
|
||||
@@ -142,20 +141,18 @@ public class IconCache extends BaseIconCache {
|
||||
}
|
||||
mPendingIconRequestCount ++;
|
||||
|
||||
IconLoadRequest request = new IconLoadRequest(mWorkerHandler, this::onIconRequestEnd) {
|
||||
@Override
|
||||
public void run() {
|
||||
if (info instanceof AppInfo || info instanceof WorkspaceItemInfo) {
|
||||
getTitleAndIcon(info, false);
|
||||
} else if (info instanceof PackageItemInfo) {
|
||||
getTitleAndIconForApp((PackageItemInfo) info, false);
|
||||
}
|
||||
MAIN_EXECUTOR.execute(() -> {
|
||||
caller.reapplyItemInfo(info);
|
||||
onEnd();
|
||||
});
|
||||
}
|
||||
};
|
||||
HandlerRunnable<ItemInfoWithIcon> request = new HandlerRunnable<>(mWorkerHandler,
|
||||
() -> {
|
||||
if (info instanceof AppInfo || info instanceof WorkspaceItemInfo) {
|
||||
getTitleAndIcon(info, false);
|
||||
} else if (info instanceof PackageItemInfo) {
|
||||
getTitleAndIconForApp((PackageItemInfo) info, false);
|
||||
}
|
||||
return info;
|
||||
},
|
||||
MAIN_EXECUTOR,
|
||||
caller::reapplyItemInfo,
|
||||
this::onIconRequestEnd);
|
||||
Utilities.postAsyncCallback(mWorkerHandler, request);
|
||||
return request;
|
||||
}
|
||||
@@ -336,12 +333,6 @@ public class IconCache extends BaseIconCache {
|
||||
return super.getEntryFromDB(cacheKey, entry, lowRes);
|
||||
}
|
||||
|
||||
public static abstract class IconLoadRequest extends HandlerRunnable {
|
||||
IconLoadRequest(Handler handler, Runnable endRunnable) {
|
||||
super(handler, endRunnable);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface for receiving itemInfo with high-res icon.
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user