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
diff --git a/src/com/android/launcher3/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java
index 7e953af..99ada34 100644
--- a/src/com/android/launcher3/config/FeatureFlags.java
+++ b/src/com/android/launcher3/config/FeatureFlags.java
@@ -274,6 +274,10 @@
"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) {
diff --git a/src/com/android/launcher3/widget/LauncherAppWidgetHost.java b/src/com/android/launcher3/widget/LauncherAppWidgetHost.java
index fe83f3f..98a960c 100644
--- a/src/com/android/launcher3/widget/LauncherAppWidgetHost.java
+++ b/src/com/android/launcher3/widget/LauncherAppWidgetHost.java
@@ -28,6 +28,7 @@
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.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 @@
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 @@
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 @@
// 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 @@
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 @@
@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();
}
diff --git a/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java b/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java
index 0865152..fc1e880 100644
--- a/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java
+++ b/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java
@@ -43,6 +43,7 @@
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 @@
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 @@
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 @@
SparseIntArray deferredColors;
boolean hasDeferredColors;
mDeferUpdatesUntilMillis = 0;
- remoteViews = mDeferredRemoteViews;
- mDeferredRemoteViews = null;
+ remoteViews = mLastRemoteViews;
deferredColors = mDeferredColorChange;
hasDeferredColors = mHasDeferredColorChange;
mDeferredColorChange = null;