diff --git a/packages/SystemUI/res/layout/dream_overlay_status_bar_view.xml b/packages/SystemUI/res/layout/dream_overlay_status_bar_view.xml
index 006b260434bc9..9add32c6ee0ae 100644
--- a/packages/SystemUI/res/layout/dream_overlay_status_bar_view.xml
+++ b/packages/SystemUI/res/layout/dream_overlay_status_bar_view.xml
@@ -18,6 +18,7 @@
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/dream_overlay_status_bar"
+ android:visibility="invisible"
android:layout_width="match_parent"
android:layout_height="@dimen/dream_overlay_status_bar_height"
android:paddingEnd="@dimen/dream_overlay_status_bar_margin"
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 93982cb2c5b98..a0b255a3bc051 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -743,6 +743,17 @@
1000
+
+ 249
+
+ 133
+
+ 282
+
+ 216
+
+ 299
+
- @*android:string/status_bar_volume
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt
new file mode 100644
index 0000000000000..d8dd6a21d4c16
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt
@@ -0,0 +1,147 @@
+/*
+ * 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
+
+import android.animation.Animator
+import android.animation.AnimatorListenerAdapter
+import android.animation.AnimatorSet
+import android.animation.ValueAnimator
+import android.view.View
+import androidx.core.animation.doOnEnd
+import com.android.systemui.animation.Interpolators
+import com.android.systemui.dreams.complication.ComplicationHostViewController
+import com.android.systemui.dreams.complication.ComplicationLayoutParams
+import com.android.systemui.dreams.dagger.DreamOverlayModule
+import com.android.systemui.statusbar.BlurUtils
+import java.util.function.Consumer
+import javax.inject.Inject
+import javax.inject.Named
+
+/** Controller for dream overlay animations. */
+class DreamOverlayAnimationsController
+@Inject
+constructor(
+ private val mBlurUtils: BlurUtils,
+ private val mComplicationHostViewController: ComplicationHostViewController,
+ private val mStatusBarViewController: DreamOverlayStatusBarViewController,
+ private val mOverlayStateController: DreamOverlayStateController,
+ @Named(DreamOverlayModule.DREAM_IN_BLUR_ANIMATION_DURATION)
+ private val mDreamInBlurAnimDuration: Int,
+ @Named(DreamOverlayModule.DREAM_IN_BLUR_ANIMATION_DELAY) private val mDreamInBlurAnimDelay: Int,
+ @Named(DreamOverlayModule.DREAM_IN_COMPLICATIONS_ANIMATION_DURATION)
+ private val mDreamInComplicationsAnimDuration: Int,
+ @Named(DreamOverlayModule.DREAM_IN_TOP_COMPLICATIONS_ANIMATION_DELAY)
+ private val mDreamInTopComplicationsAnimDelay: Int,
+ @Named(DreamOverlayModule.DREAM_IN_BOTTOM_COMPLICATIONS_ANIMATION_DELAY)
+ private val mDreamInBottomComplicationsAnimDelay: Int
+) {
+
+ var mEntryAnimations: AnimatorSet? = null
+
+ /** Starts the dream content and dream overlay entry animations. */
+ fun startEntryAnimations(view: View) {
+ cancelRunningEntryAnimations()
+
+ mEntryAnimations = AnimatorSet()
+ mEntryAnimations?.apply {
+ playTogether(
+ buildDreamInBlurAnimator(view),
+ buildDreamInTopComplicationsAnimator(),
+ buildDreamInBottomComplicationsAnimator()
+ )
+ doOnEnd { mOverlayStateController.setEntryAnimationsFinished(true) }
+ start()
+ }
+ }
+
+ /** Cancels the dream content and dream overlay animations, if they're currently running. */
+ fun cancelRunningEntryAnimations() {
+ if (mEntryAnimations?.isRunning == true) {
+ mEntryAnimations?.cancel()
+ }
+ mEntryAnimations = null
+ }
+
+ private fun buildDreamInBlurAnimator(view: View): Animator {
+ return ValueAnimator.ofFloat(1f, 0f).apply {
+ duration = mDreamInBlurAnimDuration.toLong()
+ startDelay = mDreamInBlurAnimDelay.toLong()
+ interpolator = Interpolators.LINEAR
+ addUpdateListener { animator: ValueAnimator ->
+ mBlurUtils.applyBlur(
+ view.viewRootImpl,
+ mBlurUtils.blurRadiusOfRatio(animator.animatedValue as Float).toInt(),
+ false /*opaque*/
+ )
+ }
+ }
+ }
+
+ private fun buildDreamInTopComplicationsAnimator(): Animator {
+ return ValueAnimator.ofFloat(0f, 1f).apply {
+ duration = mDreamInComplicationsAnimDuration.toLong()
+ startDelay = mDreamInTopComplicationsAnimDelay.toLong()
+ interpolator = Interpolators.LINEAR
+ addUpdateListener { va: ValueAnimator ->
+ setTopElementsAlpha(va.animatedValue as Float)
+ }
+ }
+ }
+
+ private fun buildDreamInBottomComplicationsAnimator(): Animator {
+ return ValueAnimator.ofFloat(0f, 1f).apply {
+ duration = mDreamInComplicationsAnimDuration.toLong()
+ startDelay = mDreamInBottomComplicationsAnimDelay.toLong()
+ interpolator = Interpolators.LINEAR
+ addUpdateListener { va: ValueAnimator ->
+ setBottomElementsAlpha(va.animatedValue as Float)
+ }
+ addListener(
+ object : AnimatorListenerAdapter() {
+ override fun onAnimationStart(animation: Animator) {
+ mComplicationHostViewController
+ .getViewsAtPosition(ComplicationLayoutParams.POSITION_BOTTOM)
+ .forEach(Consumer { v: View -> v.visibility = View.VISIBLE })
+ }
+ }
+ )
+ }
+ }
+
+ /** Sets alpha of top complications and the status bar. */
+ private fun setTopElementsAlpha(alpha: Float) {
+ mComplicationHostViewController
+ .getViewsAtPosition(ComplicationLayoutParams.POSITION_TOP)
+ .forEach(Consumer { v: View -> setAlphaAndEnsureVisible(v, alpha) })
+ mStatusBarViewController.setAlpha(alpha)
+ }
+
+ /** Sets alpha of bottom complications. */
+ private fun setBottomElementsAlpha(alpha: Float) {
+ mComplicationHostViewController
+ .getViewsAtPosition(ComplicationLayoutParams.POSITION_BOTTOM)
+ .forEach(Consumer { v: View -> setAlphaAndEnsureVisible(v, alpha) })
+ }
+
+ private fun setAlphaAndEnsureVisible(view: View, alpha: Float) {
+ if (alpha > 0 && view.visibility != View.VISIBLE) {
+ view.visibility = View.VISIBLE
+ }
+
+ view.alpha = alpha
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java
index 733a80dd7f69f..d0d01846c3d90 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java
@@ -54,6 +54,8 @@ public class DreamOverlayContainerViewController extends ViewController callback.onStateChanged());
+ notifyCallbacks(Callback::onStateChanged);
}
}
@@ -238,6 +247,15 @@ public class DreamOverlayStateController implements
modifyState(active ? OP_SET_STATE : OP_CLEAR_STATE, STATE_LOW_LIGHT_ACTIVE);
}
+ /**
+ * Sets whether dream content and dream overlay entry animations are finished.
+ * @param finished {@code true} if entry animations are finished, {@code false} otherwise.
+ */
+ public void setEntryAnimationsFinished(boolean finished) {
+ modifyState(finished ? OP_SET_STATE : OP_CLEAR_STATE,
+ STATE_DREAM_ENTRY_ANIMATIONS_FINISHED);
+ }
+
/**
* Returns the available complication types.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java
index bb1c4303041ad..d17fbe31c8d2c 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java
@@ -16,10 +16,6 @@
package com.android.systemui.dreams;
-import static android.app.StatusBarManager.WINDOW_STATE_HIDDEN;
-import static android.app.StatusBarManager.WINDOW_STATE_HIDING;
-import static android.app.StatusBarManager.WINDOW_STATE_SHOWING;
-
import android.app.AlarmManager;
import android.app.StatusBarManager;
import android.content.res.Resources;
@@ -83,6 +79,9 @@ public class DreamOverlayStatusBarViewController extends ViewController {
- if (!mIsAttached || mDreamOverlayStateController.isLowLightActive()) {
- return;
- }
+ if (!mIsAttached || !mEntryAnimationsFinished) {
+ return;
+ }
- switch (state) {
- case WINDOW_STATE_SHOWING:
- mView.setVisibility(View.INVISIBLE);
- break;
- case WINDOW_STATE_HIDING:
- case WINDOW_STATE_HIDDEN:
- mView.setVisibility(View.VISIBLE);
- break;
- }
- });
+ mMainExecutor.execute(this::updateVisibility);
}
private void onStatusBarItemsChanged(List newItems) {
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 fd6cfc0700add..100ccc35e638f 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationHostViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationHostViewController.java
@@ -28,6 +28,7 @@ import android.view.View;
import androidx.constraintlayout.widget.ConstraintLayout;
import androidx.lifecycle.LifecycleOwner;
+import com.android.systemui.dreams.DreamOverlayStateController;
import com.android.systemui.util.ViewController;
import java.util.Collection;
@@ -49,20 +50,34 @@ public class ComplicationHostViewController extends ViewController mComplications = new HashMap<>();
+ // Whether dream entry animations are finished.
+ private boolean mEntryAnimationsFinished = false;
+
@Inject
protected ComplicationHostViewController(
@Named(SCOPED_COMPLICATIONS_LAYOUT) ConstraintLayout view,
ComplicationLayoutEngine layoutEngine,
+ DreamOverlayStateController dreamOverlayStateController,
LifecycleOwner lifecycleOwner,
@Named(SCOPED_COMPLICATIONS_MODEL) ComplicationCollectionViewModel viewModel) {
super(view);
mLayoutEngine = layoutEngine;
mLifecycleOwner = lifecycleOwner;
mComplicationCollectionViewModel = viewModel;
+ mDreamOverlayStateController = dreamOverlayStateController;
+
+ mDreamOverlayStateController.addCallback(new DreamOverlayStateController.Callback() {
+ @Override
+ public void onStateChanged() {
+ mEntryAnimationsFinished =
+ mDreamOverlayStateController.areEntryAnimationsFinished();
+ }
+ });
}
@Override
@@ -123,6 +138,11 @@ public class ComplicationHostViewController extends ViewController lifecycleRegistryLazy) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java
index c5a7de410eb0f..c234178f8af3c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java
@@ -92,6 +92,12 @@ public class DreamOverlayContainerViewControllerTest extends SysuiTestCase {
@Mock
BouncerCallbackInteractor mBouncerCallbackInteractor;
+ @Mock
+ DreamOverlayAnimationsController mAnimationsController;
+
+ @Mock
+ DreamOverlayStateController mStateController;
+
DreamOverlayContainerViewController mController;
@Before
@@ -115,7 +121,9 @@ public class DreamOverlayContainerViewControllerTest extends SysuiTestCase {
MAX_BURN_IN_OFFSET,
BURN_IN_PROTECTION_UPDATE_INTERVAL,
MILLIS_UNTIL_FULL_JITTER,
- mBouncerCallbackInteractor);
+ mBouncerCallbackInteractor,
+ mAnimationsController,
+ mStateController);
}
@Test
@@ -188,4 +196,31 @@ public class DreamOverlayContainerViewControllerTest extends SysuiTestCase {
verify(mBlurUtils).blurRadiusOfRatio(1 - scaledFraction);
verify(mBlurUtils).applyBlur(mViewRoot, (int) blurRadius, false);
}
+
+ @Test
+ public void testStartDreamEntryAnimationsOnAttachedNonLowLight() {
+ when(mStateController.isLowLightActive()).thenReturn(false);
+
+ mController.onViewAttached();
+
+ verify(mAnimationsController).startEntryAnimations(mDreamOverlayContainerView);
+ verify(mAnimationsController, never()).cancelRunningEntryAnimations();
+ }
+
+ @Test
+ public void testNeverStartDreamEntryAnimationsOnAttachedForLowLight() {
+ when(mStateController.isLowLightActive()).thenReturn(true);
+
+ mController.onViewAttached();
+
+ verify(mAnimationsController, never()).startEntryAnimations(mDreamOverlayContainerView);
+ }
+
+ @Test
+ public void testCancelDreamEntryAnimationsOnDetached() {
+ mController.onViewAttached();
+ mController.onViewDetached();
+
+ verify(mAnimationsController).cancelRunningEntryAnimations();
+ }
}
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 f370be190761d..b7f694f85f151 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java
@@ -253,6 +253,7 @@ public class DreamOverlayServiceTest extends SysuiTestCase {
verify(mLifecycleRegistry).setCurrentState(Lifecycle.State.DESTROYED);
verify(mStateController).setOverlayActive(false);
verify(mStateController).setLowLightActive(false);
+ verify(mStateController).setEntryAnimationsFinished(false);
}
@Test
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 d1d32a13589a4..c21c7a2aacbe8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java
@@ -234,4 +234,20 @@ public class DreamOverlayStateControllerTest extends SysuiTestCase {
verify(mCallback, times(1)).onStateChanged();
assertThat(stateController.isLowLightActive()).isTrue();
}
+
+ @Test
+ public void testNotifyEntryAnimationsFinishedChanged() {
+ final DreamOverlayStateController stateController =
+ new DreamOverlayStateController(mExecutor);
+
+ stateController.addCallback(mCallback);
+ mExecutor.runAllReady();
+ assertThat(stateController.areEntryAnimationsFinished()).isFalse();
+
+ stateController.setEntryAnimationsFinished(true);
+ mExecutor.runAllReady();
+
+ verify(mCallback, times(1)).onStateChanged();
+ assertThat(stateController.areEntryAnimationsFinished()).isTrue();
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java
index aa021781296a2..85c28190d77bf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java
@@ -19,16 +19,20 @@ package com.android.systemui.dreams;
import static android.app.StatusBarManager.WINDOW_STATE_HIDDEN;
import static android.app.StatusBarManager.WINDOW_STATE_SHOWING;
+import static com.google.common.truth.Truth.assertThat;
+
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.isNull;
+import static org.mockito.Mockito.doCallRealMethod;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.app.AlarmManager;
+import android.content.Context;
import android.content.res.Resources;
import android.hardware.SensorPrivacyManager;
import android.net.ConnectivityManager;
@@ -55,6 +59,7 @@ 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;
@@ -69,7 +74,7 @@ public class DreamOverlayStatusBarViewControllerTest extends SysuiTestCase {
"{count, plural, =1 {# notification} other {# notifications}}";
@Mock
- DreamOverlayStatusBarView mView;
+ MockDreamOverlayStatusBarView mView;
@Mock
ConnectivityManager mConnectivityManager;
@Mock
@@ -105,6 +110,9 @@ public class DreamOverlayStatusBarViewControllerTest extends SysuiTestCase {
@Mock
DreamOverlayStateController mDreamOverlayStateController;
+ @Captor
+ private ArgumentCaptor mCallbackCaptor;
+
private final Executor mMainExecutor = Runnable::run;
DreamOverlayStatusBarViewController mController;
@@ -115,6 +123,8 @@ public class DreamOverlayStatusBarViewControllerTest extends SysuiTestCase {
when(mResources.getString(R.string.dream_overlay_status_bar_notification_indicator))
.thenReturn(NOTIFICATION_INDICATOR_FORMATTER_STRING);
+ doCallRealMethod().when(mView).setVisibility(anyInt());
+ doCallRealMethod().when(mView).getVisibility();
mController = new DreamOverlayStatusBarViewController(
mView,
@@ -454,12 +464,10 @@ public class DreamOverlayStatusBarViewControllerTest extends SysuiTestCase {
public void testStatusBarHiddenWhenSystemStatusBarShown() {
mController.onViewAttached();
- final ArgumentCaptor
- callbackCapture = ArgumentCaptor.forClass(StatusBarWindowStateListener.class);
- verify(mStatusBarWindowStateController).addListener(callbackCapture.capture());
- callbackCapture.getValue().onStatusBarWindowStateChanged(WINDOW_STATE_SHOWING);
+ updateEntryAnimationsFinished();
+ updateStatusBarWindowState(true);
- verify(mView).setVisibility(View.INVISIBLE);
+ assertThat(mView.getVisibility()).isEqualTo(View.INVISIBLE);
}
@Test
@@ -467,28 +475,42 @@ public class DreamOverlayStatusBarViewControllerTest extends SysuiTestCase {
mController.onViewAttached();
reset(mView);
- final ArgumentCaptor
- callbackCapture = ArgumentCaptor.forClass(StatusBarWindowStateListener.class);
- verify(mStatusBarWindowStateController).addListener(callbackCapture.capture());
- callbackCapture.getValue().onStatusBarWindowStateChanged(WINDOW_STATE_HIDDEN);
+ updateEntryAnimationsFinished();
+ updateStatusBarWindowState(false);
- verify(mView).setVisibility(View.VISIBLE);
+ assertThat(mView.getVisibility()).isEqualTo(View.VISIBLE);
}
@Test
public void testUnattachedStatusBarVisibilityUnchangedWhenSystemStatusBarHidden() {
mController.onViewAttached();
+ updateEntryAnimationsFinished();
mController.onViewDetached();
reset(mView);
- final ArgumentCaptor
- callbackCapture = ArgumentCaptor.forClass(StatusBarWindowStateListener.class);
- verify(mStatusBarWindowStateController).addListener(callbackCapture.capture());
- callbackCapture.getValue().onStatusBarWindowStateChanged(WINDOW_STATE_SHOWING);
+ updateStatusBarWindowState(true);
verify(mView, never()).setVisibility(anyInt());
}
+ @Test
+ public void testNoChangeToVisibilityBeforeDreamStartedWhenStatusBarHidden() {
+ mController.onViewAttached();
+
+ // Trigger status bar window state change.
+ final StatusBarWindowStateListener listener = updateStatusBarWindowState(false);
+
+ // Verify no visibility change because dream not started.
+ verify(mView, never()).setVisibility(anyInt());
+
+ // Dream entry animations finished.
+ updateEntryAnimationsFinished();
+
+ // Trigger another status bar window state change, and verify visibility change.
+ listener.onStatusBarWindowStateChanged(WINDOW_STATE_HIDDEN);
+ assertThat(mView.getVisibility()).isEqualTo(View.VISIBLE);
+ }
+
@Test
public void testExtraStatusBarItemSetWhenItemsChange() {
mController.onViewAttached();
@@ -507,16 +529,75 @@ public class DreamOverlayStatusBarViewControllerTest extends SysuiTestCase {
public void testLowLightHidesStatusBar() {
when(mDreamOverlayStateController.isLowLightActive()).thenReturn(true);
mController.onViewAttached();
+ updateEntryAnimationsFinished();
- verify(mView).setVisibility(View.INVISIBLE);
- reset(mView);
-
- when(mDreamOverlayStateController.isLowLightActive()).thenReturn(false);
final ArgumentCaptor callbackCapture =
ArgumentCaptor.forClass(DreamOverlayStateController.Callback.class);
verify(mDreamOverlayStateController).addCallback(callbackCapture.capture());
callbackCapture.getValue().onStateChanged();
- verify(mView).setVisibility(View.VISIBLE);
+ assertThat(mView.getVisibility()).isEqualTo(View.INVISIBLE);
+ reset(mView);
+
+ when(mDreamOverlayStateController.isLowLightActive()).thenReturn(false);
+ callbackCapture.getValue().onStateChanged();
+
+ assertThat(mView.getVisibility()).isEqualTo(View.VISIBLE);
+ }
+
+ @Test
+ public void testNoChangeToVisibilityBeforeDreamStartedWhenLowLightStateChange() {
+ when(mDreamOverlayStateController.isLowLightActive()).thenReturn(false);
+ mController.onViewAttached();
+
+ // No change to visibility because dream not fully started.
+ verify(mView, never()).setVisibility(anyInt());
+
+ // Dream entry animations finished.
+ updateEntryAnimationsFinished();
+
+ // Trigger state change and verify visibility changed.
+ final ArgumentCaptor callbackCapture =
+ ArgumentCaptor.forClass(DreamOverlayStateController.Callback.class);
+ verify(mDreamOverlayStateController).addCallback(callbackCapture.capture());
+ callbackCapture.getValue().onStateChanged();
+
+ assertThat(mView.getVisibility()).isEqualTo(View.VISIBLE);
+ }
+
+ private StatusBarWindowStateListener updateStatusBarWindowState(boolean show) {
+ when(mStatusBarWindowStateController.windowIsShowing()).thenReturn(show);
+ final ArgumentCaptor
+ callbackCapture = ArgumentCaptor.forClass(StatusBarWindowStateListener.class);
+ verify(mStatusBarWindowStateController).addListener(callbackCapture.capture());
+ final StatusBarWindowStateListener listener = callbackCapture.getValue();
+ listener.onStatusBarWindowStateChanged(show ? WINDOW_STATE_SHOWING : WINDOW_STATE_HIDDEN);
+ return listener;
+ }
+
+ private void updateEntryAnimationsFinished() {
+ when(mDreamOverlayStateController.areEntryAnimationsFinished()).thenReturn(true);
+
+ verify(mDreamOverlayStateController).addCallback(mCallbackCaptor.capture());
+ final DreamOverlayStateController.Callback callback = mCallbackCaptor.getValue();
+ callback.onStateChanged();
+ }
+
+ private static class MockDreamOverlayStatusBarView extends DreamOverlayStatusBarView {
+ private int mVisibility = View.VISIBLE;
+
+ private MockDreamOverlayStatusBarView(Context context) {
+ super(context);
+ }
+
+ @Override
+ public void setVisibility(int visibility) {
+ mVisibility = visibility;
+ }
+
+ @Override
+ public int getVisibility() {
+ return mVisibility;
+ }
}
}
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 3b9e398141ee5..b477592f8fbcb 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
@@ -16,6 +16,7 @@
package com.android.systemui.dreams.complication;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -29,16 +30,18 @@ import androidx.lifecycle.Observer;
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.dreams.DreamOverlayStateController;
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.Collection;
+import java.util.Collections;
import java.util.HashSet;
@SmallTest
@@ -77,9 +80,20 @@ public class ComplicationHostViewControllerTest extends SysuiTestCase {
@Mock
ComplicationLayoutParams mComplicationLayoutParams;
+ @Mock
+ DreamOverlayStateController mDreamOverlayStateController;
+
+ @Captor
+ private ArgumentCaptor>> mObserverCaptor;
+
+ @Captor
+ private ArgumentCaptor mCallbackCaptor;
+
@Complication.Category
static final int COMPLICATION_CATEGORY = Complication.CATEGORY_SYSTEM;
+ private ComplicationHostViewController mController;
+
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
@@ -91,6 +105,15 @@ public class ComplicationHostViewControllerTest extends SysuiTestCase {
when(mViewHolder.getCategory()).thenReturn(COMPLICATION_CATEGORY);
when(mViewHolder.getLayoutParams()).thenReturn(mComplicationLayoutParams);
when(mComplicationView.getParent()).thenReturn(mComplicationHostView);
+
+ mController = new ComplicationHostViewController(
+ mComplicationHostView,
+ mLayoutEngine,
+ mDreamOverlayStateController,
+ mLifecycleOwner,
+ mViewModel);
+
+ mController.init();
}
/**
@@ -98,25 +121,12 @@ public class ComplicationHostViewControllerTest extends SysuiTestCase {
*/
@Test
public void testViewModelObservation() {
- final ArgumentCaptor>> observerArgumentCaptor =
- ArgumentCaptor.forClass(Observer.class);
- final ComplicationHostViewController controller = new ComplicationHostViewController(
- mComplicationHostView,
- mLayoutEngine,
- mLifecycleOwner,
- mViewModel);
-
- controller.init();
-
- verify(mComplicationViewModelLiveData).observe(eq(mLifecycleOwner),
- observerArgumentCaptor.capture());
-
final Observer> observer =
- observerArgumentCaptor.getValue();
+ captureComplicationViewModelsObserver();
- // Add complication and ensure it is added to the view.
+ // Add a complication and ensure it is added to the view.
final HashSet complications = new HashSet<>(
- Arrays.asList(mComplicationViewModel));
+ Collections.singletonList(mComplicationViewModel));
observer.onChanged(complications);
verify(mLayoutEngine).addComplication(eq(mComplicationId), eq(mComplicationView),
@@ -127,4 +137,48 @@ public class ComplicationHostViewControllerTest extends SysuiTestCase {
verify(mLayoutEngine).removeComplication(eq(mComplicationId));
}
+
+ @Test
+ public void testNewComplicationsBeforeEntryAnimationsFinishSetToInvisible() {
+ final Observer> observer =
+ captureComplicationViewModelsObserver();
+
+ // Add a complication before entry animations are finished.
+ final HashSet complications = new HashSet<>(
+ Collections.singletonList(mComplicationViewModel));
+ observer.onChanged(complications);
+
+ // The complication view should be set to invisible.
+ verify(mComplicationView).setVisibility(View.INVISIBLE);
+ }
+
+ @Test
+ public void testNewComplicationsAfterEntryAnimationsFinishNotSetToInvisible() {
+ final Observer> observer =
+ captureComplicationViewModelsObserver();
+
+ // Dream entry animations finished.
+ when(mDreamOverlayStateController.areEntryAnimationsFinished()).thenReturn(true);
+ final DreamOverlayStateController.Callback stateCallback = captureOverlayStateCallback();
+ stateCallback.onStateChanged();
+
+ // Add a complication after entry animations are finished.
+ final HashSet complications = new HashSet<>(
+ Collections.singletonList(mComplicationViewModel));
+ observer.onChanged(complications);
+
+ // The complication view should not be set to invisible.
+ verify(mComplicationView, never()).setVisibility(View.INVISIBLE);
+ }
+
+ private Observer> captureComplicationViewModelsObserver() {
+ verify(mComplicationViewModelLiveData).observe(eq(mLifecycleOwner),
+ mObserverCaptor.capture());
+ return mObserverCaptor.getValue();
+ }
+
+ private DreamOverlayStateController.Callback captureOverlayStateCallback() {
+ verify(mDreamOverlayStateController).addCallback(mCallbackCaptor.capture());
+ return mCallbackCaptor.getValue();
+ }
}