diff --git a/src/com/android/launcher3/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java index 38b5c65272..da5c4c2138 100644 --- a/src/com/android/launcher3/config/FeatureFlags.java +++ b/src/com/android/launcher3/config/FeatureFlags.java @@ -277,6 +277,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;