Cache and reuses LauncherAppWidgetHostView when launcher resumes
Currently by design when launcher enters the background, it stops listening to updates in widgets. This eventually causes the dilemma for launcher when it resumes, before the update can be returned from the system process via IPC, launcher could do one of the following to fill the gap: 1. show a deferred widget view -- a placeholder that renders the shape of the widget -- to let the user know widget is being reloaded. 2. show whichever widget view that was previously displayed to the user that may now contain stale content. There is a descrepancy here since in some edge cases we are showing the former while in most other cases we are showing the later. This CL added a short-term fix to address the descrepancy and favors the later where possible. Bug: 218067434 Test: manual Change-Id: I6cd2cd704186267227e2ec47f2581843fd526fa0
This commit is contained in:
@@ -274,6 +274,10 @@ public final class FeatureFlags {
|
||||
"ENABLE_DISMISS_PREDICTION_UNDO", false,
|
||||
"Show an 'Undo' snackbar when users dismiss a predicted hotseat item");
|
||||
|
||||
public static final BooleanFlag ENABLE_CACHED_WIDGET = getDebugFlag(
|
||||
"ENABLE_CACHED_WIDGET", true,
|
||||
"Show previously cached widgets as opposed to deferred widget where available");
|
||||
|
||||
public static void initialize(Context context) {
|
||||
synchronized (sDebugFlags) {
|
||||
for (DebugFlag flag : sDebugFlags) {
|
||||
|
||||
@@ -28,6 +28,7 @@ import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.util.SparseArray;
|
||||
import android.widget.RemoteViews;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
@@ -37,6 +38,7 @@ import com.android.launcher3.BaseDraggingActivity;
|
||||
import com.android.launcher3.LauncherAppState;
|
||||
import com.android.launcher3.R;
|
||||
import com.android.launcher3.Utilities;
|
||||
import com.android.launcher3.config.FeatureFlags;
|
||||
import com.android.launcher3.model.WidgetsModel;
|
||||
import com.android.launcher3.model.data.ItemInfo;
|
||||
import com.android.launcher3.testing.TestLogging;
|
||||
@@ -70,13 +72,14 @@ public class LauncherAppWidgetHost extends AppWidgetHost {
|
||||
private final ArrayList<ProviderChangedListener> mProviderChangeListeners = new ArrayList<>();
|
||||
private final SparseArray<LauncherAppWidgetHostView> mViews = new SparseArray<>();
|
||||
private final SparseArray<PendingAppWidgetHostView> mPendingViews = new SparseArray<>();
|
||||
private final SparseArray<LauncherAppWidgetHostView> mDeferredViews = new SparseArray<>();
|
||||
private final SparseArray<RemoteViews> mCachedRemoteViews = new SparseArray<>();
|
||||
|
||||
private final Context mContext;
|
||||
private int mFlags = FLAG_STATE_IS_NORMAL;
|
||||
|
||||
private IntConsumer mAppWidgetRemovedCallback = null;
|
||||
|
||||
|
||||
public LauncherAppWidgetHost(Context context) {
|
||||
this(context, null);
|
||||
}
|
||||
@@ -95,6 +98,11 @@ public class LauncherAppWidgetHost extends AppWidgetHost {
|
||||
if (mPendingViews.get(appWidgetId) != null) {
|
||||
view = mPendingViews.get(appWidgetId);
|
||||
mPendingViews.remove(appWidgetId);
|
||||
} else if (mDeferredViews.get(appWidgetId) != null) {
|
||||
// In case the widget view is deferred, we will simply return the deferred view as
|
||||
// opposed to instantiate a new instance of LauncherAppWidgetHostView since launcher
|
||||
// already added the former to the workspace.
|
||||
view = mDeferredViews.get(appWidgetId);
|
||||
} else {
|
||||
view = new LauncherAppWidgetHostView(context);
|
||||
}
|
||||
@@ -120,12 +128,25 @@ public class LauncherAppWidgetHost extends AppWidgetHost {
|
||||
// widgets upon bind anyway. See issue 14255011 for more context.
|
||||
}
|
||||
|
||||
// We go in reverse order and inflate any deferred widget
|
||||
// We go in reverse order and inflate any deferred or cached widget
|
||||
for (int i = mViews.size() - 1; i >= 0; i--) {
|
||||
LauncherAppWidgetHostView view = mViews.valueAt(i);
|
||||
if (view instanceof DeferredAppWidgetHostView) {
|
||||
view.reInflate();
|
||||
}
|
||||
if (FeatureFlags.ENABLE_CACHED_WIDGET.get()) {
|
||||
final int appWidgetId = mViews.keyAt(i);
|
||||
if (view == mDeferredViews.get(appWidgetId)) {
|
||||
// If the widget view was deferred, we'll need to call super.createView here
|
||||
// to make the binder call to system process to fetch cumulative updates to this
|
||||
// widget, as well as setting up this view for future updates.
|
||||
super.createView(view.mLauncher, appWidgetId, view.getAppWidgetInfo());
|
||||
// At this point #onCreateView should have been called, which in turn returned
|
||||
// the deferred view. There's no reason to keep the reference anymore, so we
|
||||
// removed it here.
|
||||
mDeferredViews.remove(appWidgetId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -221,10 +242,28 @@ public class LauncherAppWidgetHost extends AppWidgetHost {
|
||||
CustomWidgetManager.INSTANCE.get(context).onViewCreated(lahv);
|
||||
return lahv;
|
||||
} else if ((mFlags & FLAG_LISTENING) == 0) {
|
||||
DeferredAppWidgetHostView view = new DeferredAppWidgetHostView(context);
|
||||
view.setAppWidget(appWidgetId, appWidget);
|
||||
mViews.put(appWidgetId, view);
|
||||
return view;
|
||||
// Since the launcher hasn't started listening to widget updates, we can't simply call
|
||||
// super.createView here because the later will make a binder call to retrieve
|
||||
// RemoteViews from system process.
|
||||
// TODO: have launcher always listens to widget updates in background so that this
|
||||
// check can be removed altogether.
|
||||
if (FeatureFlags.ENABLE_CACHED_WIDGET.get()
|
||||
&& mCachedRemoteViews.get(appWidgetId) != null) {
|
||||
// We've found RemoteViews from cache for this widget, so we will instantiate a
|
||||
// widget host view and populate it with the cached RemoteViews.
|
||||
final LauncherAppWidgetHostView view = new LauncherAppWidgetHostView(context);
|
||||
view.setAppWidget(appWidgetId, appWidget);
|
||||
view.updateAppWidget(mCachedRemoteViews.get(appWidgetId));
|
||||
mDeferredViews.put(appWidgetId, view);
|
||||
mViews.put(appWidgetId, view);
|
||||
return view;
|
||||
} else {
|
||||
// When cache misses, a placeholder for the widget will be returned instead.
|
||||
DeferredAppWidgetHostView view = new DeferredAppWidgetHostView(context);
|
||||
view.setAppWidget(appWidgetId, appWidget);
|
||||
mViews.put(appWidgetId, view);
|
||||
return view;
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
return super.createView(context, appWidgetId, appWidget);
|
||||
@@ -281,6 +320,16 @@ public class LauncherAppWidgetHost extends AppWidgetHost {
|
||||
@Override
|
||||
public void clearViews() {
|
||||
super.clearViews();
|
||||
if (FeatureFlags.ENABLE_CACHED_WIDGET.get()) {
|
||||
// First, we clear any previously cached content from existing widgets
|
||||
mCachedRemoteViews.clear();
|
||||
// Then we proceed to cache the content from the widgets
|
||||
for (int i = 0; i < mViews.size(); i++) {
|
||||
final int appWidgetId = mViews.keyAt(i);
|
||||
final LauncherAppWidgetHostView view = mViews.get(appWidgetId);
|
||||
mCachedRemoteViews.put(appWidgetId, view.mLastRemoteViews);
|
||||
}
|
||||
}
|
||||
mViews.clear();
|
||||
}
|
||||
|
||||
|
||||
@@ -43,6 +43,7 @@ import com.android.launcher3.CheckLongPressHelper;
|
||||
import com.android.launcher3.Launcher;
|
||||
import com.android.launcher3.R;
|
||||
import com.android.launcher3.Utilities;
|
||||
import com.android.launcher3.config.FeatureFlags;
|
||||
import com.android.launcher3.dragndrop.DragLayer;
|
||||
import com.android.launcher3.model.data.ItemInfo;
|
||||
import com.android.launcher3.model.data.LauncherAppWidgetInfo;
|
||||
@@ -85,7 +86,7 @@ public class LauncherAppWidgetHostView extends BaseLauncherAppWidgetHostView
|
||||
private Runnable mAutoAdvanceRunnable;
|
||||
|
||||
private long mDeferUpdatesUntilMillis = 0;
|
||||
private RemoteViews mDeferredRemoteViews;
|
||||
RemoteViews mLastRemoteViews;
|
||||
private boolean mHasDeferredColorChange = false;
|
||||
private @Nullable SparseIntArray mDeferredColorChange = null;
|
||||
|
||||
@@ -150,11 +151,18 @@ public class LauncherAppWidgetHostView extends BaseLauncherAppWidgetHostView
|
||||
TRACE_METHOD_NAME + getAppWidgetInfo().provider, getAppWidgetId());
|
||||
mTrackingWidgetUpdate = false;
|
||||
}
|
||||
if (isDeferringUpdates()) {
|
||||
mDeferredRemoteViews = remoteViews;
|
||||
return;
|
||||
if (FeatureFlags.ENABLE_CACHED_WIDGET.get()) {
|
||||
mLastRemoteViews = remoteViews;
|
||||
if (isDeferringUpdates()) {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
if (isDeferringUpdates()) {
|
||||
mLastRemoteViews = remoteViews;
|
||||
return;
|
||||
}
|
||||
mLastRemoteViews = null;
|
||||
}
|
||||
mDeferredRemoteViews = null;
|
||||
|
||||
super.updateAppWidget(remoteViews);
|
||||
|
||||
@@ -218,8 +226,7 @@ public class LauncherAppWidgetHostView extends BaseLauncherAppWidgetHostView
|
||||
SparseIntArray deferredColors;
|
||||
boolean hasDeferredColors;
|
||||
mDeferUpdatesUntilMillis = 0;
|
||||
remoteViews = mDeferredRemoteViews;
|
||||
mDeferredRemoteViews = null;
|
||||
remoteViews = mLastRemoteViews;
|
||||
deferredColors = mDeferredColorChange;
|
||||
hasDeferredColors = mHasDeferredColorChange;
|
||||
mDeferredColorChange = null;
|
||||
|
||||
Reference in New Issue
Block a user