Merge "Deferred widget update" into tm-qpr-dev
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) {