From a13cc8167fe596864244b56a76cdcd3a440dd1fc Mon Sep 17 00:00:00 2001 From: Sunny Goyal Date: Mon, 13 Feb 2023 11:37:20 -0800 Subject: [PATCH] Deferred widget update Maintaing a list of pending updates while the host is not listening, so that these can be applied when host starts listening again Bug: 267351283 Test: Manual Change-Id: I38423862a3461724b36f17ffe44e44c55fee06de --- .../uioverrides/QuickstepWidgetHolder.java | 105 ++++++++++++++++-- .../widget/LauncherWidgetHolder.java | 27 ++++- 2 files changed, 116 insertions(+), 16 deletions(-) diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepWidgetHolder.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepWidgetHolder.java index a8edd511f6..278a45ae26 100644 --- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepWidgetHolder.java +++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepWidgetHolder.java @@ -32,6 +32,7 @@ import androidx.annotation.WorkerThread; import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.model.WidgetsModel; +import com.android.launcher3.util.IntSet; import com.android.launcher3.util.Thunk; import com.android.launcher3.widget.LauncherAppWidgetHostView; import com.android.launcher3.widget.LauncherAppWidgetProviderInfo; @@ -41,13 +42,21 @@ import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.WeakHashMap; -import java.util.function.Consumer; +import java.util.function.BiConsumer; import java.util.function.IntConsumer; /** * {@link LauncherWidgetHolder} that puts the app widget host in the background */ public final class QuickstepWidgetHolder extends LauncherWidgetHolder { + + private static final UpdateKey KEY_PROVIDER_UPDATE = + AppWidgetHostView::onUpdateProviderInfo; + private static final UpdateKey KEY_VIEWS_UPDATE = + AppWidgetHostView::updateAppWidget; + private static final UpdateKey KEY_VIEW_DATA_CHANGED = + AppWidgetHostView::onViewDataChanged; + private static final List sHolders = new ArrayList<>(); private static final SparseArray sListeners = new SparseArray<>(); @@ -59,6 +68,8 @@ public final class QuickstepWidgetHolder extends LauncherWidgetHolder { private final @NonNull IntConsumer mAppWidgetRemovedCallback; private final ArrayList mProviderChangedListeners = new ArrayList<>(); + // Map to all pending updated keyed with appWidgetId; + private final SparseArray mPendingUpdateMap = new SparseArray<>(); @Thunk QuickstepWidgetHolder(@NonNull Context context, @@ -90,6 +101,57 @@ public final class QuickstepWidgetHolder extends LauncherWidgetHolder { return sWidgetHost; } + @Override + protected void updateDeferredView() { + super.updateDeferredView(); + int count = mPendingUpdateMap.size(); + for (int i = 0; i < count; i++) { + int widgetId = mPendingUpdateMap.keyAt(i); + QuickstepWidgetHolderListener listener = sListeners.get(widgetId); + if (listener == null) { + continue; + } + AppWidgetHostView view = listener.mView.get(this); + if (view == null) { + continue; + } + PendingUpdate pendingUpdate = mPendingUpdateMap.valueAt(i); + if (pendingUpdate == null) { + continue; + } + if (pendingUpdate.providerInfo != null) { + KEY_PROVIDER_UPDATE.accept(view, pendingUpdate.providerInfo); + } + if (pendingUpdate.remoteViews != null) { + KEY_VIEWS_UPDATE.accept(view, pendingUpdate.remoteViews); + } + pendingUpdate.changedViews.forEach( + viewId -> KEY_VIEW_DATA_CHANGED.accept(view, viewId)); + } + mPendingUpdateMap.clear(); + } + + private void addPendingAction(int widgetId, UpdateKey key, T data) { + PendingUpdate pendingUpdate = mPendingUpdateMap.get(widgetId); + if (pendingUpdate == null) { + pendingUpdate = new PendingUpdate(); + mPendingUpdateMap.put(widgetId, pendingUpdate); + } + + if (KEY_PROVIDER_UPDATE.equals(key)) { + // For provider change, remove all updates + pendingUpdate.providerInfo = (AppWidgetProviderInfo) data; + pendingUpdate.remoteViews = null; + pendingUpdate.changedViews.clear(); + } else if (KEY_VIEWS_UPDATE.equals(key)) { + // For views update, remove all previous updates, except the provider + pendingUpdate.remoteViews = (RemoteViews) data; + pendingUpdate.changedViews.clear(); + } else if (KEY_VIEW_DATA_CHANGED.equals(key)) { + pendingUpdate.changedViews.add((Integer) data); + } + } + /** * Delete the specified app widget from the host * @param appWidgetId The ID of the app widget to be deleted @@ -108,6 +170,12 @@ public final class QuickstepWidgetHolder extends LauncherWidgetHolder { sHolders.remove(this); } + @Override + protected boolean shouldListen(int flags) { + return (flags & (FLAG_STATE_IS_NORMAL | FLAG_ACTIVITY_STARTED)) + == (FLAG_STATE_IS_NORMAL | FLAG_ACTIVITY_STARTED); + } + /** * Add a listener that is triggered when the providers of the widgets are changed * @param listener The listener that notifies when the providers changed @@ -163,7 +231,7 @@ public final class QuickstepWidgetHolder extends LauncherWidgetHolder { QuickstepWidgetHolderListener listener = sListeners.get(appWidgetId); if (listener == null) { - listener = new QuickstepWidgetHolderListener(this, widgetView); + listener = new QuickstepWidgetHolderListener(appWidgetId, this, widgetView); sWidgetHost.setListener(appWidgetId, listener); sListeners.put(appWidgetId, listener); } else { @@ -185,14 +253,17 @@ public final class QuickstepWidgetHolder extends LauncherWidgetHolder { private static class QuickstepWidgetHolderListener implements AppWidgetHost.AppWidgetHostListener { + @NonNull private final Map mView = new WeakHashMap<>(); - @Nullable - private RemoteViews mRemoteViews = null; + private final int mWidgetId; - QuickstepWidgetHolderListener(@NonNull QuickstepWidgetHolder holder, + @Nullable private RemoteViews mRemoteViews = null; + + QuickstepWidgetHolderListener(int widgetId, @NonNull QuickstepWidgetHolder holder, @NonNull LauncherAppWidgetHostView view) { + mWidgetId = widgetId; mView.put(holder, view); } @@ -207,24 +278,30 @@ public final class QuickstepWidgetHolder extends LauncherWidgetHolder { @WorkerThread public void onUpdateProviderInfo(@Nullable AppWidgetProviderInfo info) { mRemoteViews = null; - executeOnMainExecutor(v -> v.onUpdateProviderInfo(info)); + executeOnMainExecutor(KEY_PROVIDER_UPDATE, info); } @Override @WorkerThread public void updateAppWidget(@Nullable RemoteViews views) { mRemoteViews = views; - executeOnMainExecutor(v -> v.updateAppWidget(mRemoteViews)); + executeOnMainExecutor(KEY_VIEWS_UPDATE, mRemoteViews); } @Override @WorkerThread public void onViewDataChanged(int viewId) { - executeOnMainExecutor(v -> v.onViewDataChanged(viewId)); + executeOnMainExecutor(KEY_VIEW_DATA_CHANGED, viewId); } - private void executeOnMainExecutor(Consumer consumer) { - MAIN_EXECUTOR.execute(() -> mView.values().forEach(consumer)); + private void executeOnMainExecutor(UpdateKey key, T data) { + MAIN_EXECUTOR.execute(() -> mView.forEach((holder, view) -> { + if (holder.isListening()) { + key.accept(view, data); + } else { + holder.addPendingAction(mWidgetId, key, data); + } + })); } } @@ -267,4 +344,12 @@ public final class QuickstepWidgetHolder extends LauncherWidgetHolder { return new QuickstepWidgetHolder(context, appWidgetRemovedCallback, interactionHandler); } } + + private static class PendingUpdate { + public final IntSet changedViews = new IntSet(); + public AppWidgetProviderInfo providerInfo; + public RemoteViews remoteViews; + } + + private interface UpdateKey extends BiConsumer { } } diff --git a/src/com/android/launcher3/widget/LauncherWidgetHolder.java b/src/com/android/launcher3/widget/LauncherWidgetHolder.java index d7235ade3c..bea7517c91 100644 --- a/src/com/android/launcher3/widget/LauncherWidgetHolder.java +++ b/src/com/android/launcher3/widget/LauncherWidgetHolder.java @@ -55,10 +55,10 @@ import java.util.function.IntConsumer; public class LauncherWidgetHolder { public static final int APPWIDGET_HOST_ID = 1024; - private static final int FLAG_LISTENING = 1; - private static final int FLAG_STATE_IS_NORMAL = 1 << 1; - private static final int FLAG_ACTIVITY_STARTED = 1 << 2; - private static final int FLAG_ACTIVITY_RESUMED = 1 << 3; + protected static final int FLAG_LISTENING = 1; + protected static final int FLAG_STATE_IS_NORMAL = 1 << 1; + protected static final int FLAG_ACTIVITY_STARTED = 1 << 2; + protected static final int FLAG_ACTIVITY_RESUMED = 1 << 3; private static final int FLAGS_SHOULD_LISTEN = FLAG_STATE_IS_NORMAL | FLAG_ACTIVITY_STARTED | FLAG_ACTIVITY_RESUMED; @@ -77,7 +77,7 @@ public class LauncherWidgetHolder { @NonNull private final SparseArray mCachedRemoteViews = new SparseArray<>(); - private int mFlags = FLAG_STATE_IS_NORMAL; + protected int mFlags = FLAG_STATE_IS_NORMAL; // TODO(b/191735836): Replace with ActivityOptions.KEY_SPLASH_SCREEN_STYLE when un-hidden private static final String KEY_SPLASH_SCREEN_STYLE = "android.activity.splashScreenStyle"; @@ -115,6 +115,13 @@ public class LauncherWidgetHolder { // widgets upon bind anyway. See issue 14255011 for more context. } + updateDeferredView(); + } + + /** + * Update any views which have been deferred because the host was not listening. + */ + protected void updateDeferredView() { // 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); @@ -464,7 +471,7 @@ public class LauncherWidgetHolder { } final boolean listening = isListening(); - if (!listening && (mFlags & FLAGS_SHOULD_LISTEN) == FLAGS_SHOULD_LISTEN) { + if (!listening && shouldListen(mFlags)) { // Postpone starting listening until all flags are on. startListening(); } else if (listening && (mFlags & FLAG_ACTIVITY_STARTED) == 0) { @@ -473,6 +480,14 @@ public class LauncherWidgetHolder { } } + /** + * Returns true if the holder should be listening for widget updates based + * on the provided state flags. + */ + protected boolean shouldListen(int flags) { + return (flags & FLAGS_SHOULD_LISTEN) == FLAGS_SHOULD_LISTEN; + } + /** * Returns the new LauncherWidgetHolder instance */