diff options
23 files changed, 704 insertions, 171 deletions
diff --git a/core/java/android/service/dreams/DreamOverlayService.java b/core/java/android/service/dreams/DreamOverlayService.java index aa45c20a8e13..6e8198ba0cd1 100644 --- a/core/java/android/service/dreams/DreamOverlayService.java +++ b/core/java/android/service/dreams/DreamOverlayService.java @@ -49,6 +49,17 @@ public abstract class DreamOverlayService extends Service { mShowComplications = shouldShowComplications; onStartDream(layoutParams); } + + @Override + public void wakeUp() { + onWakeUp(() -> { + try { + mDreamOverlayCallback.onWakeUpComplete(); + } catch (RemoteException e) { + Log.e(TAG, "Could not notify dream of wakeUp:" + e); + } + }); + } }; IDreamOverlayCallback mDreamOverlayCallback; @@ -71,6 +82,17 @@ public abstract class DreamOverlayService extends Service { public abstract void onStartDream(@NonNull WindowManager.LayoutParams layoutParams); /** + * This method is overridden by implementations to handle when the dream has been requested + * to wakeup. This allows any overlay animations to run. + * + * @param onCompleteCallback The callback to trigger to notify the dream service that the + * overlay has completed waking up. + * @hide + */ + public void onWakeUp(@NonNull Runnable onCompleteCallback) { + } + + /** * This method is invoked to request the dream exit. */ public final void requestExit() { diff --git a/core/java/android/service/dreams/DreamService.java b/core/java/android/service/dreams/DreamService.java index 32bdf7962273..cff20391103b 100644 --- a/core/java/android/service/dreams/DreamService.java +++ b/core/java/android/service/dreams/DreamService.java @@ -312,7 +312,14 @@ public class DreamService extends Service implements Window.Callback { @Override public void onExitRequested() { // Simply finish dream when exit is requested. - finish(); + mHandler.post(() -> finish()); + } + + @Override + public void onWakeUpComplete() { + // Finish the dream once overlay animations are complete. Execute on handler since + // this is coming in on the overlay binder. + mHandler.post(() -> finish()); } }; @@ -975,7 +982,18 @@ public class DreamService extends Service implements Window.Callback { * </p> */ public void onWakeUp() { - finish(); + if (mOverlayConnection != null) { + mOverlayConnection.addConsumer(overlay -> { + try { + overlay.wakeUp(); + } catch (RemoteException e) { + Slog.e(TAG, "Error waking the overlay service", e); + finish(); + } + }); + } else { + finish(); + } } /** {@inheritDoc} */ diff --git a/core/java/android/service/dreams/IDreamOverlay.aidl b/core/java/android/service/dreams/IDreamOverlay.aidl index 05ebbfe98c9f..7aeceb2ce538 100644 --- a/core/java/android/service/dreams/IDreamOverlay.aidl +++ b/core/java/android/service/dreams/IDreamOverlay.aidl @@ -38,4 +38,7 @@ interface IDreamOverlay { */ void startDream(in LayoutParams params, in IDreamOverlayCallback callback, in String dreamComponent, in boolean shouldShowComplications); + + /** Called when the dream is waking, to do any exit animations */ + void wakeUp(); } diff --git a/core/java/android/service/dreams/IDreamOverlayCallback.aidl b/core/java/android/service/dreams/IDreamOverlayCallback.aidl index ec76a334d5b2..4ad63f1317d1 100644 --- a/core/java/android/service/dreams/IDreamOverlayCallback.aidl +++ b/core/java/android/service/dreams/IDreamOverlayCallback.aidl @@ -28,4 +28,7 @@ interface IDreamOverlayCallback { * Invoked to request the dream exit. */ void onExitRequested(); + + /** Invoked when the dream overlay wakeUp animation is complete. */ + void onWakeUpComplete(); }
\ No newline at end of file diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 2a9e60a46972..b9bcf559f63b 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -2485,7 +2485,7 @@ <!-- The duration in milliseconds of the dream opening animation. --> <integer name="config_dreamOpenAnimationDuration">250</integer> <!-- The duration in milliseconds of the dream closing animation. --> - <integer name="config_dreamCloseAnimationDuration">100</integer> + <integer name="config_dreamCloseAnimationDuration">300</integer> <!-- Whether to dismiss the active dream when an activity is started. Doesn't apply to assistant activities (ACTIVITY_TYPE_ASSISTANT) --> diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml index 55d637916d0c..f4d802bf745e 100644 --- a/packages/SystemUI/res/values/config.xml +++ b/packages/SystemUI/res/values/config.xml @@ -743,12 +743,35 @@ <!-- How long in milliseconds before full burn-in protection is achieved. --> <integer name="config_dreamOverlayMillisUntilFullJitter">240000</integer> + <!-- The duration in milliseconds of the y-translation animation when waking up from + the dream --> + <integer name="config_dreamOverlayOutTranslationYDurationMs">333</integer> + <!-- The delay in milliseconds of the y-translation animation when waking up from + the dream for the complications at the bottom of the screen --> + <integer name="config_dreamOverlayOutTranslationYDelayBottomMs">33</integer> + <!-- The delay in milliseconds of the y-translation animation when waking up from + the dream for the complications at the top of the screen --> + <integer name="config_dreamOverlayOutTranslationYDelayTopMs">117</integer> + <!-- The duration in milliseconds of the alpha animation when waking up from the dream --> + <integer name="config_dreamOverlayOutAlphaDurationMs">200</integer> + <!-- The delay in milliseconds of the alpha animation when waking up from the dream for the + complications at the top of the screen --> + <integer name="config_dreamOverlayOutAlphaDelayTopMs">217</integer> + <!-- The delay in milliseconds of the alpha animation when waking up from the dream for the + complications at the bottom of the screen --> + <integer name="config_dreamOverlayOutAlphaDelayBottomMs">133</integer> + <!-- The duration in milliseconds of the blur animation when waking up from + the dream --> + <integer name="config_dreamOverlayOutBlurDurationMs">250</integer> + <integer name="complicationFadeOutMs">500</integer> <integer name="complicationFadeInMs">500</integer> <integer name="complicationRestoreMs">1000</integer> + <integer name="complicationFadeOutDelayMs">200</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. --> @@ -780,27 +803,6 @@ <item>com.android.systemui</item> </string-array> - <!-- The thresholds which determine the color used by the AQI dream overlay. - NOTE: This must always be kept sorted from low to high --> - <integer-array name="config_dreamAqiThresholds"> - <item>-1</item> - <item>50</item> - <item>100</item> - <item>150</item> - <item>200</item> - <item>300</item> - </integer-array> - - <!-- The color values which correspond to the thresholds above --> - <integer-array name="config_dreamAqiColorValues"> - <item>@color/dream_overlay_aqi_good</item> - <item>@color/dream_overlay_aqi_moderate</item> - <item>@color/dream_overlay_aqi_unhealthy_sensitive</item> - <item>@color/dream_overlay_aqi_unhealthy</item> - <item>@color/dream_overlay_aqi_very_unhealthy</item> - <item>@color/dream_overlay_aqi_hazardous</item> - </integer-array> - <!-- Whether the device should display hotspot UI. If true, UI will display only when tethering is available. If false, UI will never show regardless of tethering availability" --> <bool name="config_show_wifi_tethering">true</bool> diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index c78d36dd3685..7cda9d70ea49 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -1534,6 +1534,7 @@ <dimen name="dream_overlay_complication_margin">0dp</dimen> <dimen name="dream_overlay_y_offset">80dp</dimen> + <dimen name="dream_overlay_exit_y_offset">40dp</dimen> <dimen name="dream_aqi_badge_corner_radius">28dp</dimen> <dimen name="dream_aqi_badge_padding_vertical">6dp</dimen> diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt index d8dd6a21d4c1..0087c8439370 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt +++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt @@ -17,17 +17,19 @@ 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 android.view.animation.Interpolator +import androidx.annotation.FloatRange 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.complication.ComplicationLayoutParams.Position import com.android.systemui.dreams.dagger.DreamOverlayModule import com.android.systemui.statusbar.BlurUtils -import java.util.function.Consumer +import com.android.systemui.statusbar.CrossFadeHelper import javax.inject.Inject import javax.inject.Named @@ -40,108 +42,239 @@ constructor( 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, + private val mDreamInBlurAnimDurationMs: Long, + @Named(DreamOverlayModule.DREAM_IN_BLUR_ANIMATION_DELAY) + private val mDreamInBlurAnimDelayMs: Long, @Named(DreamOverlayModule.DREAM_IN_COMPLICATIONS_ANIMATION_DURATION) - private val mDreamInComplicationsAnimDuration: Int, + private val mDreamInComplicationsAnimDurationMs: Long, @Named(DreamOverlayModule.DREAM_IN_TOP_COMPLICATIONS_ANIMATION_DELAY) - private val mDreamInTopComplicationsAnimDelay: Int, + private val mDreamInTopComplicationsAnimDelayMs: Long, @Named(DreamOverlayModule.DREAM_IN_BOTTOM_COMPLICATIONS_ANIMATION_DELAY) - private val mDreamInBottomComplicationsAnimDelay: Int + private val mDreamInBottomComplicationsAnimDelayMs: Long, + @Named(DreamOverlayModule.DREAM_OUT_TRANSLATION_Y_DISTANCE) + private val mDreamOutTranslationYDistance: Int, + @Named(DreamOverlayModule.DREAM_OUT_TRANSLATION_Y_DURATION) + private val mDreamOutTranslationYDurationMs: Long, + @Named(DreamOverlayModule.DREAM_OUT_TRANSLATION_Y_DELAY_BOTTOM) + private val mDreamOutTranslationYDelayBottomMs: Long, + @Named(DreamOverlayModule.DREAM_OUT_TRANSLATION_Y_DELAY_TOP) + private val mDreamOutTranslationYDelayTopMs: Long, + @Named(DreamOverlayModule.DREAM_OUT_ALPHA_DURATION) private val mDreamOutAlphaDurationMs: Long, + @Named(DreamOverlayModule.DREAM_OUT_ALPHA_DELAY_BOTTOM) + private val mDreamOutAlphaDelayBottomMs: Long, + @Named(DreamOverlayModule.DREAM_OUT_ALPHA_DELAY_TOP) private val mDreamOutAlphaDelayTopMs: Long, + @Named(DreamOverlayModule.DREAM_OUT_BLUR_DURATION) private val mDreamOutBlurDurationMs: Long ) { - var mEntryAnimations: AnimatorSet? = null + private var mAnimator: Animator? = null + + /** + * Store the current alphas at the various positions. This is so that we may resume an animation + * at the current alpha. + */ + private var mCurrentAlphaAtPosition = mutableMapOf<Int, Float>() + + @FloatRange(from = 0.0, to = 1.0) private var mBlurProgress: Float = 0f /** 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() - } + @JvmOverloads + fun startEntryAnimations(view: View, animatorBuilder: () -> AnimatorSet = { AnimatorSet() }) { + cancelAnimations() + + mAnimator = + animatorBuilder().apply { + playTogether( + blurAnimator( + view = view, + from = 1f, + to = 0f, + durationMs = mDreamInBlurAnimDurationMs, + delayMs = mDreamInBlurAnimDelayMs + ), + alphaAnimator( + from = 0f, + to = 1f, + durationMs = mDreamInComplicationsAnimDurationMs, + delayMs = mDreamInTopComplicationsAnimDelayMs, + position = ComplicationLayoutParams.POSITION_TOP + ), + alphaAnimator( + from = 0f, + to = 1f, + durationMs = mDreamInComplicationsAnimDurationMs, + delayMs = mDreamInBottomComplicationsAnimDelayMs, + position = ComplicationLayoutParams.POSITION_BOTTOM + ) + ) + doOnEnd { + mAnimator = null + mOverlayStateController.setEntryAnimationsFinished(true) + } + start() + } + } + + /** Starts the dream content and dream overlay exit animations. */ + @JvmOverloads + fun startExitAnimations( + view: View, + doneCallback: () -> Unit, + animatorBuilder: () -> AnimatorSet = { AnimatorSet() } + ) { + cancelAnimations() + + mAnimator = + animatorBuilder().apply { + playTogether( + blurAnimator( + view = view, + // Start the blurring wherever the entry animation ended, in + // case it was cancelled early. + from = mBlurProgress, + to = 1f, + durationMs = mDreamOutBlurDurationMs + ), + translationYAnimator( + from = 0f, + to = mDreamOutTranslationYDistance.toFloat(), + durationMs = mDreamOutTranslationYDurationMs, + delayMs = mDreamOutTranslationYDelayBottomMs, + position = ComplicationLayoutParams.POSITION_BOTTOM, + animInterpolator = Interpolators.EMPHASIZED_ACCELERATE + ), + translationYAnimator( + from = 0f, + to = mDreamOutTranslationYDistance.toFloat(), + durationMs = mDreamOutTranslationYDurationMs, + delayMs = mDreamOutTranslationYDelayTopMs, + position = ComplicationLayoutParams.POSITION_TOP, + animInterpolator = Interpolators.EMPHASIZED_ACCELERATE + ), + alphaAnimator( + from = + mCurrentAlphaAtPosition.getOrDefault( + key = ComplicationLayoutParams.POSITION_BOTTOM, + defaultValue = 1f + ), + to = 0f, + durationMs = mDreamOutAlphaDurationMs, + delayMs = mDreamOutAlphaDelayBottomMs, + position = ComplicationLayoutParams.POSITION_BOTTOM + ), + alphaAnimator( + from = + mCurrentAlphaAtPosition.getOrDefault( + key = ComplicationLayoutParams.POSITION_TOP, + defaultValue = 1f + ), + to = 0f, + durationMs = mDreamOutAlphaDurationMs, + delayMs = mDreamOutAlphaDelayTopMs, + position = ComplicationLayoutParams.POSITION_TOP + ) + ) + doOnEnd { + mAnimator = null + mOverlayStateController.setExitAnimationsRunning(false) + doneCallback() + } + start() + } + mOverlayStateController.setExitAnimationsRunning(true) } /** Cancels the dream content and dream overlay animations, if they're currently running. */ - fun cancelRunningEntryAnimations() { - if (mEntryAnimations?.isRunning == true) { - mEntryAnimations?.cancel() - } - mEntryAnimations = null + fun cancelAnimations() { + mAnimator = + mAnimator?.let { + it.cancel() + null + } } - private fun buildDreamInBlurAnimator(view: View): Animator { - return ValueAnimator.ofFloat(1f, 0f).apply { - duration = mDreamInBlurAnimDuration.toLong() - startDelay = mDreamInBlurAnimDelay.toLong() + private fun blurAnimator( + view: View, + from: Float, + to: Float, + durationMs: Long, + delayMs: Long = 0 + ): Animator { + return ValueAnimator.ofFloat(from, to).apply { + duration = durationMs + startDelay = delayMs interpolator = Interpolators.LINEAR addUpdateListener { animator: ValueAnimator -> + mBlurProgress = animator.animatedValue as Float mBlurUtils.applyBlur( - view.viewRootImpl, - mBlurUtils.blurRadiusOfRatio(animator.animatedValue as Float).toInt(), - false /*opaque*/ + viewRootImpl = view.viewRootImpl, + radius = mBlurUtils.blurRadiusOfRatio(mBlurProgress).toInt(), + opaque = false ) } } } - private fun buildDreamInTopComplicationsAnimator(): Animator { - return ValueAnimator.ofFloat(0f, 1f).apply { - duration = mDreamInComplicationsAnimDuration.toLong() - startDelay = mDreamInTopComplicationsAnimDelay.toLong() + private fun alphaAnimator( + from: Float, + to: Float, + durationMs: Long, + delayMs: Long, + @Position position: Int + ): Animator { + return ValueAnimator.ofFloat(from, to).apply { + duration = durationMs + startDelay = delayMs interpolator = Interpolators.LINEAR addUpdateListener { va: ValueAnimator -> - setTopElementsAlpha(va.animatedValue as Float) + setElementsAlphaAtPosition( + alpha = va.animatedValue as Float, + position = position, + fadingOut = to < from + ) } } } - private fun buildDreamInBottomComplicationsAnimator(): Animator { - return ValueAnimator.ofFloat(0f, 1f).apply { - duration = mDreamInComplicationsAnimDuration.toLong() - startDelay = mDreamInBottomComplicationsAnimDelay.toLong() - interpolator = Interpolators.LINEAR + private fun translationYAnimator( + from: Float, + to: Float, + durationMs: Long, + delayMs: Long, + @Position position: Int, + animInterpolator: Interpolator + ): Animator { + return ValueAnimator.ofFloat(from, to).apply { + duration = durationMs + startDelay = delayMs + interpolator = animInterpolator addUpdateListener { va: ValueAnimator -> - setBottomElementsAlpha(va.animatedValue as Float) + setElementsTranslationYAtPosition(va.animatedValue as Float, position) } - 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) }) + /** Sets alpha of complications at the specified position. */ + private fun setElementsAlphaAtPosition(alpha: Float, position: Int, fadingOut: Boolean) { + mCurrentAlphaAtPosition[position] = alpha + mComplicationHostViewController.getViewsAtPosition(position).forEach { view -> + if (fadingOut) { + CrossFadeHelper.fadeOut(view, 1 - alpha, /* remap= */ false) + } else { + CrossFadeHelper.fadeIn(view, alpha, /* remap= */ false) + } + } + if (position == ComplicationLayoutParams.POSITION_TOP) { + mStatusBarViewController.setFadeAmount(alpha, fadingOut) + } } - private fun setAlphaAndEnsureVisible(view: View, alpha: Float) { - if (alpha > 0 && view.visibility != View.VISIBLE) { - view.visibility = View.VISIBLE + /** Sets y translation of complications at the specified position. */ + private fun setElementsTranslationYAtPosition(translationY: Float, position: Int) { + mComplicationHostViewController.getViewsAtPosition(position).forEach { v -> + v.translationY = translationY + } + if (position == ComplicationLayoutParams.POSITION_TOP) { + mStatusBarViewController.setTranslationY(translationY) } - - 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 5c6d24813570..9d7ad305c7e5 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java @@ -29,6 +29,8 @@ import android.util.MathUtils; import android.view.View; import android.view.ViewGroup; +import androidx.annotation.NonNull; + import com.android.systemui.R; import com.android.systemui.animation.Interpolators; import com.android.systemui.dagger.qualifiers.Main; @@ -42,6 +44,7 @@ import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; import com.android.systemui.util.ViewController; import java.util.Arrays; +import java.util.concurrent.Executor; import javax.inject.Inject; import javax.inject.Named; @@ -194,7 +197,7 @@ public class DreamOverlayContainerViewController extends ViewController<DreamOve } mPrimaryBouncerCallbackInteractor.removeBouncerExpansionCallback(mBouncerExpansionCallback); - mDreamOverlayAnimationsController.cancelRunningEntryAnimations(); + mDreamOverlayAnimationsController.cancelAnimations(); } View getContainerView() { @@ -251,4 +254,17 @@ public class DreamOverlayContainerViewController extends ViewController<DreamOve : aboutToShowBouncerProgress(expansion + 0.03f)); return MathUtils.lerp(-mDreamOverlayMaxTranslationY, 0, fraction); } + + /** + * Handle the dream waking up and run any necessary animations. + * + * @param onAnimationEnd Callback to trigger once animations are finished. + * @param callbackExecutor Executor to execute the callback on. + */ + public void wakeUp(@NonNull Runnable onAnimationEnd, @NonNull Executor callbackExecutor) { + mDreamOverlayAnimationsController.startExitAnimations(mView, () -> { + callbackExecutor.execute(onAnimationEnd); + return null; + }); + } } diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java index 8542412f82f8..e76d5b30d3e6 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java @@ -213,6 +213,15 @@ public class DreamOverlayService extends android.service.dreams.DreamOverlayServ mLifecycleRegistry.setCurrentState(state); } + @Override + public void onWakeUp(@NonNull Runnable onCompletedCallback) { + mExecutor.execute(() -> { + if (mDreamOverlayContainerViewController != null) { + mDreamOverlayContainerViewController.wakeUp(onCompletedCallback, mExecutor); + } + }); + } + /** * Inserts {@link Window} to host the dream overlay into the dream's parent window. Must be * called from the main executing thread. The window attributes closely mirror those that are diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java index e80d0beabf2b..5f942b6fb834 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java @@ -52,6 +52,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; + public static final int STATE_DREAM_EXIT_ANIMATIONS_RUNNING = 1 << 3; private static final int OP_CLEAR_STATE = 1; private static final int OP_SET_STATE = 2; @@ -211,6 +212,14 @@ public class DreamOverlayStateController implements return containsState(STATE_DREAM_ENTRY_ANIMATIONS_FINISHED); } + /** + * Returns whether the dream content and dream overlay exit animations are running. + * @return {@code true} if animations are running, {@code false} otherwise. + */ + public boolean areExitAnimationsRunning() { + return containsState(STATE_DREAM_EXIT_ANIMATIONS_RUNNING); + } + private boolean containsState(int state) { return (mState & state) != 0; } @@ -257,6 +266,15 @@ public class DreamOverlayStateController implements } /** + * Sets whether dream content and dream overlay exit animations are running. + * @param running {@code true} if exit animations are running, {@code false} otherwise. + */ + public void setExitAnimationsRunning(boolean running) { + modifyState(running ? OP_SET_STATE : OP_CLEAR_STATE, + STATE_DREAM_EXIT_ANIMATIONS_RUNNING); + } + + /** * 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 d17fbe31c8d2..f1bb156199ef 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java @@ -37,6 +37,7 @@ import com.android.systemui.R; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.dreams.DreamOverlayStatusBarItemsProvider.StatusBarItem; import com.android.systemui.dreams.dagger.DreamOverlayComponent; +import com.android.systemui.statusbar.CrossFadeHelper; import com.android.systemui.statusbar.policy.IndividualSensorPrivacyController; import com.android.systemui.statusbar.policy.NextAlarmController; import com.android.systemui.statusbar.policy.ZenModeController; @@ -217,18 +218,29 @@ public class DreamOverlayStatusBarViewController extends ViewController<DreamOve } /** - * Sets alpha of the dream overlay status bar. + * Sets fade of the dream overlay status bar. * * No-op if the dream overlay status bar should not be shown. */ - protected void setAlpha(float alpha) { + protected void setFadeAmount(float fadeAmount, boolean fadingOut) { updateVisibility(); if (mView.getVisibility() != View.VISIBLE) { return; } - mView.setAlpha(alpha); + if (fadingOut) { + CrossFadeHelper.fadeOut(mView, 1 - fadeAmount, /* remap= */ false); + } else { + CrossFadeHelper.fadeIn(mView, fadeAmount, /* remap= */ false); + } + } + + /** + * Sets the y translation of the dream overlay status bar. + */ + public void setTranslationY(float translationY) { + mView.setTranslationY(translationY); } private boolean shouldShowStatusBar() { diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/Complication.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/Complication.java index 41f557850f88..b07efdfff5f2 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/complication/Complication.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/Complication.java @@ -197,11 +197,11 @@ public interface Complication { */ interface VisibilityController { /** - * Called to set the visibility of all shown and future complications. + * Called to set the visibility of all shown and future complications. Changes in visibility + * will always be animated. * @param visibility The desired future visibility. - * @param animate whether the change should be animated. */ - void setVisibility(@View.Visibility int visibility, boolean animate); + void setVisibility(@View.Visibility int visibility); } /** diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutEngine.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutEngine.java index 440dcbc18a12..48159aed524e 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutEngine.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutEngine.java @@ -21,12 +21,9 @@ import static com.android.systemui.dreams.complication.dagger.ComplicationHostVi import static com.android.systemui.dreams.complication.dagger.ComplicationHostViewModule.COMPLICATION_MARGIN_DEFAULT; import static com.android.systemui.dreams.complication.dagger.ComplicationHostViewModule.SCOPED_COMPLICATIONS_LAYOUT; -import android.animation.Animator; -import android.animation.AnimatorListenerAdapter; import android.util.Log; import android.view.View; import android.view.ViewGroup; -import android.view.ViewPropertyAnimator; import androidx.constraintlayout.widget.ConstraintLayout; import androidx.constraintlayout.widget.Constraints; @@ -34,6 +31,7 @@ import androidx.constraintlayout.widget.Constraints; import com.android.systemui.R; import com.android.systemui.dreams.complication.ComplicationLayoutParams.Position; import com.android.systemui.dreams.dagger.DreamOverlayComponent; +import com.android.systemui.statusbar.CrossFadeHelper; import com.android.systemui.touch.TouchInsetManager; import java.util.ArrayList; @@ -481,7 +479,6 @@ public class ComplicationLayoutEngine implements Complication.VisibilityControll private final TouchInsetManager.TouchInsetSession mSession; private final int mFadeInDuration; private final int mFadeOutDuration; - private ViewPropertyAnimator mViewPropertyAnimator; /** */ @Inject @@ -498,26 +495,16 @@ public class ComplicationLayoutEngine implements Complication.VisibilityControll } @Override - public void setVisibility(int visibility, boolean animate) { - final boolean appearing = visibility == View.VISIBLE; - - if (mViewPropertyAnimator != null) { - mViewPropertyAnimator.cancel(); - } - - if (appearing) { - mLayout.setVisibility(View.VISIBLE); + public void setVisibility(int visibility) { + if (visibility == View.VISIBLE) { + CrossFadeHelper.fadeIn(mLayout, mFadeInDuration, /* delay= */ 0); + } else { + CrossFadeHelper.fadeOut( + mLayout, + mFadeOutDuration, + /* delay= */ 0, + /* endRunnable= */ null); } - - mViewPropertyAnimator = mLayout.animate() - .alpha(appearing ? 1f : 0f) - .setDuration(appearing ? mFadeInDuration : mFadeOutDuration) - .setListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - mLayout.setVisibility(visibility); - } - }); } /** diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutParams.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutParams.java index 2b32d349dd67..4fae68d57ee8 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutParams.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutParams.java @@ -38,7 +38,7 @@ public class ComplicationLayoutParams extends ViewGroup.LayoutParams { POSITION_START, }) - @interface Position {} + public @interface Position {} /** Align view with the top of parent or bottom of preceding {@link Complication}. */ public static final int POSITION_TOP = 1 << 0; /** Align view with the bottom of parent or top of preceding {@link Complication}. */ diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/ComplicationHostViewModule.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/ComplicationHostViewModule.java index c9fecc96c1b5..09cc7c51ccf4 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/ComplicationHostViewModule.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/ComplicationHostViewModule.java @@ -41,6 +41,7 @@ public abstract class ComplicationHostViewModule { public static final String COMPLICATIONS_FADE_OUT_DURATION = "complications_fade_out_duration"; public static final String COMPLICATIONS_FADE_IN_DURATION = "complications_fade_in_duration"; public static final String COMPLICATIONS_RESTORE_TIMEOUT = "complication_restore_timeout"; + public static final String COMPLICATIONS_FADE_OUT_DELAY = "complication_fade_out_delay"; /** * Generates a {@link ConstraintLayout}, which can host @@ -75,6 +76,16 @@ public abstract class ComplicationHostViewModule { } /** + * Provides the delay to wait for before fading out complications. + */ + @Provides + @Named(COMPLICATIONS_FADE_OUT_DELAY) + @DreamOverlayComponent.DreamOverlayScope + static int providesComplicationsFadeOutDelay(@Main Resources resources) { + return resources.getInteger(R.integer.complicationFadeOutDelayMs); + } + + /** * Provides the fade in duration for complications. */ @Provides 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 cb012fa42e94..ed0e1d97e40a 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java @@ -55,6 +55,22 @@ public abstract class DreamOverlayModule { "dream_in_top_complications_anim_delay"; public static final String DREAM_IN_BOTTOM_COMPLICATIONS_ANIMATION_DELAY = "dream_in_bottom_complications_anim_delay"; + public static final String DREAM_OUT_TRANSLATION_Y_DISTANCE = + "dream_out_complications_translation_y"; + public static final String DREAM_OUT_TRANSLATION_Y_DURATION = + "dream_out_complications_translation_y_duration"; + public static final String DREAM_OUT_TRANSLATION_Y_DELAY_BOTTOM = + "dream_out_complications_translation_y_delay_bottom"; + public static final String DREAM_OUT_TRANSLATION_Y_DELAY_TOP = + "dream_out_complications_translation_y_delay_top"; + public static final String DREAM_OUT_ALPHA_DURATION = + "dream_out_complications_alpha_duration"; + public static final String DREAM_OUT_ALPHA_DELAY_BOTTOM = + "dream_out_complications_alpha_delay_bottom"; + public static final String DREAM_OUT_ALPHA_DELAY_TOP = + "dream_out_complications_alpha_delay_top"; + public static final String DREAM_OUT_BLUR_DURATION = + "dream_out_blur_duration"; /** */ @Provides @@ -127,8 +143,8 @@ public abstract class DreamOverlayModule { */ @Provides @Named(DREAM_IN_BLUR_ANIMATION_DURATION) - static int providesDreamInBlurAnimationDuration(@Main Resources resources) { - return resources.getInteger(R.integer.config_dreamOverlayInBlurDurationMs); + static long providesDreamInBlurAnimationDuration(@Main Resources resources) { + return (long) resources.getInteger(R.integer.config_dreamOverlayInBlurDurationMs); } /** @@ -136,8 +152,8 @@ public abstract class DreamOverlayModule { */ @Provides @Named(DREAM_IN_BLUR_ANIMATION_DELAY) - static int providesDreamInBlurAnimationDelay(@Main Resources resources) { - return resources.getInteger(R.integer.config_dreamOverlayInBlurDelayMs); + static long providesDreamInBlurAnimationDelay(@Main Resources resources) { + return (long) resources.getInteger(R.integer.config_dreamOverlayInBlurDelayMs); } /** @@ -145,8 +161,8 @@ public abstract class DreamOverlayModule { */ @Provides @Named(DREAM_IN_COMPLICATIONS_ANIMATION_DURATION) - static int providesDreamInComplicationsAnimationDuration(@Main Resources resources) { - return resources.getInteger(R.integer.config_dreamOverlayInComplicationsDurationMs); + static long providesDreamInComplicationsAnimationDuration(@Main Resources resources) { + return (long) resources.getInteger(R.integer.config_dreamOverlayInComplicationsDurationMs); } /** @@ -154,8 +170,8 @@ public abstract class DreamOverlayModule { */ @Provides @Named(DREAM_IN_TOP_COMPLICATIONS_ANIMATION_DELAY) - static int providesDreamInTopComplicationsAnimationDelay(@Main Resources resources) { - return resources.getInteger(R.integer.config_dreamOverlayInTopComplicationsDelayMs); + static long providesDreamInTopComplicationsAnimationDelay(@Main Resources resources) { + return (long) resources.getInteger(R.integer.config_dreamOverlayInTopComplicationsDelayMs); } /** @@ -163,8 +179,69 @@ public abstract class DreamOverlayModule { */ @Provides @Named(DREAM_IN_BOTTOM_COMPLICATIONS_ANIMATION_DELAY) - static int providesDreamInBottomComplicationsAnimationDelay(@Main Resources resources) { - return resources.getInteger(R.integer.config_dreamOverlayInBottomComplicationsDelayMs); + static long providesDreamInBottomComplicationsAnimationDelay(@Main Resources resources) { + return (long) resources.getInteger( + R.integer.config_dreamOverlayInBottomComplicationsDelayMs); + } + + /** + * Provides the number of pixels to translate complications when waking up from dream. + */ + @Provides + @Named(DREAM_OUT_TRANSLATION_Y_DISTANCE) + @DreamOverlayComponent.DreamOverlayScope + static int providesDreamOutComplicationsTranslationY(@Main Resources resources) { + return resources.getDimensionPixelSize(R.dimen.dream_overlay_exit_y_offset); + } + + @Provides + @Named(DREAM_OUT_TRANSLATION_Y_DURATION) + @DreamOverlayComponent.DreamOverlayScope + static long providesDreamOutComplicationsTranslationYDuration(@Main Resources resources) { + return (long) resources.getInteger(R.integer.config_dreamOverlayOutTranslationYDurationMs); + } + + @Provides + @Named(DREAM_OUT_TRANSLATION_Y_DELAY_BOTTOM) + @DreamOverlayComponent.DreamOverlayScope + static long providesDreamOutComplicationsTranslationYDelayBottom(@Main Resources resources) { + return (long) resources.getInteger( + R.integer.config_dreamOverlayOutTranslationYDelayBottomMs); + } + + @Provides + @Named(DREAM_OUT_TRANSLATION_Y_DELAY_TOP) + @DreamOverlayComponent.DreamOverlayScope + static long providesDreamOutComplicationsTranslationYDelayTop(@Main Resources resources) { + return (long) resources.getInteger(R.integer.config_dreamOverlayOutTranslationYDelayTopMs); + } + + @Provides + @Named(DREAM_OUT_ALPHA_DURATION) + @DreamOverlayComponent.DreamOverlayScope + static long providesDreamOutComplicationsAlphaDuration(@Main Resources resources) { + return (long) resources.getInteger(R.integer.config_dreamOverlayOutAlphaDurationMs); + } + + @Provides + @Named(DREAM_OUT_ALPHA_DELAY_BOTTOM) + @DreamOverlayComponent.DreamOverlayScope + static long providesDreamOutComplicationsAlphaDelayBottom(@Main Resources resources) { + return (long) resources.getInteger(R.integer.config_dreamOverlayOutAlphaDelayBottomMs); + } + + @Provides + @Named(DREAM_OUT_ALPHA_DELAY_TOP) + @DreamOverlayComponent.DreamOverlayScope + static long providesDreamOutComplicationsAlphaDelayTop(@Main Resources resources) { + return (long) resources.getInteger(R.integer.config_dreamOverlayOutAlphaDelayTopMs); + } + + @Provides + @Named(DREAM_OUT_BLUR_DURATION) + @DreamOverlayComponent.DreamOverlayScope + static long providesDreamOutBlurDuration(@Main Resources resources) { + return (long) resources.getInteger(R.integer.config_dreamOverlayOutBlurDurationMs); } @Provides diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/HideComplicationTouchHandler.java b/packages/SystemUI/src/com/android/systemui/dreams/touch/HideComplicationTouchHandler.java index 3087cdfd0cc0..e276e0c65b1e 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/touch/HideComplicationTouchHandler.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/touch/HideComplicationTouchHandler.java @@ -16,22 +16,26 @@ package com.android.systemui.dreams.touch; +import static com.android.systemui.dreams.complication.dagger.ComplicationHostViewModule.COMPLICATIONS_FADE_OUT_DELAY; import static com.android.systemui.dreams.complication.dagger.ComplicationHostViewModule.COMPLICATIONS_RESTORE_TIMEOUT; -import android.os.Handler; import android.util.Log; import android.view.MotionEvent; import android.view.View; +import androidx.annotation.Nullable; + import com.android.systemui.dagger.qualifiers.Main; +import com.android.systemui.dreams.DreamOverlayStateController; import com.android.systemui.dreams.complication.Complication; import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; import com.android.systemui.touch.TouchInsetManager; +import com.android.systemui.util.concurrency.DelayableExecutor; import com.google.common.util.concurrent.ListenableFuture; +import java.util.ArrayDeque; import java.util.concurrent.ExecutionException; -import java.util.concurrent.Executor; import javax.inject.Inject; import javax.inject.Named; @@ -49,33 +53,58 @@ public class HideComplicationTouchHandler implements DreamTouchHandler { private static final String TAG = "HideComplicationHandler"; private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); - private final Complication.VisibilityController mVisibilityController; private final int mRestoreTimeout; + private final int mFadeOutDelay; private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager; - private final Handler mHandler; - private final Executor mExecutor; + private final DelayableExecutor mExecutor; + private final DreamOverlayStateController mOverlayStateController; private final TouchInsetManager mTouchInsetManager; + private final Complication.VisibilityController mVisibilityController; + private boolean mHidden = false; + @Nullable + private Runnable mHiddenCallback; + private final ArrayDeque<Runnable> mCancelCallbacks = new ArrayDeque<>(); + private final Runnable mRestoreComplications = new Runnable() { @Override public void run() { - mVisibilityController.setVisibility(View.VISIBLE, true); + mVisibilityController.setVisibility(View.VISIBLE); + mHidden = false; + } + }; + + private final Runnable mHideComplications = new Runnable() { + @Override + public void run() { + if (mOverlayStateController.areExitAnimationsRunning()) { + // Avoid interfering with the exit animations. + return; + } + mVisibilityController.setVisibility(View.INVISIBLE); + mHidden = true; + if (mHiddenCallback != null) { + mHiddenCallback.run(); + mHiddenCallback = null; + } } }; @Inject HideComplicationTouchHandler(Complication.VisibilityController visibilityController, @Named(COMPLICATIONS_RESTORE_TIMEOUT) int restoreTimeout, + @Named(COMPLICATIONS_FADE_OUT_DELAY) int fadeOutDelay, TouchInsetManager touchInsetManager, StatusBarKeyguardViewManager statusBarKeyguardViewManager, - @Main Executor executor, - @Main Handler handler) { + @Main DelayableExecutor executor, + DreamOverlayStateController overlayStateController) { mVisibilityController = visibilityController; mRestoreTimeout = restoreTimeout; + mFadeOutDelay = fadeOutDelay; mStatusBarKeyguardViewManager = statusBarKeyguardViewManager; - mHandler = handler; mTouchInsetManager = touchInsetManager; mExecutor = executor; + mOverlayStateController = overlayStateController; } @Override @@ -87,7 +116,8 @@ public class HideComplicationTouchHandler implements DreamTouchHandler { final boolean bouncerShowing = mStatusBarKeyguardViewManager.isBouncerShowing(); // If other sessions are interested in this touch, do not fade out elements. - if (session.getActiveSessionCount() > 1 || bouncerShowing) { + if (session.getActiveSessionCount() > 1 || bouncerShowing + || mOverlayStateController.areExitAnimationsRunning()) { if (DEBUG) { Log.d(TAG, "not fading. Active session count: " + session.getActiveSessionCount() + ". Bouncer showing: " + bouncerShowing); @@ -115,8 +145,11 @@ public class HideComplicationTouchHandler implements DreamTouchHandler { touchCheck.addListener(() -> { try { if (!touchCheck.get()) { - mHandler.removeCallbacks(mRestoreComplications); - mVisibilityController.setVisibility(View.INVISIBLE, true); + // Cancel all pending callbacks. + while (!mCancelCallbacks.isEmpty()) mCancelCallbacks.pop().run(); + mCancelCallbacks.add( + mExecutor.executeDelayed( + mHideComplications, mFadeOutDelay)); } else { // If a touch occurred inside the dream overlay touch insets, do not // handle the touch. @@ -130,7 +163,23 @@ public class HideComplicationTouchHandler implements DreamTouchHandler { || motionEvent.getAction() == MotionEvent.ACTION_UP) { // End session and initiate delayed reappearance of the complications. session.pop(); - mHandler.postDelayed(mRestoreComplications, mRestoreTimeout); + runAfterHidden(() -> mCancelCallbacks.add( + mExecutor.executeDelayed(mRestoreComplications, + mRestoreTimeout))); + } + }); + } + + /** + * Triggers a runnable after complications have been hidden. Will override any previously set + * runnable currently waiting for hide to happen. + */ + private void runAfterHidden(Runnable runnable) { + mExecutor.execute(() -> { + if (mHidden) { + runnable.run(); + } else { + mHiddenCallback = runnable; } }); } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java index 7cd3843fd506..5ed0bff99b61 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java @@ -409,6 +409,11 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, private final int mDreamOpenAnimationDuration; /** + * The duration in milliseconds of the dream close animation. + */ + private final int mDreamCloseAnimationDuration; + + /** * The animation used for hiding keyguard. This is used to fetch the animation timings if * WindowManager is not providing us with them. */ @@ -1054,7 +1059,8 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, } mUnoccludeAnimator = ValueAnimator.ofFloat(1f, 0f); - mUnoccludeAnimator.setDuration(UNOCCLUDE_ANIMATION_DURATION); + mUnoccludeAnimator.setDuration(isDream ? mDreamCloseAnimationDuration + : UNOCCLUDE_ANIMATION_DURATION); mUnoccludeAnimator.setInterpolator(Interpolators.TOUCH_RESPONSE); mUnoccludeAnimator.addUpdateListener( animation -> { @@ -1204,6 +1210,8 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, mDreamOpenAnimationDuration = context.getResources().getInteger( com.android.internal.R.integer.config_dreamOpenAnimationDuration); + mDreamCloseAnimationDuration = context.getResources().getInteger( + com.android.internal.R.integer.config_dreamCloseAnimationDuration); } public void userActivity() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayAnimationsControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayAnimationsControllerTest.kt new file mode 100644 index 000000000000..99406ed44606 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayAnimationsControllerTest.kt @@ -0,0 +1,125 @@ +package com.android.systemui.dreams + +import android.animation.Animator +import android.animation.AnimatorSet +import android.testing.AndroidTestingRunner +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.dreams.complication.ComplicationHostViewController +import com.android.systemui.statusbar.BlurUtils +import com.android.systemui.util.mockito.argumentCaptor +import com.android.systemui.util.mockito.mock +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.Mockito.never +import org.mockito.Mockito.times +import org.mockito.Mockito.verify +import org.mockito.MockitoAnnotations + +@SmallTest +@RunWith(AndroidTestingRunner::class) +class DreamOverlayAnimationsControllerTest : SysuiTestCase() { + + companion object { + private const val DREAM_IN_BLUR_ANIMATION_DURATION = 1L + private const val DREAM_IN_BLUR_ANIMATION_DELAY = 2L + private const val DREAM_IN_COMPLICATIONS_ANIMATION_DURATION = 3L + private const val DREAM_IN_TOP_COMPLICATIONS_ANIMATION_DELAY = 4L + private const val DREAM_IN_BOTTOM_COMPLICATIONS_ANIMATION_DELAY = 5L + private const val DREAM_OUT_TRANSLATION_Y_DISTANCE = 6 + private const val DREAM_OUT_TRANSLATION_Y_DURATION = 7L + private const val DREAM_OUT_TRANSLATION_Y_DELAY_BOTTOM = 8L + private const val DREAM_OUT_TRANSLATION_Y_DELAY_TOP = 9L + private const val DREAM_OUT_ALPHA_DURATION = 10L + private const val DREAM_OUT_ALPHA_DELAY_BOTTOM = 11L + private const val DREAM_OUT_ALPHA_DELAY_TOP = 12L + private const val DREAM_OUT_BLUR_DURATION = 13L + } + + @Mock private lateinit var mockAnimator: AnimatorSet + @Mock private lateinit var blurUtils: BlurUtils + @Mock private lateinit var hostViewController: ComplicationHostViewController + @Mock private lateinit var statusBarViewController: DreamOverlayStatusBarViewController + @Mock private lateinit var stateController: DreamOverlayStateController + private lateinit var controller: DreamOverlayAnimationsController + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + controller = + DreamOverlayAnimationsController( + blurUtils, + hostViewController, + statusBarViewController, + stateController, + DREAM_IN_BLUR_ANIMATION_DURATION, + DREAM_IN_BLUR_ANIMATION_DELAY, + DREAM_IN_COMPLICATIONS_ANIMATION_DURATION, + DREAM_IN_TOP_COMPLICATIONS_ANIMATION_DELAY, + DREAM_IN_BOTTOM_COMPLICATIONS_ANIMATION_DELAY, + DREAM_OUT_TRANSLATION_Y_DISTANCE, + DREAM_OUT_TRANSLATION_Y_DURATION, + DREAM_OUT_TRANSLATION_Y_DELAY_BOTTOM, + DREAM_OUT_TRANSLATION_Y_DELAY_TOP, + DREAM_OUT_ALPHA_DURATION, + DREAM_OUT_ALPHA_DELAY_BOTTOM, + DREAM_OUT_ALPHA_DELAY_TOP, + DREAM_OUT_BLUR_DURATION + ) + } + + @Test + fun testExitAnimationOnEnd() { + val mockCallback: () -> Unit = mock() + + controller.startExitAnimations( + view = mock(), + doneCallback = mockCallback, + animatorBuilder = { mockAnimator } + ) + + val captor = argumentCaptor<Animator.AnimatorListener>() + verify(mockAnimator).addListener(captor.capture()) + val listener = captor.value + + verify(mockCallback, never()).invoke() + listener.onAnimationEnd(mockAnimator) + verify(mockCallback, times(1)).invoke() + } + + @Test + fun testCancellation() { + controller.startExitAnimations( + view = mock(), + doneCallback = mock(), + animatorBuilder = { mockAnimator } + ) + + verify(mockAnimator, never()).cancel() + controller.cancelAnimations() + verify(mockAnimator, times(1)).cancel() + } + + @Test + fun testExitAfterStartWillCancel() { + val mockStartAnimator: AnimatorSet = mock() + val mockExitAnimator: AnimatorSet = mock() + + controller.startEntryAnimations(view = mock(), animatorBuilder = { mockStartAnimator }) + + verify(mockStartAnimator, never()).cancel() + + controller.startExitAnimations( + view = mock(), + doneCallback = mock(), + animatorBuilder = { mockExitAnimator } + ) + + // Verify that we cancelled the start animator in favor of the exit + // animator. + verify(mockStartAnimator, times(1)).cancel() + verify(mockExitAnimator, never()).cancel() + } +} 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 517804db2a70..73c226d11bc4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java @@ -204,7 +204,7 @@ public class DreamOverlayContainerViewControllerTest extends SysuiTestCase { mController.onViewAttached(); verify(mAnimationsController).startEntryAnimations(mDreamOverlayContainerView); - verify(mAnimationsController, never()).cancelRunningEntryAnimations(); + verify(mAnimationsController, never()).cancelAnimations(); } @Test @@ -221,6 +221,6 @@ public class DreamOverlayContainerViewControllerTest extends SysuiTestCase { mController.onViewAttached(); mController.onViewDetached(); - verify(mAnimationsController).cancelRunningEntryAnimations(); + verify(mAnimationsController).cancelAnimations(); } } 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 f04a37f4c3fa..ffb8342a56a5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java @@ -20,6 +20,7 @@ import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.clearInvocations; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -337,4 +338,28 @@ public class DreamOverlayServiceTest extends SysuiTestCase { verify(mDreamOverlayComponent).getDreamOverlayContainerViewController(); verify(mDreamOverlayComponent).getDreamOverlayTouchMonitor(); } + + @Test + public void testWakeUp() throws RemoteException { + final IBinder proxy = mService.onBind(new Intent()); + final IDreamOverlay overlay = IDreamOverlay.Stub.asInterface(proxy); + + // Inform the overlay service of dream starting. + overlay.startDream(mWindowParams, mDreamOverlayCallback, DREAM_COMPONENT, + true /*shouldShowComplication*/); + mMainExecutor.runAllReady(); + + final Runnable callback = mock(Runnable.class); + mService.onWakeUp(callback); + mMainExecutor.runAllReady(); + verify(mDreamOverlayContainerViewController).wakeUp(callback, mMainExecutor); + } + + @Test + public void testWakeUpBeforeStartDoesNothing() { + final Runnable callback = mock(Runnable.class); + mService.onWakeUp(callback); + mMainExecutor.runAllReady(); + verify(mDreamOverlayContainerViewController, never()).wakeUp(callback, mMainExecutor); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/HideComplicationTouchHandlerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/HideComplicationTouchHandlerTest.java index 14a5702c8e5b..4e3aca710884 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/HideComplicationTouchHandlerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/HideComplicationTouchHandlerTest.java @@ -16,8 +16,6 @@ package com.android.systemui.dreams.touch; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.never; @@ -33,6 +31,7 @@ import androidx.concurrent.futures.CallbackToFutureAdapter; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; +import com.android.systemui.dreams.DreamOverlayStateController; import com.android.systemui.dreams.complication.Complication; import com.android.systemui.shared.system.InputChannelCompat; import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; @@ -52,6 +51,7 @@ import org.mockito.MockitoAnnotations; @RunWith(AndroidTestingRunner.class) public class HideComplicationTouchHandlerTest extends SysuiTestCase { private static final int RESTORE_TIMEOUT = 1000; + private static final int HIDE_DELAY = 500; @Mock Complication.VisibilityController mVisibilityController; @@ -71,11 +71,18 @@ public class HideComplicationTouchHandlerTest extends SysuiTestCase { @Mock DreamTouchHandler.TouchSession mSession; - FakeExecutor mFakeExecutor = new FakeExecutor(new FakeSystemClock()); + @Mock + DreamOverlayStateController mStateController; + + FakeSystemClock mClock; + + FakeExecutor mFakeExecutor; @Before public void setup() { MockitoAnnotations.initMocks(this); + mClock = new FakeSystemClock(); + mFakeExecutor = new FakeExecutor(mClock); } /** @@ -86,10 +93,11 @@ public class HideComplicationTouchHandlerTest extends SysuiTestCase { final HideComplicationTouchHandler touchHandler = new HideComplicationTouchHandler( mVisibilityController, RESTORE_TIMEOUT, + HIDE_DELAY, mTouchInsetManager, mStatusBarKeyguardViewManager, mFakeExecutor, - mHandler); + mStateController); // Report multiple active sessions. when(mSession.getActiveSessionCount()).thenReturn(2); @@ -103,8 +111,10 @@ public class HideComplicationTouchHandlerTest extends SysuiTestCase { // Verify session end. verify(mSession).pop(); + mClock.advanceTime(HIDE_DELAY); + // Verify no interaction with visibility controller. - verify(mVisibilityController, never()).setVisibility(anyInt(), anyBoolean()); + verify(mVisibilityController, never()).setVisibility(anyInt()); } /** @@ -115,10 +125,11 @@ public class HideComplicationTouchHandlerTest extends SysuiTestCase { final HideComplicationTouchHandler touchHandler = new HideComplicationTouchHandler( mVisibilityController, RESTORE_TIMEOUT, + HIDE_DELAY, mTouchInsetManager, mStatusBarKeyguardViewManager, mFakeExecutor, - mHandler); + mStateController); // Report one session. when(mSession.getActiveSessionCount()).thenReturn(1); @@ -132,8 +143,10 @@ public class HideComplicationTouchHandlerTest extends SysuiTestCase { // Verify session end. verify(mSession).pop(); + mClock.advanceTime(HIDE_DELAY); + // Verify no interaction with visibility controller. - verify(mVisibilityController, never()).setVisibility(anyInt(), anyBoolean()); + verify(mVisibilityController, never()).setVisibility(anyInt()); } /** @@ -144,10 +157,11 @@ public class HideComplicationTouchHandlerTest extends SysuiTestCase { final HideComplicationTouchHandler touchHandler = new HideComplicationTouchHandler( mVisibilityController, RESTORE_TIMEOUT, + HIDE_DELAY, mTouchInsetManager, mStatusBarKeyguardViewManager, mFakeExecutor, - mHandler); + mStateController); // Report one session when(mSession.getActiveSessionCount()).thenReturn(1); @@ -177,8 +191,10 @@ public class HideComplicationTouchHandlerTest extends SysuiTestCase { // Verify session ended. verify(mSession).pop(); + mClock.advanceTime(HIDE_DELAY); + // Verify no interaction with visibility controller. - verify(mVisibilityController, never()).setVisibility(anyInt(), anyBoolean()); + verify(mVisibilityController, never()).setVisibility(anyInt()); } /** @@ -189,10 +205,11 @@ public class HideComplicationTouchHandlerTest extends SysuiTestCase { final HideComplicationTouchHandler touchHandler = new HideComplicationTouchHandler( mVisibilityController, RESTORE_TIMEOUT, + HIDE_DELAY, mTouchInsetManager, mStatusBarKeyguardViewManager, mFakeExecutor, - mHandler); + mStateController); // Report one session when(mSession.getActiveSessionCount()).thenReturn(1); @@ -221,11 +238,11 @@ public class HideComplicationTouchHandlerTest extends SysuiTestCase { inputEventListenerCaptor.getValue().onInputEvent(mMotionEvent); mFakeExecutor.runAllReady(); - // Verify callback to restore visibility cancelled. - verify(mHandler).removeCallbacks(any()); - + // Verify visibility controller doesn't hide until after timeout + verify(mVisibilityController, never()).setVisibility(eq(View.INVISIBLE)); + mClock.advanceTime(HIDE_DELAY); // Verify visibility controller told to hide complications. - verify(mVisibilityController).setVisibility(eq(View.INVISIBLE), anyBoolean()); + verify(mVisibilityController).setVisibility(eq(View.INVISIBLE)); Mockito.clearInvocations(mVisibilityController, mHandler); @@ -235,11 +252,8 @@ public class HideComplicationTouchHandlerTest extends SysuiTestCase { mFakeExecutor.runAllReady(); // Verify visibility controller told to show complications. - ArgumentCaptor<Runnable> delayRunnableCaptor = ArgumentCaptor.forClass(Runnable.class); - verify(mHandler).postDelayed(delayRunnableCaptor.capture(), - eq(Long.valueOf(RESTORE_TIMEOUT))); - delayRunnableCaptor.getValue().run(); - verify(mVisibilityController).setVisibility(eq(View.VISIBLE), anyBoolean()); + mClock.advanceTime(RESTORE_TIMEOUT); + verify(mVisibilityController).setVisibility(eq(View.VISIBLE)); // Verify session ended. verify(mSession).pop(); |