Marshal access to DreamOverlayStateController.

This changelist marshals calls on DreamoverlayStateController
on a single executor to ensure consistency and ordered
execution.

Bug: 211497162
Test: atest DreamOverlayStateController
Change-Id: If6870b9f1212bae845f39500b4d08925531f8de2
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java
index bc9bdfc..66679bb 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java
@@ -17,15 +17,20 @@
 package com.android.systemui.dreams;
 
 import androidx.annotation.NonNull;
+import androidx.concurrent.futures.CallbackToFutureAdapter;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.statusbar.policy.CallbackController;
 
+import com.google.common.util.concurrent.ListenableFuture;
+
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.HashMap;
 import java.util.Objects;
+import java.util.concurrent.Executor;
 
 import javax.inject.Inject;
 
@@ -77,12 +82,14 @@
         }
     }
 
+    private final Executor mExecutor;
     private final ArrayList<Callback> mCallbacks = new ArrayList<>();
     private final HashMap<ComplicationToken, ComplicationProvider> mComplications = new HashMap<>();
 
     @VisibleForTesting
     @Inject
-    public DreamOverlayStateController() {
+    public DreamOverlayStateController(@Main Executor executor) {
+        mExecutor = executor;
     }
 
     /**
@@ -90,11 +97,16 @@
      * @param provider The {@link ComplicationProvider} providing the dream.
      * @return The {@link ComplicationToken} tied to the supplied {@link ComplicationProvider}.
      */
-    public ComplicationToken addComplication(ComplicationProvider provider) {
-        final ComplicationToken token = new ComplicationToken(mNextComplicationTokenId++);
-        mComplications.put(token, provider);
-        notifyCallbacks();
-        return token;
+    public ListenableFuture<ComplicationToken> addComplication(ComplicationProvider provider) {
+        return CallbackToFutureAdapter.getFuture(completer -> {
+            mExecutor.execute(() -> {
+                final ComplicationToken token = new ComplicationToken(mNextComplicationTokenId++);
+                mComplications.put(token, provider);
+                notifyCallbacks();
+                completer.set(token);
+            });
+            return "DreamOverlayStateController::addComplication";
+        });
     }
 
     /**
@@ -103,14 +115,19 @@
      *              to be removed.
      * @return The removed {@link ComplicationProvider}, {@code null} if not found.
      */
-    public ComplicationProvider removeComplication(ComplicationToken token) {
-        final ComplicationProvider removedComplication = mComplications.remove(token);
+    public ListenableFuture<ComplicationProvider> removeComplication(ComplicationToken token) {
+        return CallbackToFutureAdapter.getFuture(completer -> {
+            mExecutor.execute(() -> {
+                final ComplicationProvider removedComplication = mComplications.remove(token);
 
-        if (removedComplication != null) {
-            notifyCallbacks();
-        }
+                if (removedComplication != null) {
+                    notifyCallbacks();
+                }
+                completer.set(removedComplication);
+            });
 
-        return removedComplication;
+            return "DreamOverlayStateController::removeComplication";
+        });
     }
 
     private void notifyCallbacks() {
@@ -121,24 +138,28 @@
 
     @Override
     public void addCallback(@NonNull Callback callback) {
-        Objects.requireNonNull(callback, "Callback must not be null. b/128895449");
-        if (mCallbacks.contains(callback)) {
-            return;
-        }
+        mExecutor.execute(() -> {
+            Objects.requireNonNull(callback, "Callback must not be null. b/128895449");
+            if (mCallbacks.contains(callback)) {
+                return;
+            }
 
-        mCallbacks.add(callback);
+            mCallbacks.add(callback);
 
-        if (mComplications.isEmpty()) {
-            return;
-        }
+            if (mComplications.isEmpty()) {
+                return;
+            }
 
-        callback.onComplicationsChanged();
+            callback.onComplicationsChanged();
+        });
     }
 
     @Override
     public void removeCallback(@NonNull Callback callback) {
-        Objects.requireNonNull(callback, "Callback must not be null. b/128895449");
-        mCallbacks.remove(callback);
+        mExecutor.execute(() -> {
+            Objects.requireNonNull(callback, "Callback must not be null. b/128895449");
+            mCallbacks.remove(callback);
+        });
     }
 
     /**
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java
index 1f4333e..efc3c7c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java
@@ -27,6 +27,10 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.util.concurrency.FakeExecutor;
+import com.android.systemui.util.time.FakeSystemClock;
+
+import com.google.common.util.concurrent.ListenableFuture;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -45,20 +49,25 @@
     @Mock
     ComplicationProvider mProvider;
 
+    final FakeExecutor mExecutor = new FakeExecutor(new FakeSystemClock());
+
     @Before
     public void setup() {
         MockitoAnnotations.initMocks(this);
     }
 
     @Test
-    public void testCallback() {
-        final DreamOverlayStateController stateController = new DreamOverlayStateController();
+    public void testCallback() throws Exception {
+        final DreamOverlayStateController stateController = new DreamOverlayStateController(
+                mExecutor);
         stateController.addCallback(mCallback);
 
         // Add complication and verify callback is notified.
-        final DreamOverlayStateController.ComplicationToken token =
+        final ListenableFuture<DreamOverlayStateController.ComplicationToken> tokenFuture =
                 stateController.addComplication(mProvider);
 
+        mExecutor.runAllReady();
+
         verify(mCallback, times(1)).onComplicationsChanged();
 
         final Collection<ComplicationProvider> providers = stateController.getComplications();
@@ -68,19 +77,23 @@
         clearInvocations(mCallback);
 
         // Remove complication and verify callback is notified.
-        stateController.removeComplication(token);
+        stateController.removeComplication(tokenFuture.get());
+        mExecutor.runAllReady();
         verify(mCallback, times(1)).onComplicationsChanged();
         assertTrue(providers.isEmpty());
     }
 
     @Test
     public void testNotifyOnCallbackAdd() {
-        final DreamOverlayStateController stateController = new DreamOverlayStateController();
-        final DreamOverlayStateController.ComplicationToken token =
-                stateController.addComplication(mProvider);
+        final DreamOverlayStateController stateController =
+                new DreamOverlayStateController(mExecutor);
+
+        stateController.addComplication(mProvider);
+        mExecutor.runAllReady();
 
         // Verify callback occurs on add when an overlay is already present.
         stateController.addCallback(mCallback);
+        mExecutor.runAllReady();
         verify(mCallback, times(1)).onComplicationsChanged();
     }
 }