From bd638e7b41d5bf80ee300e3138aa48010dcff446 Mon Sep 17 00:00:00 2001 From: Pinyao Ting Date: Fri, 17 Mar 2023 16:33:24 -0700 Subject: [PATCH] Retains cached widget in launcher process Currently cached widget are retained in LauncherWidgetHolder which is released when Launcher activity is recreated. This CL moves the cached widget into LauncherAppState to keep the cache alive. Bug: 268189435 Test: steps below 1. Add multiple widgets (Calendar / Weather ... e.t.c) to Home Screen 2. Open Google Map, start navigation to any place 3. Google Map enters navigation mode and changes resolution 4. Swipe up to exit Google Map and go to Home Screen 5. Verify you don't see deferred widget host view. Change-Id: I8b56167313780cd1be2a5da88517114acc6d44af --- .../android/launcher3/LauncherAppState.java | 10 +++ .../widget/LauncherWidgetHolder.java | 71 ++++++++++++------- 2 files changed, 54 insertions(+), 27 deletions(-) diff --git a/src/com/android/launcher3/LauncherAppState.java b/src/com/android/launcher3/LauncherAppState.java index c81ad01f2e..abf58661e7 100644 --- a/src/com/android/launcher3/LauncherAppState.java +++ b/src/com/android/launcher3/LauncherAppState.java @@ -31,7 +31,11 @@ import android.content.SharedPreferences.OnSharedPreferenceChangeListener; import android.content.pm.LauncherApps; import android.os.UserHandle; import android.util.Log; +import android.util.SparseArray; +import android.widget.RemoteViews; +import androidx.annotation.GuardedBy; +import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.android.launcher3.config.FeatureFlags; @@ -70,6 +74,12 @@ public class LauncherAppState implements SafeCloseable { private final InvariantDeviceProfile mInvariantDeviceProfile; private final RunnableList mOnTerminateCallback = new RunnableList(); + // WORKAROUND: b/269335387 remove this after widget background listener is enabled + /* Array of RemoteViews cached by Launcher process */ + @GuardedBy("itself") + @NonNull + public final SparseArray mCachedRemoteViews = new SparseArray<>(); + public static LauncherAppState getInstance(final Context context) { return INSTANCE.get(context); } diff --git a/src/com/android/launcher3/widget/LauncherWidgetHolder.java b/src/com/android/launcher3/widget/LauncherWidgetHolder.java index 8e67eb110b..2ca825cf6b 100644 --- a/src/com/android/launcher3/widget/LauncherWidgetHolder.java +++ b/src/com/android/launcher3/widget/LauncherWidgetHolder.java @@ -36,6 +36,7 @@ import androidx.annotation.Nullable; import com.android.launcher3.BaseActivity; 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; @@ -74,8 +75,6 @@ public class LauncherWidgetHolder { private final SparseArray mPendingViews = new SparseArray<>(); @NonNull private final SparseArray mDeferredViews = new SparseArray<>(); - @NonNull - private final SparseArray mCachedRemoteViews = new SparseArray<>(); protected int mFlags = FLAG_STATE_IS_NORMAL; @@ -174,6 +173,12 @@ public class LauncherWidgetHolder { public void deleteAppWidgetId(int appWidgetId) { mWidgetHost.deleteAppWidgetId(appWidgetId); mViews.remove(appWidgetId); + if (FeatureFlags.ENABLE_CACHED_WIDGET.get()) { + final LauncherAppState state = LauncherAppState.getInstance(mContext); + synchronized (state.mCachedRemoteViews) { + state.mCachedRemoteViews.delete(appWidgetId); + } + } } /** @@ -308,7 +313,17 @@ public class LauncherWidgetHolder { if (WidgetsModel.GO_DISABLE_WIDGETS) { return; } - + if (FeatureFlags.ENABLE_CACHED_WIDGET.get()) { + // Cache the content from the widgets when Launcher stops listening to widget updates + final LauncherAppState state = LauncherAppState.getInstance(mContext); + synchronized (state.mCachedRemoteViews) { + for (int i = 0; i < mViews.size(); i++) { + final int appWidgetId = mViews.keyAt(i); + final LauncherAppWidgetHostView view = mViews.get(appWidgetId); + state.mCachedRemoteViews.put(appWidgetId, view.mLastRemoteViews); + } + } + } mWidgetHost.stopListening(); setListeningFlag(false); } @@ -350,23 +365,24 @@ public class LauncherWidgetHolder { // 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; + if (FeatureFlags.ENABLE_CACHED_WIDGET.get()) { + final RemoteViews cachedRemoteViews = getCachedRemoteViews(appWidgetId); + if (cachedRemoteViews != 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(cachedRemoteViews); + mDeferredViews.put(appWidgetId, view); + mViews.put(appWidgetId, view); + return view; + } } + // If cache misses or not enabled, a placeholder for the widget will be returned. + DeferredAppWidgetHostView view = new DeferredAppWidgetHostView(context); + view.setAppWidget(appWidgetId, appWidget); + mViews.put(appWidgetId, view); + return view; } else { try { return mWidgetHost.createView(context, appWidgetId, appWidget); @@ -432,15 +448,8 @@ public class LauncherWidgetHolder { LauncherAppWidgetHost tempHost = (LauncherAppWidgetHost) mWidgetHost; tempHost.clearViews(); if (FeatureFlags.ENABLE_CACHED_WIDGET.get()) { - // First, we clear any previously cached content from existing widgets - mCachedRemoteViews.clear(); + // Clear previously cached content from existing widgets mDeferredViews.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(); } @@ -481,6 +490,14 @@ public class LauncherWidgetHolder { return (flags & FLAGS_SHOULD_LISTEN) == FLAGS_SHOULD_LISTEN; } + @Nullable + private RemoteViews getCachedRemoteViews(int appWidgetId) { + final LauncherAppState state = LauncherAppState.getInstance(mContext); + synchronized (state.mCachedRemoteViews) { + return state.mCachedRemoteViews.get(appWidgetId); + } + } + /** * Returns the new LauncherWidgetHolder instance */