Do not allow complications when dream overlay is not enabled.

Complications are currently only hosted by the dream overlay. Dependent
logic that listens on the DreamOverlayStateController for changes around
complications should not be triggered when the dream overlay is not
enabled. This changelist addresses this by preventing the
DreamOverlayStateController from accepting complications when the
overlay is not enabled (component wise).

This change also null checks a complication view retrieved from the
view holder before adding it to the layout.

Test: atest DreamOverlayStateControllerTest
Test: atest ComplicationHostViewControllerTest
Fixes: 259859096
Change-Id: Ifa1b4a9da0070a949e422003d3fd4e9ca484a16e
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayRegistrant.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayRegistrant.java
index d145f5c..87c5f51 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayRegistrant.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayRegistrant.java
@@ -16,6 +16,8 @@
 
 package com.android.systemui.dreams;
 
+import static com.android.systemui.dreams.dagger.DreamModule.DREAM_OVERLAY_SERVICE_COMPONENT;
+
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.Context;
@@ -35,6 +37,7 @@
 import com.android.systemui.dagger.qualifiers.Main;
 
 import javax.inject.Inject;
+import javax.inject.Named;
 
 /**
  * {@link DreamOverlayRegistrant} is responsible for telling system server that SystemUI should be
@@ -98,12 +101,13 @@
     }
 
     @Inject
-    public DreamOverlayRegistrant(Context context, @Main Resources resources) {
+    public DreamOverlayRegistrant(Context context, @Main Resources resources,
+            @Named(DREAM_OVERLAY_SERVICE_COMPONENT) ComponentName dreamOverlayServiceComponent) {
         mContext = context;
         mResources = resources;
         mDreamManager = IDreamManager.Stub.asInterface(
                 ServiceManager.getService(DreamService.DREAM_SERVICE));
-        mOverlayServiceComponent = new ComponentName(mContext, DreamOverlayService.class);
+        mOverlayServiceComponent = dreamOverlayServiceComponent;
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java
index 5f942b6..ccfdd096 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java
@@ -16,6 +16,8 @@
 
 package com.android.systemui.dreams;
 
+import static com.android.systemui.dreams.dagger.DreamModule.DREAM_OVERLAY_ENABLED;
+
 import android.service.dreams.DreamService;
 import android.util.Log;
 
@@ -37,6 +39,7 @@
 import java.util.stream.Collectors;
 
 import javax.inject.Inject;
+import javax.inject.Named;
 
 /**
  * {@link DreamOverlayStateController} is the source of truth for Dream overlay configurations and
@@ -83,6 +86,7 @@
     }
 
     private final Executor mExecutor;
+    private final boolean mOverlayEnabled;
     private final ArrayList<Callback> mCallbacks = new ArrayList<>();
 
     @Complication.ComplicationType
@@ -94,14 +98,27 @@
 
     @VisibleForTesting
     @Inject
-    public DreamOverlayStateController(@Main Executor executor) {
+    public DreamOverlayStateController(@Main Executor executor,
+            @Named(DREAM_OVERLAY_ENABLED) boolean overlayEnabled) {
         mExecutor = executor;
+        mOverlayEnabled = overlayEnabled;
+        if (DEBUG) {
+            Log.d(TAG, "Dream overlay enabled:" + mOverlayEnabled);
+        }
     }
 
     /**
      * Adds a complication to be included on the dream overlay.
      */
     public void addComplication(Complication complication) {
+        if (!mOverlayEnabled) {
+            if (DEBUG) {
+                Log.d(TAG,
+                        "Ignoring adding complication due to overlay disabled:" + complication);
+            }
+            return;
+        }
+
         mExecutor.execute(() -> {
             if (mComplications.add(complication)) {
                 if (DEBUG) {
@@ -116,6 +133,14 @@
      * Removes a complication from inclusion on the dream overlay.
      */
     public void removeComplication(Complication complication) {
+        if (!mOverlayEnabled) {
+            if (DEBUG) {
+                Log.d(TAG,
+                        "Ignoring removing complication due to overlay disabled:" + complication);
+            }
+            return;
+        }
+
         mExecutor.execute(() -> {
             if (mComplications.remove(complication)) {
                 if (DEBUG) {
@@ -193,7 +218,7 @@
      * @return {@code true} if overlay is active, {@code false} otherwise.
      */
     public boolean isOverlayActive() {
-        return containsState(STATE_DREAM_OVERLAY_ACTIVE);
+        return mOverlayEnabled && containsState(STATE_DREAM_OVERLAY_ACTIVE);
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationHostViewController.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationHostViewController.java
index 100ccc3..a2e11b2 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationHostViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationHostViewController.java
@@ -138,19 +138,27 @@
                     final ComplicationId id = complication.getId();
                     final Complication.ViewHolder viewHolder = complication.getComplication()
                             .createView(complication);
+
+                    final View view = viewHolder.getView();
+
+                    if (view == null) {
+                        Log.e(TAG, "invalid complication view. null view supplied by ViewHolder");
+                        return;
+                    }
+
                     // Complications to be added before dream entry animations are finished are set
                     // to invisible and are animated in.
                     if (!mEntryAnimationsFinished) {
-                        viewHolder.getView().setVisibility(View.INVISIBLE);
+                        view.setVisibility(View.INVISIBLE);
                     }
                     mComplications.put(id, viewHolder);
-                    if (viewHolder.getView().getParent() != null) {
+                    if (view.getParent() != null) {
                         Log.e(TAG, "View for complication "
                                 + complication.getComplication().getClass()
                                 + " already has a parent. Make sure not to reuse complication "
                                 + "views!");
                     }
-                    mLayoutEngine.addComplication(id, viewHolder.getView(),
+                    mLayoutEngine.addComplication(id, view,
                             viewHolder.getLayoutParams(), viewHolder.getCategory());
                 });
     }
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 101f4a4..e7b29bb 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java
@@ -16,7 +16,9 @@
 
 package com.android.systemui.dreams.dagger;
 
+import android.content.ComponentName;
 import android.content.Context;
+import android.content.pm.PackageManager;
 import android.content.res.Resources;
 
 import com.android.dream.lowlight.dagger.LowLightDreamModule;
@@ -24,6 +26,7 @@
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dreams.DreamOverlayNotificationCountProvider;
+import com.android.systemui.dreams.DreamOverlayService;
 import com.android.systemui.dreams.complication.dagger.RegisteredComplicationsModule;
 
 import java.util.Optional;
@@ -45,10 +48,35 @@
         })
 public interface DreamModule {
     String DREAM_ONLY_ENABLED_FOR_DOCK_USER = "dream_only_enabled_for_dock_user";
+    String DREAM_OVERLAY_SERVICE_COMPONENT = "dream_overlay_service_component";
+    String DREAM_OVERLAY_ENABLED = "dream_overlay_enabled";
 
     String DREAM_SUPPORTED = "dream_supported";
 
     /**
+     * Provides the dream component
+     */
+    @Provides
+    @Named(DREAM_OVERLAY_SERVICE_COMPONENT)
+    static ComponentName providesDreamOverlayService(Context context) {
+        return new ComponentName(context, DreamOverlayService.class);
+    }
+
+    /**
+     * Provides whether dream overlay is enabled.
+     */
+    @Provides
+    @Named(DREAM_OVERLAY_ENABLED)
+    static Boolean providesDreamOverlayEnabled(PackageManager packageManager,
+            @Named(DREAM_OVERLAY_SERVICE_COMPONENT) ComponentName component) {
+        try {
+            return packageManager.getServiceInfo(component, PackageManager.GET_META_DATA).enabled;
+        } catch (PackageManager.NameNotFoundException e) {
+            return false;
+        }
+    }
+
+    /**
      * Provides an instance of the dream backend.
      */
     @Provides
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 c21c7a2..ee989d1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java
@@ -63,7 +63,7 @@
     @Test
     public void testStateChange_overlayActive() {
         final DreamOverlayStateController stateController = new DreamOverlayStateController(
-                mExecutor);
+                mExecutor, true);
         stateController.addCallback(mCallback);
         stateController.setOverlayActive(true);
         mExecutor.runAllReady();
@@ -85,7 +85,7 @@
     @Test
     public void testCallback() {
         final DreamOverlayStateController stateController = new DreamOverlayStateController(
-                mExecutor);
+                mExecutor, true);
         stateController.addCallback(mCallback);
 
         // Add complication and verify callback is notified.
@@ -111,7 +111,7 @@
     @Test
     public void testNotifyOnCallbackAdd() {
         final DreamOverlayStateController stateController =
-                new DreamOverlayStateController(mExecutor);
+                new DreamOverlayStateController(mExecutor, true);
 
         stateController.addComplication(mComplication);
         mExecutor.runAllReady();
@@ -123,9 +123,24 @@
     }
 
     @Test
+    public void testNotifyOnCallbackAddOverlayDisabled() {
+        final DreamOverlayStateController stateController =
+                new DreamOverlayStateController(mExecutor, false);
+
+        stateController.addComplication(mComplication);
+        mExecutor.runAllReady();
+
+        // Verify callback occurs on add when an overlay is already present.
+        stateController.addCallback(mCallback);
+        mExecutor.runAllReady();
+        verify(mCallback, never()).onComplicationsChanged();
+    }
+
+
+    @Test
     public void testComplicationFilteringWhenShouldShowComplications() {
         final DreamOverlayStateController stateController =
-                new DreamOverlayStateController(mExecutor);
+                new DreamOverlayStateController(mExecutor, true);
         stateController.setShouldShowComplications(true);
 
         final Complication alwaysAvailableComplication = Mockito.mock(Complication.class);
@@ -165,7 +180,7 @@
     @Test
     public void testComplicationFilteringWhenShouldHideComplications() {
         final DreamOverlayStateController stateController =
-                new DreamOverlayStateController(mExecutor);
+                new DreamOverlayStateController(mExecutor, true);
         stateController.setShouldShowComplications(true);
 
         final Complication alwaysAvailableComplication = Mockito.mock(Complication.class);
@@ -212,7 +227,7 @@
     public void testComplicationWithNoTypeNotFiltered() {
         final Complication complication = Mockito.mock(Complication.class);
         final DreamOverlayStateController stateController =
-                new DreamOverlayStateController(mExecutor);
+                new DreamOverlayStateController(mExecutor, true);
         stateController.addComplication(complication);
         mExecutor.runAllReady();
         assertThat(stateController.getComplications(true).contains(complication))
@@ -222,7 +237,7 @@
     @Test
     public void testNotifyLowLightChanged() {
         final DreamOverlayStateController stateController =
-                new DreamOverlayStateController(mExecutor);
+                new DreamOverlayStateController(mExecutor, true);
 
         stateController.addCallback(mCallback);
         mExecutor.runAllReady();
@@ -238,7 +253,7 @@
     @Test
     public void testNotifyEntryAnimationsFinishedChanged() {
         final DreamOverlayStateController stateController =
-                new DreamOverlayStateController(mExecutor);
+                new DreamOverlayStateController(mExecutor, true);
 
         stateController.addCallback(mCallback);
         mExecutor.runAllReady();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationHostViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationHostViewControllerTest.java
index b477592..dcd8736 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationHostViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationHostViewControllerTest.java
@@ -15,6 +15,8 @@
  */
 package com.android.systemui.dreams.complication;
 
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
@@ -139,6 +141,21 @@
     }
 
     @Test
+    public void testMalformedComplicationAddition() {
+        final Observer<Collection<ComplicationViewModel>> observer =
+                captureComplicationViewModelsObserver();
+
+        // Add a complication and ensure it is added to the view.
+        final HashSet<ComplicationViewModel> complications = new HashSet<>(
+                Collections.singletonList(mComplicationViewModel));
+        when(mViewHolder.getView()).thenReturn(null);
+        observer.onChanged(complications);
+
+        verify(mLayoutEngine, never()).addComplication(any(), any(), any(), anyInt());
+
+    }
+
+    @Test
     public void testNewComplicationsBeforeEntryAnimationsFinishSetToInvisible() {
         final Observer<Collection<ComplicationViewModel>> observer =
                 captureComplicationViewModelsObserver();