Merge "Implement ComplicationTypesUpdater."
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
index 4696eed..4dacf65 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
@@ -31,10 +31,8 @@
 import com.android.internal.policy.PhoneWindow;
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.keyguard.KeyguardUpdateMonitorCallback;
-import com.android.settingslib.dream.DreamBackend;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dreams.complication.Complication;
-import com.android.systemui.dreams.complication.ComplicationUtils;
 import com.android.systemui.dreams.dagger.DreamOverlayComponent;
 import com.android.systemui.dreams.touch.DreamOverlayTouchMonitor;
 
@@ -59,7 +57,6 @@
     // content area).
     private final DreamOverlayContainerViewController mDreamOverlayContainerViewController;
     private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
-    private final DreamBackend mDreamBackend;
 
     // A reference to the {@link Window} used to hold the dream overlay.
     private Window mWindow;
@@ -112,7 +109,6 @@
         setCurrentState(Lifecycle.State.CREATED);
         mLifecycleRegistry = component.getLifecycleRegistry();
         mDreamOverlayTouchMonitor = component.getDreamOverlayTouchMonitor();
-        mDreamBackend = component.getDreamBackend();
         mDreamOverlayTouchMonitor.init();
     }
 
@@ -136,9 +132,6 @@
     public void onStartDream(@NonNull WindowManager.LayoutParams layoutParams) {
         setCurrentState(Lifecycle.State.STARTED);
         mExecutor.execute(() -> {
-            mStateController.setAvailableComplicationTypes(
-                    ComplicationUtils.convertComplicationTypes(
-                            mDreamBackend.getEnabledComplications()));
             addOverlayWindowLocked(layoutParams);
             setCurrentState(Lifecycle.State.RESUMED);
             mStateController.setOverlayActive(true);
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationTypesUpdater.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationTypesUpdater.java
new file mode 100644
index 0000000..83249aa
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationTypesUpdater.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.dreams.complication;
+
+import android.content.Context;
+import android.database.ContentObserver;
+import android.os.UserHandle;
+import android.provider.Settings;
+
+import com.android.settingslib.dream.DreamBackend;
+import com.android.systemui.CoreStartable;
+import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.dreams.DreamOverlayStateController;
+import com.android.systemui.util.settings.SecureSettings;
+
+import java.util.concurrent.Executor;
+
+import javax.inject.Inject;
+
+/**
+ * {@link ComplicationTypesUpdater} observes the state of available complication types set by the
+ * user, and pushes updates to {@link DreamOverlayStateController}.
+ */
+@SysUISingleton
+public class ComplicationTypesUpdater extends CoreStartable {
+    private final DreamBackend mDreamBackend;
+    private final Executor mExecutor;
+    private final SecureSettings mSecureSettings;
+
+    private final DreamOverlayStateController mDreamOverlayStateController;
+
+    @Inject
+    ComplicationTypesUpdater(Context context,
+            DreamBackend dreamBackend,
+            @Main Executor executor,
+            SecureSettings secureSettings,
+            DreamOverlayStateController dreamOverlayStateController) {
+        super(context);
+
+        mDreamBackend = dreamBackend;
+        mExecutor = executor;
+        mSecureSettings = secureSettings;
+        mDreamOverlayStateController = dreamOverlayStateController;
+    }
+
+    @Override
+    public void start() {
+        final ContentObserver settingsObserver = new ContentObserver(null /*handler*/) {
+            @Override
+            public void onChange(boolean selfChange) {
+                mExecutor.execute(() -> mDreamOverlayStateController.setAvailableComplicationTypes(
+                        getAvailableComplicationTypes()));
+            }
+        };
+
+        mSecureSettings.registerContentObserverForUser(
+                Settings.Secure.SCREENSAVER_ENABLED_COMPLICATIONS,
+                settingsObserver,
+                UserHandle.myUserId());
+        settingsObserver.onChange(false);
+    }
+
+    /**
+     * Returns complication types that are currently available by user setting.
+     */
+    @Complication.ComplicationType
+    private int getAvailableComplicationTypes() {
+        return ComplicationUtils.convertComplicationTypes(mDreamBackend.getEnabledComplications());
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java
index d8af9e5..c61f796 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java
@@ -16,10 +16,14 @@
 
 package com.android.systemui.dreams.dagger;
 
+import android.content.Context;
+
+import com.android.settingslib.dream.DreamBackend;
 import com.android.systemui.dreams.complication.dagger.RegisteredComplicationsModule;
 import com.android.systemui.dreams.touch.dagger.DreamTouchModule;
 
 import dagger.Module;
+import dagger.Provides;
 
 /**
  * Dagger Module providing Communal-related functionality.
@@ -32,4 +36,11 @@
             DreamOverlayComponent.class,
         })
 public interface DreamModule {
+    /**
+     * Provides an instance of the dream backend.
+     */
+    @Provides
+    static DreamBackend providesDreamBackend(Context context) {
+        return DreamBackend.getInstance(context);
+    }
 }
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayComponent.java b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayComponent.java
index 60278a9..05ab901 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayComponent.java
@@ -22,7 +22,6 @@
 import androidx.lifecycle.LifecycleRegistry;
 import androidx.lifecycle.ViewModelStore;
 
-import com.android.settingslib.dream.DreamBackend;
 import com.android.systemui.dreams.DreamOverlayContainerViewController;
 import com.android.systemui.dreams.complication.Complication;
 import com.android.systemui.dreams.complication.dagger.ComplicationModule;
@@ -69,7 +68,4 @@
 
     /** Builds a {@link DreamOverlayTouchMonitor} */
     DreamOverlayTouchMonitor getDreamOverlayTouchMonitor();
-
-    /** Builds a ${@link DreamBackend} */
-    DreamBackend getDreamBackend();
 }
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java
index efa063f..4eb5cb9 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java
@@ -17,7 +17,6 @@
 package com.android.systemui.dreams.dagger;
 
 import android.content.ContentResolver;
-import android.content.Context;
 import android.content.res.Resources;
 import android.os.Handler;
 import android.view.LayoutInflater;
@@ -28,7 +27,6 @@
 import androidx.lifecycle.LifecycleRegistry;
 
 import com.android.internal.util.Preconditions;
-import com.android.settingslib.dream.DreamBackend;
 import com.android.systemui.R;
 import com.android.systemui.battery.BatteryMeterView;
 import com.android.systemui.battery.BatteryMeterViewController;
@@ -149,10 +147,4 @@
     static Lifecycle providesLifecycle(LifecycleOwner lifecycleOwner) {
         return lifecycleOwner.getLifecycle();
     }
-
-    @Provides
-    @DreamOverlayComponent.DreamOverlayScope
-    static DreamBackend providesDreamBackend(Context context) {
-        return DreamBackend.getInstance(context);
-    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java
index 529a163..58ffbfa 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java
@@ -37,9 +37,7 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.keyguard.KeyguardUpdateMonitor;
-import com.android.settingslib.dream.DreamBackend;
 import com.android.systemui.SysuiTestCase;
-import com.android.systemui.dreams.complication.Complication;
 import com.android.systemui.dreams.dagger.DreamOverlayComponent;
 import com.android.systemui.dreams.touch.DreamOverlayTouchMonitor;
 import com.android.systemui.util.concurrency.FakeExecutor;
@@ -53,10 +51,6 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
-import java.util.Arrays;
-import java.util.HashSet;
-import java.util.Set;
-
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
 public class DreamOverlayServiceTest extends SysuiTestCase {
@@ -101,9 +95,6 @@
     @Mock
     DreamOverlayStateController mStateController;
 
-    @Mock
-    DreamBackend mDreamBackend;
-
     DreamOverlayService mService;
 
     @Before
@@ -119,8 +110,6 @@
                 .thenReturn(mLifecycleRegistry);
         when(mDreamOverlayComponent.getDreamOverlayTouchMonitor())
                 .thenReturn(mDreamOverlayTouchMonitor);
-        when(mDreamOverlayComponent.getDreamBackend())
-                .thenReturn(mDreamBackend);
         when(mDreamOverlayComponentFactory
                 .create(any(), any()))
                 .thenReturn(mDreamOverlayComponent);
@@ -174,26 +163,6 @@
     }
 
     @Test
-    public void testSetAvailableComplicationTypes() throws Exception {
-        final Set<Integer> enabledComplications = new HashSet<>(
-                Arrays.asList(DreamBackend.COMPLICATION_TYPE_TIME,
-                        DreamBackend.COMPLICATION_TYPE_DATE,
-                        DreamBackend.COMPLICATION_TYPE_WEATHER));
-        when(mDreamBackend.getEnabledComplications()).thenReturn(enabledComplications);
-
-        final IBinder proxy = mService.onBind(new Intent());
-        final IDreamOverlay overlay = IDreamOverlay.Stub.asInterface(proxy);
-
-        overlay.startDream(mWindowParams, mDreamOverlayCallback);
-        mMainExecutor.runAllReady();
-
-        final int expectedTypes =
-                Complication.COMPLICATION_TYPE_TIME | Complication.COMPLICATION_TYPE_DATE
-                        | Complication.COMPLICATION_TYPE_WEATHER;
-        verify(mStateController).setAvailableComplicationTypes(expectedTypes);
-    }
-
-    @Test
     public void testDestroy() {
         mService.onDestroy();
         mMainExecutor.runAllReady();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationTypesUpdaterTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationTypesUpdaterTest.java
new file mode 100644
index 0000000..09976e0
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationTypesUpdaterTest.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.dreams.complication;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.database.ContentObserver;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.testing.AndroidTestingRunner;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.settingslib.dream.DreamBackend;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.dreams.DreamOverlayStateController;
+import com.android.systemui.util.concurrency.FakeExecutor;
+import com.android.systemui.util.settings.SecureSettings;
+import com.android.systemui.util.time.FakeSystemClock;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.Arrays;
+import java.util.HashSet;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class ComplicationTypesUpdaterTest extends SysuiTestCase {
+    @Mock
+    private Context mContext;
+    @Mock
+    private DreamBackend mDreamBackend;
+    @Mock
+    private SecureSettings mSecureSettings;
+    @Mock
+    private DreamOverlayStateController mDreamOverlayStateController;
+    @Captor
+    private ArgumentCaptor<ContentObserver> mSettingsObserverCaptor;
+
+    private final FakeExecutor mExecutor = new FakeExecutor(new FakeSystemClock());
+
+    private ComplicationTypesUpdater mController;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        when(mDreamBackend.getEnabledComplications()).thenReturn(new HashSet<>());
+
+        mController = new ComplicationTypesUpdater(mContext, mDreamBackend, mExecutor,
+                mSecureSettings, mDreamOverlayStateController);
+    }
+
+    @Test
+    public void testPushUpdateToDreamOverlayStateControllerImmediatelyOnStart() {
+        // DreamOverlayStateController shouldn't be updated before start().
+        verify(mDreamOverlayStateController, never()).setAvailableComplicationTypes(anyInt());
+
+        mController.start();
+        mExecutor.runAllReady();
+
+        // DreamOverlayStateController updated immediately on start().
+        verify(mDreamOverlayStateController).setAvailableComplicationTypes(anyInt());
+    }
+
+    @Test
+    public void testPushUpdateToDreamOverlayStateControllerOnChange() {
+        mController.start();
+        mExecutor.runAllReady();
+
+        when(mDreamBackend.getEnabledComplications()).thenReturn(new HashSet<>(Arrays.asList(
+                DreamBackend.COMPLICATION_TYPE_TIME, DreamBackend.COMPLICATION_TYPE_WEATHER,
+                DreamBackend.COMPLICATION_TYPE_AIR_QUALITY)));
+        final ContentObserver settingsObserver = captureSettingsObserver();
+        settingsObserver.onChange(false);
+        mExecutor.runAllReady();
+
+        verify(mDreamOverlayStateController).setAvailableComplicationTypes(
+                Complication.COMPLICATION_TYPE_TIME | Complication.COMPLICATION_TYPE_WEATHER
+                        | Complication.COMPLICATION_TYPE_AIR_QUALITY);
+    }
+
+    private ContentObserver captureSettingsObserver() {
+        verify(mSecureSettings).registerContentObserverForUser(
+                eq(Settings.Secure.SCREENSAVER_ENABLED_COMPLICATIONS),
+                mSettingsObserverCaptor.capture(), eq(UserHandle.myUserId()));
+        return mSettingsObserverCaptor.getValue();
+    }
+}