diff --git a/core/java/android/service/dreams/DreamOverlayService.java b/core/java/android/service/dreams/DreamOverlayService.java index aa45c20a8e135..6e8198ba0cd1a 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; @@ -70,6 +81,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. */ diff --git a/core/java/android/service/dreams/DreamService.java b/core/java/android/service/dreams/DreamService.java index 32bdf79622730..cff20391103ba 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 { *

*/ 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 05ebbfe98c9fc..7aeceb2ce5380 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 ec76a334d5b2d..4ad63f1317d15 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 2a9e60a469725..b9bcf559f63bb 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -2485,7 +2485,7 @@ 250 - 100 + 300 diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml index 55d637916d0cd..f4d802bf745ef 100644 --- a/packages/SystemUI/res/values/config.xml +++ b/packages/SystemUI/res/values/config.xml @@ -743,12 +743,35 @@ 240000 + + 333 + + 33 + + 117 + + 200 + + 217 + + 133 + + 250 + 500 500 1000 + 200 + 249 @@ -780,27 +803,6 @@ com.android.systemui - - - -1 - 50 - 100 - 150 - 200 - 300 - - - - - @color/dream_overlay_aqi_good - @color/dream_overlay_aqi_moderate - @color/dream_overlay_aqi_unhealthy_sensitive - @color/dream_overlay_aqi_unhealthy - @color/dream_overlay_aqi_very_unhealthy - @color/dream_overlay_aqi_hazardous - - true diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index c78d36dd36853..7cda9d70ea49a 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -1534,6 +1534,7 @@ 0dp 80dp + 40dp 28dp 6dp diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt index d8dd6a21d4c16..0087c84393708 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() + + @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() + @JvmOverloads + fun startEntryAnimations(view: View, animatorBuilder: () -> AnimatorSet = { AnimatorSet() }) { + cancelAnimations() - mEntryAnimations = AnimatorSet() - mEntryAnimations?.apply { - playTogether( - buildDreamInBlurAnimator(view), - buildDreamInTopComplicationsAnimator(), - buildDreamInBottomComplicationsAnimator() - ) - doOnEnd { mOverlayStateController.setEntryAnimationsFinished(true) } - start() - } + 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) }) - } - - private fun setAlphaAndEnsureVisible(view: View, alpha: Float) { - if (alpha > 0 && view.visibility != View.VISIBLE) { - view.visibility = View.VISIBLE + /** 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) + } + } - view.alpha = alpha + /** 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) + } } } diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java index 5c6d24813570b..9d7ad305c7e50 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 { + 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 8542412f82f86..e76d5b30d3e61 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 e80d0beabf2b0..5f942b6fb834f 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; } @@ -256,6 +265,15 @@ public class DreamOverlayStateController implements STATE_DREAM_ENTRY_ANIMATIONS_FINISHED); } + /** + * 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. */ diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java index d17fbe31c8d2c..f1bb156199ef5 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 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 7cd3843fd5066..5ed0bff99b614 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java @@ -408,6 +408,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 0000000000000..99406ed446060 --- /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() + 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 517804db2a70a..73c226d11bc43 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 f04a37f4c3fa0..ffb8342a56a54 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 14a5702c8e5b0..4e3aca7108845 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 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();