From e5dbb75acdc47cd394ccf7e215e5dfc25c5ba6dd Mon Sep 17 00:00:00 2001 From: Pinyao Ting Date: Wed, 8 Jun 2022 15:00:20 -0700 Subject: [PATCH] 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 --- .../launcher3/config/FeatureFlags.java | 4 ++ .../widget/LauncherAppWidgetHost.java | 61 +++++++++++++++++-- .../widget/LauncherAppWidgetHostView.java | 21 ++++--- 3 files changed, 73 insertions(+), 13 deletions(-) diff --git a/src/com/android/launcher3/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java index 7e953af8be..99ada34dce 100644 --- a/src/com/android/launcher3/config/FeatureFlags.java +++ b/src/com/android/launcher3/config/FeatureFlags.java @@ -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) { diff --git a/src/com/android/launcher3/widget/LauncherAppWidgetHost.java b/src/com/android/launcher3/widget/LauncherAppWidgetHost.java index fe83f3f606..98a960c535 100644 --- a/src/com/android/launcher3/widget/LauncherAppWidgetHost.java +++ b/src/com/android/launcher3/widget/LauncherAppWidgetHost.java @@ -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 mProviderChangeListeners = new ArrayList<>(); private final SparseArray mViews = new SparseArray<>(); private final SparseArray mPendingViews = new SparseArray<>(); + private final SparseArray mDeferredViews = new SparseArray<>(); + private final SparseArray 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(); } diff --git a/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java b/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java index 08651523a6..fc1e880373 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.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;