diff options
| author | 2022-10-27 00:45:55 +0000 | |
|---|---|---|
| committer | 2022-11-01 01:06:23 +0000 | |
| commit | 9ecc6277a9cf9eb6524940565ee500cd7e124823 (patch) | |
| tree | af2c9ffb9c93159f0d0a88d45999094c112945bc | |
| parent | c849d793a115813ff033d264b147e4ec469d1888 (diff) | |
Dream IN blur + complications fade-in animations.
This change implements the following dream IN animations
- un-blur dream content shortly after dream started fading in
- fade in top complications and status bar, and then bottom
complications separately
Test: verified on device
Test: atest ComplicationHostViewControllerTest
Test: atest DreamOverlayStateControllerTest
Test: atest DreamOverlayContainerViewControllerTest
Test: atest DreamOverlayStatusBarViewControllerTest
Test: atest DreamOverlayServiceTest
Bug: 222507937
Change-Id: Ia641bf42fe15103953981e43f06fe12ebb518d74
14 files changed, 523 insertions, 63 deletions
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 006b260434bc..9add32c6ee0a 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 93982cb2c5b9..a0b255a3bc05 100644 --- a/packages/SystemUI/res/values/config.xml +++ b/packages/SystemUI/res/values/config.xml @@ -743,6 +743,17 @@ <integer name="complicationRestoreMs">1000</integer> + <!-- Duration in milliseconds of the dream in un-blur animation. --> + <integer name="config_dreamOverlayInBlurDurationMs">249</integer> + <!-- Delay in milliseconds of the dream in un-blur animation. --> + <integer name="config_dreamOverlayInBlurDelayMs">133</integer> + <!-- Duration in milliseconds of the dream in complications fade-in animation. --> + <integer name="config_dreamOverlayInComplicationsDurationMs">282</integer> + <!-- Delay in milliseconds of the dream in top complications fade-in animation. --> + <integer name="config_dreamOverlayInTopComplicationsDelayMs">216</integer> + <!-- Delay in milliseconds of the dream in bottom complications fade-in animation. --> + <integer name="config_dreamOverlayInBottomComplicationsDelayMs">299</integer> + <!-- Icons that don't show in a collapsed non-keyguard statusbar --> <string-array name="config_collapsed_statusbar_icon_blocklist" translatable="false"> <item>@*android:string/status_bar_volume</item> 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 000000000000..d8dd6a21d4c1 --- /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 733a80dd7f69..d0d01846c3d9 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<DreamOve private final DreamOverlayStatusBarViewController mStatusBarViewController; private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager; private final BlurUtils mBlurUtils; + private final DreamOverlayAnimationsController mDreamOverlayAnimationsController; + private final DreamOverlayStateController mStateController; private final ComplicationHostViewController mComplicationHostViewController; @@ -134,12 +136,16 @@ public class DreamOverlayContainerViewController extends ViewController<DreamOve @Named(DreamOverlayModule.BURN_IN_PROTECTION_UPDATE_INTERVAL) long burnInProtectionUpdateInterval, @Named(DreamOverlayModule.MILLIS_UNTIL_FULL_JITTER) long millisUntilFullJitter, - BouncerCallbackInteractor bouncerCallbackInteractor) { + BouncerCallbackInteractor bouncerCallbackInteractor, + DreamOverlayAnimationsController animationsController, + DreamOverlayStateController stateController) { super(containerView); mDreamOverlayContentView = contentView; mStatusBarViewController = statusBarViewController; mStatusBarKeyguardViewManager = statusBarKeyguardViewManager; mBlurUtils = blurUtils; + mDreamOverlayAnimationsController = animationsController; + mStateController = stateController; mComplicationHostViewController = complicationHostViewController; mDreamOverlayMaxTranslationY = resources.getDimensionPixelSize( @@ -172,6 +178,11 @@ public class DreamOverlayContainerViewController extends ViewController<DreamOve bouncer.addBouncerExpansionCallback(mBouncerExpansionCallback); } mBouncerCallbackInteractor.addBouncerExpansionCallback(mBouncerExpansionCallback); + + // Start dream entry animations. Skip animations for low light clock. + if (!mStateController.isLowLightActive()) { + mDreamOverlayAnimationsController.startEntryAnimations(mView); + } } @Override @@ -182,6 +193,8 @@ public class DreamOverlayContainerViewController extends ViewController<DreamOve bouncer.removeBouncerExpansionCallback(mBouncerExpansionCallback); } mBouncerCallbackInteractor.removeBouncerExpansionCallback(mBouncerExpansionCallback); + + mDreamOverlayAnimationsController.cancelRunningEntryAnimations(); } View getContainerView() { diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java index d1b73685a3f3..6380fd51114c 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java @@ -257,6 +257,7 @@ public class DreamOverlayService extends android.service.dreams.DreamOverlayServ mStateController.setOverlayActive(false); mStateController.setLowLightActive(false); + mStateController.setEntryAnimationsFinished(false); mDreamOverlayContainerViewController = null; mDreamOverlayTouchMonitor = null; diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java index 72feaca59ace..e80d0beabf2b 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java @@ -51,6 +51,7 @@ public class DreamOverlayStateController implements public static final int STATE_DREAM_OVERLAY_ACTIVE = 1 << 0; public static final int STATE_LOW_LIGHT_ACTIVE = 1 << 1; + public static final int STATE_DREAM_ENTRY_ANIMATIONS_FINISHED = 1 << 2; private static final int OP_CLEAR_STATE = 1; private static final int OP_SET_STATE = 2; @@ -202,6 +203,14 @@ public class DreamOverlayStateController implements return containsState(STATE_LOW_LIGHT_ACTIVE); } + /** + * Returns whether the dream content and dream overlay entry animations are finished. + * @return {@code true} if animations are finished, {@code false} otherwise. + */ + public boolean areEntryAnimationsFinished() { + return containsState(STATE_DREAM_ENTRY_ANIMATIONS_FINISHED); + } + private boolean containsState(int state) { return (mState & state) != 0; } @@ -218,7 +227,7 @@ public class DreamOverlayStateController implements } if (existingState != mState) { - notifyCallbacks(callback -> callback.onStateChanged()); + notifyCallbacks(Callback::onStateChanged); } } @@ -239,6 +248,15 @@ public class DreamOverlayStateController implements } /** + * 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. */ @Complication.ComplicationType diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java index bb1c4303041a..d17fbe31c8d2 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<DreamOve private boolean mIsAttached; + // Whether dream entry animations are finished. + private boolean mEntryAnimationsFinished = false; + private final NetworkRequest mNetworkRequest = new NetworkRequest.Builder() .clearCapabilities() .addTransportType(NetworkCapabilities.TRANSPORT_WIFI).build(); @@ -109,7 +108,9 @@ public class DreamOverlayStatusBarViewController extends ViewController<DreamOve new DreamOverlayStateController.Callback() { @Override public void onStateChanged() { - updateLowLightState(); + mEntryAnimationsFinished = + mDreamOverlayStateController.areEntryAnimationsFinished(); + updateVisibility(); } }; @@ -195,7 +196,6 @@ public class DreamOverlayStatusBarViewController extends ViewController<DreamOve mStatusBarItemsProvider.addCallback(mStatusBarItemsProviderCallback); mDreamOverlayStateController.addCallback(mDreamOverlayStateCallback); - updateLowLightState(); mTouchInsetSession.addViewToTracking(mView); } @@ -216,6 +216,26 @@ public class DreamOverlayStatusBarViewController extends ViewController<DreamOve mIsAttached = false; } + /** + * Sets alpha of the dream overlay status bar. + * + * No-op if the dream overlay status bar should not be shown. + */ + protected void setAlpha(float alpha) { + updateVisibility(); + + if (mView.getVisibility() != View.VISIBLE) { + return; + } + + mView.setAlpha(alpha); + } + + private boolean shouldShowStatusBar() { + return !mDreamOverlayStateController.isLowLightActive() + && !mStatusBarWindowStateController.windowIsShowing(); + } + private void updateWifiUnavailableStatusIcon() { final NetworkCapabilities capabilities = mConnectivityManager.getNetworkCapabilities( @@ -235,13 +255,12 @@ public class DreamOverlayStatusBarViewController extends ViewController<DreamOve hasAlarm ? buildAlarmContentDescription(alarm) : null); } - private void updateLowLightState() { - int visibility = View.VISIBLE; - if (mDreamOverlayStateController.isLowLightActive() - || mStatusBarWindowStateController.windowIsShowing()) { - visibility = View.INVISIBLE; + private void updateVisibility() { + if (shouldShowStatusBar()) { + mView.setVisibility(View.VISIBLE); + } else { + mView.setVisibility(View.INVISIBLE); } - mView.setVisibility(visibility); } private String buildAlarmContentDescription(AlarmManager.AlarmClockInfo alarm) { @@ -298,21 +317,11 @@ public class DreamOverlayStatusBarViewController extends ViewController<DreamOve } private void onSystemStatusBarStateChanged(@StatusBarManager.WindowVisibleState int state) { - mMainExecutor.execute(() -> { - 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<StatusBarItem> 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 fd6cfc0700ad..100ccc35e638 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<ConstraintLay private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); private final ComplicationLayoutEngine mLayoutEngine; + private final DreamOverlayStateController mDreamOverlayStateController; private final LifecycleOwner mLifecycleOwner; private final ComplicationCollectionViewModel mComplicationCollectionViewModel; private final HashMap<ComplicationId, Complication.ViewHolder> 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<ConstraintLay final ComplicationId id = complication.getId(); final Complication.ViewHolder viewHolder = complication.getComplication() .createView(complication); + // 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); + } mComplications.put(id, viewHolder); if (viewHolder.getView().getParent() != null) { Log.e(TAG, "View for complication " 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 4fe1622d73a5..cb012fa42e94 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java @@ -47,6 +47,14 @@ public abstract class DreamOverlayModule { public static final String BURN_IN_PROTECTION_UPDATE_INTERVAL = "burn_in_protection_update_interval"; public static final String MILLIS_UNTIL_FULL_JITTER = "millis_until_full_jitter"; + public static final String DREAM_IN_BLUR_ANIMATION_DURATION = "dream_in_blur_anim_duration"; + public static final String DREAM_IN_BLUR_ANIMATION_DELAY = "dream_in_blur_anim_delay"; + public static final String DREAM_IN_COMPLICATIONS_ANIMATION_DURATION = + "dream_in_complications_anim_duration"; + public static final String DREAM_IN_TOP_COMPLICATIONS_ANIMATION_DELAY = + "dream_in_top_complications_anim_delay"; + public static final String DREAM_IN_BOTTOM_COMPLICATIONS_ANIMATION_DELAY = + "dream_in_bottom_complications_anim_delay"; /** */ @Provides @@ -114,6 +122,51 @@ public abstract class DreamOverlayModule { return resources.getInteger(R.integer.config_dreamOverlayMillisUntilFullJitter); } + /** + * Duration in milliseconds of the dream in un-blur animation. + */ + @Provides + @Named(DREAM_IN_BLUR_ANIMATION_DURATION) + static int providesDreamInBlurAnimationDuration(@Main Resources resources) { + return resources.getInteger(R.integer.config_dreamOverlayInBlurDurationMs); + } + + /** + * Delay in milliseconds of the dream in un-blur animation. + */ + @Provides + @Named(DREAM_IN_BLUR_ANIMATION_DELAY) + static int providesDreamInBlurAnimationDelay(@Main Resources resources) { + return resources.getInteger(R.integer.config_dreamOverlayInBlurDelayMs); + } + + /** + * Duration in milliseconds of the dream in complications fade-in animation. + */ + @Provides + @Named(DREAM_IN_COMPLICATIONS_ANIMATION_DURATION) + static int providesDreamInComplicationsAnimationDuration(@Main Resources resources) { + return resources.getInteger(R.integer.config_dreamOverlayInComplicationsDurationMs); + } + + /** + * Delay in milliseconds of the dream in top complications fade-in animation. + */ + @Provides + @Named(DREAM_IN_TOP_COMPLICATIONS_ANIMATION_DELAY) + static int providesDreamInTopComplicationsAnimationDelay(@Main Resources resources) { + return resources.getInteger(R.integer.config_dreamOverlayInTopComplicationsDelayMs); + } + + /** + * Delay in milliseconds of the dream in bottom complications fade-in animation. + */ + @Provides + @Named(DREAM_IN_BOTTOM_COMPLICATIONS_ANIMATION_DELAY) + static int providesDreamInBottomComplicationsAnimationDelay(@Main Resources resources) { + return resources.getInteger(R.integer.config_dreamOverlayInBottomComplicationsDelayMs); + } + @Provides @DreamOverlayComponent.DreamOverlayScope static LifecycleOwner providesLifecycleOwner(Lazy<LifecycleRegistry> 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 c5a7de410eb0..c234178f8af3 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 f370be190761..b7f694f85f15 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 d1d32a13589a..c21c7a2aacbe 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 aa021781296a..85c28190d77b 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<DreamOverlayStateController.Callback> 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<StatusBarWindowStateListener> - 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,29 +475,43 @@ public class DreamOverlayStatusBarViewControllerTest extends SysuiTestCase { mController.onViewAttached(); reset(mView); - final ArgumentCaptor<StatusBarWindowStateListener> - 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<StatusBarWindowStateListener> - 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(); when(mStatusBarItem.getView()).thenReturn(mStatusBarItemView); @@ -507,16 +529,75 @@ public class DreamOverlayStatusBarViewControllerTest extends SysuiTestCase { public void testLowLightHidesStatusBar() { when(mDreamOverlayStateController.isLowLightActive()).thenReturn(true); mController.onViewAttached(); + updateEntryAnimationsFinished(); + + final ArgumentCaptor<DreamOverlayStateController.Callback> callbackCapture = + ArgumentCaptor.forClass(DreamOverlayStateController.Callback.class); + verify(mDreamOverlayStateController).addCallback(callbackCapture.capture()); + callbackCapture.getValue().onStateChanged(); - verify(mView).setVisibility(View.INVISIBLE); + 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<DreamOverlayStateController.Callback> callbackCapture = ArgumentCaptor.forClass(DreamOverlayStateController.Callback.class); verify(mDreamOverlayStateController).addCallback(callbackCapture.capture()); callbackCapture.getValue().onStateChanged(); - verify(mView).setVisibility(View.VISIBLE); + assertThat(mView.getVisibility()).isEqualTo(View.VISIBLE); + } + + private StatusBarWindowStateListener updateStatusBarWindowState(boolean show) { + when(mStatusBarWindowStateController.windowIsShowing()).thenReturn(show); + final ArgumentCaptor<StatusBarWindowStateListener> + 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 3b9e398141ee..b477592f8fbc 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<Observer<Collection<ComplicationViewModel>>> mObserverCaptor; + + @Captor + private ArgumentCaptor<DreamOverlayStateController.Callback> mCallbackCaptor; + @Complication.Category static final int COMPLICATION_CATEGORY = Complication.CATEGORY_SYSTEM; + private ComplicationHostViewController mController; + @Before public void setup() { MockitoAnnotations.initMocks(this); @@ -91,32 +105,28 @@ public class ComplicationHostViewControllerTest extends SysuiTestCase { when(mViewHolder.getCategory()).thenReturn(COMPLICATION_CATEGORY); when(mViewHolder.getLayoutParams()).thenReturn(mComplicationLayoutParams); when(mComplicationView.getParent()).thenReturn(mComplicationHostView); - } - /** - * Ensures the lifecycle of complications is properly handled. - */ - @Test - public void testViewModelObservation() { - final ArgumentCaptor<Observer<Collection<ComplicationViewModel>>> observerArgumentCaptor = - ArgumentCaptor.forClass(Observer.class); - final ComplicationHostViewController controller = new ComplicationHostViewController( + mController = new ComplicationHostViewController( mComplicationHostView, mLayoutEngine, + mDreamOverlayStateController, mLifecycleOwner, mViewModel); - controller.init(); - - verify(mComplicationViewModelLiveData).observe(eq(mLifecycleOwner), - observerArgumentCaptor.capture()); + mController.init(); + } + /** + * Ensures the lifecycle of complications is properly handled. + */ + @Test + public void testViewModelObservation() { final Observer<Collection<ComplicationViewModel>> 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<ComplicationViewModel> 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<Collection<ComplicationViewModel>> observer = + captureComplicationViewModelsObserver(); + + // Add a complication before entry animations are finished. + final HashSet<ComplicationViewModel> 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<Collection<ComplicationViewModel>> 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<ComplicationViewModel> 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<Collection<ComplicationViewModel>> captureComplicationViewModelsObserver() { + verify(mComplicationViewModelLiveData).observe(eq(mLifecycleOwner), + mObserverCaptor.capture()); + return mObserverCaptor.getValue(); + } + + private DreamOverlayStateController.Callback captureOverlayStateCallback() { + verify(mDreamOverlayStateController).addCallback(mCallbackCaptor.capture()); + return mCallbackCaptor.getValue(); + } } |