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
diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepWidgetHolder.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepWidgetHolder.java
index a8edd51..278a45a 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepWidgetHolder.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepWidgetHolder.java
@@ -32,6 +32,7 @@
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.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<AppWidgetProviderInfo> KEY_PROVIDER_UPDATE =
+ AppWidgetHostView::onUpdateProviderInfo;
+ private static final UpdateKey<RemoteViews> KEY_VIEWS_UPDATE =
+ AppWidgetHostView::updateAppWidget;
+ private static final UpdateKey<Integer> KEY_VIEW_DATA_CHANGED =
+ AppWidgetHostView::onViewDataChanged;
+
private static final List<QuickstepWidgetHolder> sHolders = new ArrayList<>();
private static final SparseArray<QuickstepWidgetHolderListener> sListeners =
new SparseArray<>();
@@ -59,6 +68,8 @@
private final @NonNull IntConsumer mAppWidgetRemovedCallback;
private final ArrayList<ProviderChangedListener> mProviderChangedListeners = new ArrayList<>();
+ // Map to all pending updated keyed with appWidgetId;
+ private final SparseArray<PendingUpdate> mPendingUpdateMap = new SparseArray<>();
@Thunk
QuickstepWidgetHolder(@NonNull Context context,
@@ -90,6 +101,57 @@
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 <T> void addPendingAction(int widgetId, UpdateKey<T> 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 @@
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 @@
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 @@
private static class QuickstepWidgetHolderListener
implements AppWidgetHost.AppWidgetHostListener {
+
@NonNull
private final Map<QuickstepWidgetHolder, AppWidgetHostView> 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 @@
@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<AppWidgetHostView> consumer) {
- MAIN_EXECUTOR.execute(() -> mView.values().forEach(consumer));
+ private <T> void executeOnMainExecutor(UpdateKey<T> 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 @@
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<T> extends BiConsumer<AppWidgetHostView, T> { }
}
diff --git a/src/com/android/launcher3/widget/LauncherWidgetHolder.java b/src/com/android/launcher3/widget/LauncherWidgetHolder.java
index d7235ad..bea7517 100644
--- a/src/com/android/launcher3/widget/LauncherWidgetHolder.java
+++ b/src/com/android/launcher3/widget/LauncherWidgetHolder.java
@@ -55,10 +55,10 @@
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 @@
@NonNull
private final SparseArray<RemoteViews> 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 @@
// 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 @@
}
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) {
@@ -474,6 +481,14 @@
}
/**
+ * 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
*/
public static LauncherWidgetHolder newInstance(Context context) {