Merge "Media Complication Introduction."
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java
index 0311740..41287b6 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java
@@ -31,6 +31,7 @@
 import com.android.systemui.keyguard.KeyguardViewMediator;
 import com.android.systemui.keyguard.dagger.KeyguardModule;
 import com.android.systemui.log.SessionTracker;
+import com.android.systemui.media.dream.MediaDreamSentinel;
 import com.android.systemui.media.systemsounds.HomeSoundEffectController;
 import com.android.systemui.power.PowerUI;
 import com.android.systemui.privacy.television.TvOngoingPrivacyChip;
@@ -226,4 +227,11 @@
     @ClassKey(SmartSpaceComplication.Registrant.class)
     public abstract CoreStartable bindSmartSpaceComplicationRegistrant(
             SmartSpaceComplication.Registrant registrant);
+
+    /** Inject into MediaDreamSentinel. */
+    @Binds
+    @IntoMap
+    @ClassKey(MediaDreamSentinel.class)
+    public abstract CoreStartable bindMediaDreamSentinel(
+            MediaDreamSentinel sentinel);
 }
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt
index c8cd432..64ebe56 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt
@@ -31,6 +31,7 @@
 import com.android.systemui.R
 import com.android.systemui.animation.Interpolators
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dreams.DreamOverlayStateController
 import com.android.systemui.keyguard.WakefulnessLifecycle
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.statusbar.CrossFadeHelper
@@ -82,7 +83,8 @@
     private val notifLockscreenUserManager: NotificationLockscreenUserManager,
     configurationController: ConfigurationController,
     wakefulnessLifecycle: WakefulnessLifecycle,
-    private val statusBarKeyguardViewManager: StatusBarKeyguardViewManager
+    private val statusBarKeyguardViewManager: StatusBarKeyguardViewManager,
+    private val dreamOverlayStateController: DreamOverlayStateController
 ) {
 
     /**
@@ -167,7 +169,7 @@
         })
     }
 
-    private val mediaHosts = arrayOfNulls<MediaHost>(LOCATION_LOCKSCREEN + 1)
+    private val mediaHosts = arrayOfNulls<MediaHost>(LOCATION_DREAM_OVERLAY + 1)
     /**
      * The last location where this view was at before going to the desired location. This is
      * useful for guided transitions.
@@ -349,6 +351,17 @@
         }
 
     /**
+     * Is the doze animation currently Running
+     */
+    private var dreamOverlayActive: Boolean = false
+        private set(value) {
+            if (field != value) {
+                field = value
+                updateDesiredLocation(forceNoAnimation = true)
+            }
+        }
+
+    /**
      * The current cross fade progress. 0.5f means it's just switching
      * between the start and the end location and the content is fully faded, while 0.75f means
      * that we're halfway faded in again in the target state.
@@ -444,6 +457,12 @@
             }
         })
 
+        dreamOverlayStateController.addCallback(object : DreamOverlayStateController.Callback {
+            override fun onStateChanged() {
+                dreamOverlayStateController.isOverlayActive.also { dreamOverlayActive = it }
+            }
+        })
+
         wakefulnessLifecycle.addObserver(object : WakefulnessLifecycle.Observer {
             override fun onFinishedGoingToSleep() {
                 goingToSleep = false
@@ -940,6 +959,7 @@
                 statusbarState == StatusBarState.FULLSCREEN_USER_SWITCHER))
         val allowedOnLockscreen = notifLockscreenUserManager.shouldShowLockscreenNotifications()
         val location = when {
+            dreamOverlayActive -> LOCATION_DREAM_OVERLAY
             (qsExpansion > 0.0f || inSplitShade) && !onLockscreen -> LOCATION_QS
             qsExpansion > 0.4f && onLockscreen -> LOCATION_QS
             !hasActiveMedia -> LOCATION_QS
@@ -1035,6 +1055,11 @@
         const val LOCATION_LOCKSCREEN = 2
 
         /**
+         * Attached on the dream overlay
+         */
+        const val LOCATION_DREAM_OVERLAY = 3
+
+        /**
          * Attached at the root of the hierarchy in an overlay
          */
         const val IN_OVERLAY = -1000
diff --git a/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java b/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java
index 4baef3a..2bc910e 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java
@@ -25,6 +25,7 @@
 import com.android.systemui.media.MediaHierarchyManager;
 import com.android.systemui.media.MediaHost;
 import com.android.systemui.media.MediaHostStatesManager;
+import com.android.systemui.media.dream.dagger.MediaComplicationComponent;
 import com.android.systemui.media.taptotransfer.MediaTttCommandLineHelper;
 import com.android.systemui.media.taptotransfer.MediaTttFlags;
 import com.android.systemui.media.taptotransfer.receiver.MediaTttChipControllerReceiver;
@@ -43,11 +44,14 @@
 import dagger.multibindings.IntoMap;
 
 /** Dagger module for the media package. */
-@Module
+@Module(subcomponents = {
+        MediaComplicationComponent.class,
+})
 public interface MediaModule {
     String QS_PANEL = "media_qs_panel";
     String QUICK_QS_PANEL = "media_quick_qs_panel";
     String KEYGUARD = "media_keyguard";
+    String DREAM = "dream";
 
     /** */
     @Provides
@@ -82,6 +86,16 @@
     /** */
     @Provides
     @SysUISingleton
+    @Named(DREAM)
+    static MediaHost providesDreamMediaHost(MediaHost.MediaHostStateHolder stateHolder,
+            MediaHierarchyManager hierarchyManager, MediaDataManager dataManager,
+            MediaHostStatesManager statesManager) {
+        return new MediaHost(stateHolder, hierarchyManager, dataManager, statesManager);
+    }
+
+    /** */
+    @Provides
+    @SysUISingleton
     static Optional<MediaTttChipControllerSender> providesMediaTttChipControllerSender(
             MediaTttFlags mediaTttFlags,
             Context context,
diff --git a/packages/SystemUI/src/com/android/systemui/media/dream/MediaComplicationViewController.java b/packages/SystemUI/src/com/android/systemui/media/dream/MediaComplicationViewController.java
new file mode 100644
index 0000000..65c5bc7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/dream/MediaComplicationViewController.java
@@ -0,0 +1,68 @@
+/*
+ * 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.media.dream;
+
+import static com.android.systemui.media.dagger.MediaModule.DREAM;
+import static com.android.systemui.media.dream.dagger.MediaComplicationComponent.MediaComplicationModule.MEDIA_COMPLICATION_CONTAINER;
+
+import android.widget.FrameLayout;
+
+import com.android.systemui.media.MediaHierarchyManager;
+import com.android.systemui.media.MediaHost;
+import com.android.systemui.media.MediaHostState;
+import com.android.systemui.util.ViewController;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+
+/**
+ * {@link MediaComplicationViewController} handles connecting the
+ * {@link com.android.systemui.dreams.complication.Complication} view to the {@link MediaHost}.
+ */
+public class MediaComplicationViewController extends ViewController<FrameLayout> {
+    private final MediaHost mMediaHost;
+
+    @Inject
+    public MediaComplicationViewController(
+            @Named(MEDIA_COMPLICATION_CONTAINER) FrameLayout view,
+            @Named(DREAM) MediaHost mediaHost) {
+        super(view);
+        mMediaHost = mediaHost;
+    }
+
+    @Override
+    protected void onInit() {
+        super.onInit();
+        mMediaHost.setExpansion(MediaHostState.COLLAPSED);
+        mMediaHost.setShowsOnlyActiveMedia(true);
+        mMediaHost.setFalsingProtectionNeeded(true);
+        mMediaHost.init(MediaHierarchyManager.LOCATION_DREAM_OVERLAY);
+    }
+
+    @Override
+    protected void onViewAttached() {
+        mMediaHost.hostView.setLayoutParams(new FrameLayout.LayoutParams(
+                FrameLayout.LayoutParams.MATCH_PARENT,
+                FrameLayout.LayoutParams.WRAP_CONTENT));
+        mView.addView(mMediaHost.hostView);
+    }
+
+    @Override
+    protected void onViewDetached() {
+        mView.removeView(mMediaHost.hostView);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/dream/MediaDreamComplication.java b/packages/SystemUI/src/com/android/systemui/media/dream/MediaDreamComplication.java
new file mode 100644
index 0000000..2c35db3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/dream/MediaDreamComplication.java
@@ -0,0 +1,43 @@
+/*
+ * 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.media.dream;
+
+import com.android.systemui.dreams.complication.Complication;
+import com.android.systemui.dreams.complication.ComplicationViewModel;
+import com.android.systemui.media.dream.dagger.MediaComplicationComponent;
+
+import javax.inject.Inject;
+
+/**
+ * Media control complication for dream overlay.
+ */
+public class MediaDreamComplication implements Complication {
+    MediaComplicationComponent.Factory mComponentFactory;
+
+    /**
+     * Default constructor for {@link MediaDreamComplication}.
+     */
+    @Inject
+    public MediaDreamComplication(MediaComplicationComponent.Factory componentFactory) {
+        mComponentFactory = componentFactory;
+    }
+
+    @Override
+    public ViewHolder createView(ComplicationViewModel model) {
+        return mComponentFactory.create().getViewHolder();
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/dream/MediaDreamSentinel.java b/packages/SystemUI/src/com/android/systemui/media/dream/MediaDreamSentinel.java
new file mode 100644
index 0000000..8934cd10
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/dream/MediaDreamSentinel.java
@@ -0,0 +1,97 @@
+/*
+ * 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.media.dream;
+
+import android.content.Context;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.systemui.CoreStartable;
+import com.android.systemui.dreams.DreamOverlayStateController;
+import com.android.systemui.media.MediaData;
+import com.android.systemui.media.MediaDataManager;
+import com.android.systemui.media.SmartspaceMediaData;
+
+import javax.inject.Inject;
+
+/**
+ * {@link MediaDreamSentinel} is responsible for tracking media state and registering/unregistering
+ * the media complication as appropriate
+ */
+public class MediaDreamSentinel extends CoreStartable {
+    private MediaDataManager.Listener mListener = new MediaDataManager.Listener() {
+        private boolean mAdded;
+        @Override
+        public void onSmartspaceMediaDataRemoved(@NonNull String key, boolean immediately) {
+        }
+
+        @Override
+        public void onMediaDataRemoved(@NonNull String key) {
+            if (!mAdded) {
+                return;
+            }
+
+            if (mMediaDataManager.hasActiveMedia()) {
+                return;
+            }
+
+            mAdded = false;
+            mDreamOverlayStateController.removeComplication(mComplication);
+        }
+
+        @Override
+        public void onSmartspaceMediaDataLoaded(@NonNull String key,
+                @NonNull SmartspaceMediaData data, boolean shouldPrioritize,
+                boolean isSsReactivated) {
+        }
+
+        @Override
+        public void onMediaDataLoaded(@NonNull String key, @Nullable String oldKey,
+                @NonNull MediaData data, boolean immediately, int receivedSmartspaceCardLatency) {
+            if (mAdded) {
+                return;
+            }
+
+            if (!mMediaDataManager.hasActiveMedia()) {
+                return;
+            }
+
+            mAdded = true;
+            mDreamOverlayStateController.addComplication(mComplication);
+        }
+    };
+
+    private final MediaDataManager mMediaDataManager;
+    private final DreamOverlayStateController mDreamOverlayStateController;
+    private final MediaDreamComplication mComplication;
+
+    @Inject
+    public MediaDreamSentinel(Context context, MediaDataManager mediaDataManager,
+            DreamOverlayStateController dreamOverlayStateController,
+            MediaDreamComplication complication) {
+        super(context);
+        mMediaDataManager = mediaDataManager;
+        mDreamOverlayStateController = dreamOverlayStateController;
+        mComplication = complication;
+    }
+
+    @Override
+    public void start() {
+        mMediaDataManager.addListener(mListener);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/dream/MediaViewHolder.java b/packages/SystemUI/src/com/android/systemui/media/dream/MediaViewHolder.java
new file mode 100644
index 0000000..128a38c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/dream/MediaViewHolder.java
@@ -0,0 +1,58 @@
+/*
+ * 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.media.dream;
+
+import static com.android.systemui.media.dream.dagger.MediaComplicationComponent.MediaComplicationModule.MEDIA_COMPLICATION_CONTAINER;
+import static com.android.systemui.media.dream.dagger.MediaComplicationComponent.MediaComplicationModule.MEDIA_COMPLICATION_LAYOUT_PARAMS;
+
+import android.view.View;
+import android.widget.FrameLayout;
+
+import com.android.systemui.dreams.complication.Complication;
+import com.android.systemui.dreams.complication.ComplicationLayoutParams;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+
+/**
+ * {@link Complication.ViewHolder} implementation for media control.
+ */
+public class MediaViewHolder implements Complication.ViewHolder {
+    private final FrameLayout mContainer;
+    private final MediaComplicationViewController mViewController;
+    private final ComplicationLayoutParams mLayoutParams;
+
+    @Inject
+    MediaViewHolder(@Named(MEDIA_COMPLICATION_CONTAINER) FrameLayout container,
+            MediaComplicationViewController controller,
+            @Named(MEDIA_COMPLICATION_LAYOUT_PARAMS) ComplicationLayoutParams layoutParams) {
+        mContainer = container;
+        mViewController = controller;
+        mViewController.init();
+        mLayoutParams = layoutParams;
+    }
+
+    @Override
+    public View getView() {
+        return mContainer;
+    }
+
+    @Override
+    public ComplicationLayoutParams getLayoutParams() {
+        return mLayoutParams;
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/dream/dagger/MediaComplicationComponent.java b/packages/SystemUI/src/com/android/systemui/media/dream/dagger/MediaComplicationComponent.java
new file mode 100644
index 0000000..3372899
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/dream/dagger/MediaComplicationComponent.java
@@ -0,0 +1,100 @@
+/*
+ * 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.media.dream.dagger;
+
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import android.content.Context;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+
+import com.android.systemui.dreams.complication.ComplicationLayoutParams;
+import com.android.systemui.media.dream.MediaViewHolder;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+
+import javax.inject.Named;
+import javax.inject.Scope;
+
+import dagger.Module;
+import dagger.Provides;
+import dagger.Subcomponent;
+
+/**
+ * {@link MediaComplicationComponent} is responsible for generating dependencies surrounding the
+ * media {@link com.android.systemui.dreams.complication.Complication}, such as view controllers
+ * and layout details.
+ */
+@Subcomponent(modules = {
+        MediaComplicationComponent.MediaComplicationModule.class,
+})
+@MediaComplicationComponent.MediaComplicationScope
+public interface MediaComplicationComponent {
+    @Documented
+    @Retention(RUNTIME)
+    @Scope
+    @interface MediaComplicationScope {}
+
+    /**
+     * Generates {@link MediaComplicationComponent}.
+     */
+    @Subcomponent.Factory
+    interface Factory {
+        MediaComplicationComponent create();
+    }
+
+    /**
+     * Creates {@link MediaViewHolder}.
+     */
+    MediaViewHolder getViewHolder();
+
+    /**
+     * Scoped values for {@link MediaComplicationComponent}.
+     */
+    @Module
+    interface MediaComplicationModule {
+        String MEDIA_COMPLICATION_CONTAINER = "media_complication_container";
+        String MEDIA_COMPLICATION_LAYOUT_PARAMS = "media_complication_layout_params";
+
+        /**
+         * Provides the complication view.
+         */
+        @Provides
+        @MediaComplicationScope
+        @Named(MEDIA_COMPLICATION_CONTAINER)
+        static FrameLayout provideComplicationContainer(Context context) {
+            return new FrameLayout(context);
+        }
+
+        /**
+         * Provides the layout parameters for the complication view.
+         */
+        @Provides
+        @MediaComplicationScope
+        @Named(MEDIA_COMPLICATION_LAYOUT_PARAMS)
+        static ComplicationLayoutParams provideLayoutParams() {
+            return new ComplicationLayoutParams(0,
+                    ViewGroup.LayoutParams.WRAP_CONTENT,
+                    ComplicationLayoutParams.POSITION_BOTTOM
+                            | ComplicationLayoutParams.POSITION_END,
+                    ComplicationLayoutParams.DIRECTION_UP,
+                    0,
+                    true);
+        }
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaHierarchyManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaHierarchyManagerTest.kt
index a3ffb2f..97b3b10 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaHierarchyManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaHierarchyManagerTest.kt
@@ -25,6 +25,7 @@
 import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.controls.controller.ControlsControllerImplTest.Companion.eq
+import com.android.systemui.dreams.DreamOverlayStateController
 import com.android.systemui.keyguard.WakefulnessLifecycle
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.statusbar.NotificationLockscreenUserManager
@@ -85,6 +86,8 @@
     private lateinit var configurationController: ConfigurationController
     @Mock
     private lateinit var uniqueObjectHostView: UniqueObjectHostView
+    @Mock
+    private lateinit var dreamOverlayStateController: DreamOverlayStateController
     @Captor
     private lateinit var wakefullnessObserver: ArgumentCaptor<(WakefulnessLifecycle.Observer)>
     @Captor
@@ -110,7 +113,8 @@
                 notificationLockscreenUserManager,
                 configurationController,
                 wakefulnessLifecycle,
-                statusBarKeyguardViewManager)
+                statusBarKeyguardViewManager,
+                dreamOverlayStateController)
         verify(wakefulnessLifecycle).addObserver(wakefullnessObserver.capture())
         verify(statusBarStateController).addCallback(statusBarCallback.capture())
         setupHost(lockHost, MediaHierarchyManager.LOCATION_LOCKSCREEN)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dream/MediaComplicationViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dream/MediaComplicationViewControllerTest.java
new file mode 100644
index 0000000..29188da
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dream/MediaComplicationViewControllerTest.java
@@ -0,0 +1,68 @@
+/*
+ * 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.media.dream;
+
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
+
+import android.testing.AndroidTestingRunner;
+import android.widget.FrameLayout;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.media.MediaHost;
+import com.android.systemui.util.animation.UniqueObjectHostView;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class MediaComplicationViewControllerTest extends SysuiTestCase {
+    @Mock
+    private MediaHost mMediaHost;
+
+    @Mock
+    private UniqueObjectHostView mView;
+
+    @Mock
+    private FrameLayout mComplicationContainer;
+
+    @Before
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+        mMediaHost.hostView = mView;
+    }
+
+    @Test
+    public void testMediaHostViewInteraction() {
+        final MediaComplicationViewController controller = new MediaComplicationViewController(
+                mComplicationContainer, mMediaHost);
+
+        controller.init();
+
+        controller.onViewAttached();
+        verify(mComplicationContainer).addView(eq(mView));
+
+        controller.onViewDetached();
+        verify(mComplicationContainer).removeView(eq(mView));
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dream/MediaDreamSentinelTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dream/MediaDreamSentinelTest.java
new file mode 100644
index 0000000..114fc90
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dream/MediaDreamSentinelTest.java
@@ -0,0 +1,93 @@
+/*
+ * 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.media.dream;
+
+import static org.mockito.ArgumentMatchers.any;
+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.testing.AndroidTestingRunner;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.dreams.DreamOverlayStateController;
+import com.android.systemui.media.MediaData;
+import com.android.systemui.media.MediaDataManager;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class MediaDreamSentinelTest extends SysuiTestCase {
+    @Mock
+    MediaDataManager mMediaDataManager;
+
+    @Mock
+    DreamOverlayStateController mDreamOverlayStateController;
+
+    @Mock
+    MediaDreamComplication mComplication;
+
+    final String mKey = "key";
+    final String mOldKey = "old_key";
+
+    @Mock
+    MediaData mData;
+
+    @Before
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+    }
+
+    @Test
+    public void testComplicationAddition() {
+        final MediaDreamSentinel sentinel = new MediaDreamSentinel(mContext, mMediaDataManager,
+                mDreamOverlayStateController, mComplication);
+
+        sentinel.start();
+
+        ArgumentCaptor<MediaDataManager.Listener> listenerCaptor =
+                ArgumentCaptor.forClass(MediaDataManager.Listener.class);
+        verify(mMediaDataManager).addListener(listenerCaptor.capture());
+
+        final MediaDataManager.Listener listener = listenerCaptor.getValue();
+
+        when(mMediaDataManager.hasActiveMedia()).thenReturn(false);
+        listener.onMediaDataLoaded(mKey, mOldKey, mData, true, 0);
+        verify(mDreamOverlayStateController, never()).addComplication(any());
+
+        when(mMediaDataManager.hasActiveMedia()).thenReturn(true);
+        listener.onMediaDataLoaded(mKey, mOldKey, mData, true, 0);
+        verify(mDreamOverlayStateController).addComplication(eq(mComplication));
+
+        listener.onMediaDataRemoved(mKey);
+        verify(mDreamOverlayStateController, never()).removeComplication(any());
+
+        when(mMediaDataManager.hasActiveMedia()).thenReturn(false);
+        listener.onMediaDataRemoved(mKey);
+        verify(mDreamOverlayStateController).removeComplication(eq(mComplication));
+    }
+
+}