diff options
10 files changed, 956 insertions, 728 deletions
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt index f934b1f3ab99..5b47ae525919 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt @@ -59,31 +59,31 @@ class ActivityLaunchAnimator( companion object { /** The timings when animating a View into an app. */ @JvmField - val TIMINGS = LaunchAnimator.Timings( - totalDuration = 500L, - contentBeforeFadeOutDelay = 0L, - contentBeforeFadeOutDuration = 150L, - contentAfterFadeInDelay = 150L, - contentAfterFadeInDuration = 183L - ) + val TIMINGS = + LaunchAnimator.Timings( + totalDuration = 500L, + contentBeforeFadeOutDelay = 0L, + contentBeforeFadeOutDuration = 150L, + contentAfterFadeInDelay = 150L, + contentAfterFadeInDuration = 183L + ) /** * The timings when animating a Dialog into an app. We need to wait at least 200ms before * showing the app (which is under the dialog window) so that the dialog window dim is fully * faded out, to avoid flicker. */ - val DIALOG_TIMINGS = TIMINGS.copy( - contentBeforeFadeOutDuration = 200L, - contentAfterFadeInDelay = 200L - ) + val DIALOG_TIMINGS = + TIMINGS.copy(contentBeforeFadeOutDuration = 200L, contentAfterFadeInDelay = 200L) /** The interpolators when animating a View or a dialog into an app. */ - val INTERPOLATORS = LaunchAnimator.Interpolators( - positionInterpolator = Interpolators.EMPHASIZED, - positionXInterpolator = createPositionXInterpolator(), - contentBeforeFadeOutInterpolator = Interpolators.LINEAR_OUT_SLOW_IN, - contentAfterFadeInInterpolator = PathInterpolator(0f, 0f, 0.6f, 1f) - ) + val INTERPOLATORS = + LaunchAnimator.Interpolators( + positionInterpolator = Interpolators.EMPHASIZED, + positionXInterpolator = createPositionXInterpolator(), + contentBeforeFadeOutInterpolator = Interpolators.LINEAR_OUT_SLOW_IN, + contentAfterFadeInInterpolator = PathInterpolator(0f, 0f, 0.6f, 1f) + ) /** Durations & interpolators for the navigation bar fading in & out. */ private const val ANIMATION_DURATION_NAV_FADE_IN = 266L @@ -98,11 +98,12 @@ class ActivityLaunchAnimator( private const val LAUNCH_TIMEOUT = 1000L private fun createPositionXInterpolator(): Interpolator { - val path = Path().apply { - moveTo(0f, 0f) - cubicTo(0.1217f, 0.0462f, 0.15f, 0.4686f, 0.1667f, 0.66f) - cubicTo(0.1834f, 0.8878f, 0.1667f, 1f, 1f, 1f) - } + val path = + Path().apply { + moveTo(0f, 0f) + cubicTo(0.1217f, 0.0462f, 0.15f, 0.4686f, 0.1667f, 0.66f) + cubicTo(0.1834f, 0.8878f, 0.1667f, 1f, 1f, 1f) + } return PathInterpolator(path) } } @@ -150,29 +151,37 @@ class ActivityLaunchAnimator( return } - val callback = this.callback ?: throw IllegalStateException( - "ActivityLaunchAnimator.callback must be set before using this animator") + val callback = + this.callback + ?: throw IllegalStateException( + "ActivityLaunchAnimator.callback must be set before using this animator" + ) val runner = Runner(controller) val hideKeyguardWithAnimation = callback.isOnKeyguard() && !showOverLockscreen // Pass the RemoteAnimationAdapter to the intent starter only if we are not hiding the // keyguard with the animation - val animationAdapter = if (!hideKeyguardWithAnimation) { - RemoteAnimationAdapter( - runner, - TIMINGS.totalDuration, - TIMINGS.totalDuration - 150 /* statusBarTransitionDelay */ - ) - } else { - null - } + val animationAdapter = + if (!hideKeyguardWithAnimation) { + RemoteAnimationAdapter( + runner, + TIMINGS.totalDuration, + TIMINGS.totalDuration - 150 /* statusBarTransitionDelay */ + ) + } else { + null + } // Register the remote animation for the given package to also animate trampoline // activity launches. if (packageName != null && animationAdapter != null) { try { - ActivityTaskManager.getService().registerRemoteAnimationForNextActivityStart( - packageName, animationAdapter, null /* launchCookie */) + ActivityTaskManager.getService() + .registerRemoteAnimationForNextActivityStart( + packageName, + animationAdapter, + null /* launchCookie */ + ) } catch (e: RemoteException) { Log.w(TAG, "Unable to register the remote animation", e) } @@ -184,12 +193,15 @@ class ActivityLaunchAnimator( // keyguard. val willAnimate = launchResult == ActivityManager.START_TASK_TO_FRONT || - launchResult == ActivityManager.START_SUCCESS || - (launchResult == ActivityManager.START_DELIVERED_TO_TOP && - hideKeyguardWithAnimation) - - Log.i(TAG, "launchResult=$launchResult willAnimate=$willAnimate " + - "hideKeyguardWithAnimation=$hideKeyguardWithAnimation") + launchResult == ActivityManager.START_SUCCESS || + (launchResult == ActivityManager.START_DELIVERED_TO_TOP && + hideKeyguardWithAnimation) + + Log.i( + TAG, + "launchResult=$launchResult willAnimate=$willAnimate " + + "hideKeyguardWithAnimation=$hideKeyguardWithAnimation" + ) controller.callOnIntentStartedOnMainThread(willAnimate) // If we expect an animation, post a timeout to cancel it in case the remote animation is @@ -206,9 +218,7 @@ class ActivityLaunchAnimator( private fun Controller.callOnIntentStartedOnMainThread(willAnimate: Boolean) { if (Looper.myLooper() != Looper.getMainLooper()) { - this.launchContainer.context.mainExecutor.execute { - this.onIntentStarted(willAnimate) - } + this.launchContainer.context.mainExecutor.execute { this.onIntentStarted(willAnimate) } } else { this.onIntentStarted(willAnimate) } @@ -246,8 +256,7 @@ class ActivityLaunchAnimator( } /** Create a new animation [Runner] controlled by [controller]. */ - @VisibleForTesting - fun createRunner(controller: Controller): Runner = Runner(controller) + @VisibleForTesting fun createRunner(controller: Controller): Runner = Runner(controller) interface PendingIntentStarter { /** @@ -271,19 +280,16 @@ class ActivityLaunchAnimator( interface Listener { /** Called when an activity launch animation started. */ - @JvmDefault - fun onLaunchAnimationStart() {} + @JvmDefault fun onLaunchAnimationStart() {} /** * Called when an activity launch animation is finished. This will be called if and only if * [onLaunchAnimationStart] was called earlier. */ - @JvmDefault - fun onLaunchAnimationEnd() {} + @JvmDefault fun onLaunchAnimationEnd() {} /** Called when an activity launch animation made progress. */ - @JvmDefault - fun onLaunchAnimationProgress(linearProgress: Float) {} + @JvmDefault fun onLaunchAnimationProgress(linearProgress: Float) {} } /** @@ -327,6 +333,17 @@ class ActivityLaunchAnimator( get() = false /** + * Whether the expandable controller by this [Controller] is below the launching window that + * is going to be animated. + * + * This should be `false` when launching an app from the shade or status bar, given that + * they are drawn above all apps. This is usually `true` when using this launcher in a + * normal app or a launcher, that are drawn below the animating activity/window. + */ + val isBelowAnimatingWindow: Boolean + get() = false + + /** * The intent was started. If [willAnimate] is false, nothing else will happen and the * animation will not be started. */ @@ -394,9 +411,7 @@ class ActivityLaunchAnimator( return } - context.mainExecutor.execute { - startAnimation(apps, nonApps, iCallback) - } + context.mainExecutor.execute { startAnimation(apps, nonApps, iCallback) } } private fun startAnimation( @@ -408,9 +423,7 @@ class ActivityLaunchAnimator( Log.d(TAG, "Remote animation started") } - val window = apps?.firstOrNull { - it.mode == RemoteAnimationTarget.MODE_OPENING - } + val window = apps?.firstOrNull { it.mode == RemoteAnimationTarget.MODE_OPENING } if (window == null) { Log.i(TAG, "Aborting the animation as no window is opening") @@ -420,80 +433,96 @@ class ActivityLaunchAnimator( return } - val navigationBar = nonApps?.firstOrNull { - it.windowType == WindowManager.LayoutParams.TYPE_NAVIGATION_BAR - } + val navigationBar = + nonApps?.firstOrNull { + it.windowType == WindowManager.LayoutParams.TYPE_NAVIGATION_BAR + } val windowBounds = window.screenSpaceBounds - val endState = LaunchAnimator.State( - top = windowBounds.top, - bottom = windowBounds.bottom, - left = windowBounds.left, - right = windowBounds.right - ) + val endState = + LaunchAnimator.State( + top = windowBounds.top, + bottom = windowBounds.bottom, + left = windowBounds.left, + right = windowBounds.right + ) val callback = this@ActivityLaunchAnimator.callback!! - val windowBackgroundColor = window.taskInfo?.let { callback.getBackgroundColor(it) } - ?: window.backgroundColor + val windowBackgroundColor = + window.taskInfo?.let { callback.getBackgroundColor(it) } ?: window.backgroundColor // Make sure we use the modified timings when animating a dialog into an app. - val launchAnimator = if (controller.isDialogLaunch) { - dialogToAppAnimator - } else { - launchAnimator - } + val launchAnimator = + if (controller.isDialogLaunch) { + dialogToAppAnimator + } else { + launchAnimator + } // TODO(b/184121838): We should somehow get the top and bottom radius of the window // instead of recomputing isExpandingFullyAbove here. val isExpandingFullyAbove = launchAnimator.isExpandingFullyAbove(controller.launchContainer, endState) - val endRadius = if (isExpandingFullyAbove) { - // Most of the time, expanding fully above the root view means expanding in full - // screen. - ScreenDecorationsUtils.getWindowCornerRadius(context) - } else { - // This usually means we are in split screen mode, so 2 out of 4 corners will have - // a radius of 0. - 0f - } + val endRadius = + if (isExpandingFullyAbove) { + // Most of the time, expanding fully above the root view means expanding in full + // screen. + ScreenDecorationsUtils.getWindowCornerRadius(context) + } else { + // This usually means we are in split screen mode, so 2 out of 4 corners will + // have + // a radius of 0. + 0f + } endState.topCornerRadius = endRadius endState.bottomCornerRadius = endRadius // We animate the opening window and delegate the view expansion to [this.controller]. val delegate = this.controller - val controller = object : LaunchAnimator.Controller by delegate { - override fun onLaunchAnimationStart(isExpandingFullyAbove: Boolean) { - listeners.forEach { it.onLaunchAnimationStart() } - delegate.onLaunchAnimationStart(isExpandingFullyAbove) - } - - override fun onLaunchAnimationEnd(isExpandingFullyAbove: Boolean) { - listeners.forEach { it.onLaunchAnimationEnd() } - iCallback?.invoke() - delegate.onLaunchAnimationEnd(isExpandingFullyAbove) - } + val controller = + object : Controller by delegate { + override fun onLaunchAnimationStart(isExpandingFullyAbove: Boolean) { + listeners.forEach { it.onLaunchAnimationStart() } + delegate.onLaunchAnimationStart(isExpandingFullyAbove) + } - override fun onLaunchAnimationProgress( - state: LaunchAnimator.State, - progress: Float, - linearProgress: Float - ) { - // Apply the state to the window only if it is visible, i.e. when the expanding - // view is *not* visible. - if (!state.visible) { - applyStateToWindow(window, state) + override fun onLaunchAnimationEnd(isExpandingFullyAbove: Boolean) { + listeners.forEach { it.onLaunchAnimationEnd() } + iCallback?.invoke() + delegate.onLaunchAnimationEnd(isExpandingFullyAbove) } - navigationBar?.let { applyStateToNavigationBar(it, state, linearProgress) } - listeners.forEach { it.onLaunchAnimationProgress(linearProgress) } - delegate.onLaunchAnimationProgress(state, progress, linearProgress) + override fun onLaunchAnimationProgress( + state: LaunchAnimator.State, + progress: Float, + linearProgress: Float + ) { + // Apply the state to the window only if it is visible, i.e. when the + // expanding view is *not* visible. + if (!state.visible) { + applyStateToWindow(window, state, linearProgress) + } + navigationBar?.let { applyStateToNavigationBar(it, state, linearProgress) } + + listeners.forEach { it.onLaunchAnimationProgress(linearProgress) } + delegate.onLaunchAnimationProgress(state, progress, linearProgress) + } } - } - animation = launchAnimator.startAnimation( - controller, endState, windowBackgroundColor, drawHole = true) + animation = + launchAnimator.startAnimation( + controller, + endState, + windowBackgroundColor, + fadeOutWindowBackgroundLayer = !controller.isBelowAnimatingWindow, + drawHole = !controller.isBelowAnimatingWindow, + ) } - private fun applyStateToWindow(window: RemoteAnimationTarget, state: LaunchAnimator.State) { + private fun applyStateToWindow( + window: RemoteAnimationTarget, + state: LaunchAnimator.State, + linearProgress: Float, + ) { if (transactionApplierView.viewRootImpl == null) { // If the view root we synchronize with was detached, don't apply any transaction // (as [SyncRtSurfaceTransactionApplier.scheduleApply] would otherwise throw). @@ -535,19 +564,38 @@ class ActivityLaunchAnimator( windowCropF.bottom.roundToInt() ) + // The alpha of the opening window. If it opens above the expandable, then it should + // fade in progressively. Otherwise, it should be fully opaque and will be progressively + // revealed as the window background color layer above the window fades out. + val alpha = + if (controller.isBelowAnimatingWindow) { + val windowProgress = + LaunchAnimator.getProgress( + TIMINGS, + linearProgress, + TIMINGS.contentAfterFadeInDelay, + TIMINGS.contentAfterFadeInDuration + ) + + INTERPOLATORS.contentAfterFadeInInterpolator.getInterpolation(windowProgress) + } else { + 1f + } + // The scale will also be applied to the corner radius, so we divide by the scale to // keep the original radius. We use the max of (topCornerRadius, bottomCornerRadius) to // make sure that the window does not draw itself behind the expanding view. This is // especially important for lock screen animations, where the window is not clipped by // the shade. val cornerRadius = maxOf(state.topCornerRadius, state.bottomCornerRadius) / scale - val params = SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(window.leash) - .withAlpha(1f) - .withMatrix(matrix) - .withWindowCrop(windowCrop) - .withCornerRadius(cornerRadius) - .withVisibility(true) - .build() + val params = + SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(window.leash) + .withAlpha(alpha) + .withMatrix(matrix) + .withWindowCrop(windowCrop) + .withCornerRadius(cornerRadius) + .withVisibility(true) + .build() transactionApplier.scheduleApply(params) } @@ -563,14 +611,21 @@ class ActivityLaunchAnimator( return } - val fadeInProgress = LaunchAnimator.getProgress(TIMINGS, linearProgress, - ANIMATION_DELAY_NAV_FADE_IN, ANIMATION_DURATION_NAV_FADE_OUT) + val fadeInProgress = + LaunchAnimator.getProgress( + TIMINGS, + linearProgress, + ANIMATION_DELAY_NAV_FADE_IN, + ANIMATION_DURATION_NAV_FADE_OUT + ) val params = SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(navigationBar.leash) if (fadeInProgress > 0) { matrix.reset() matrix.setTranslate( - 0f, (state.top - navigationBar.sourceContainerBounds.top).toFloat()) + 0f, + (state.top - navigationBar.sourceContainerBounds.top).toFloat() + ) windowCrop.set(state.left, 0, state.right, state.height) params .withAlpha(NAV_FADE_IN_INTERPOLATOR.getInterpolation(fadeInProgress)) @@ -578,8 +633,13 @@ class ActivityLaunchAnimator( .withWindowCrop(windowCrop) .withVisibility(true) } else { - val fadeOutProgress = LaunchAnimator.getProgress(TIMINGS, linearProgress, 0, - ANIMATION_DURATION_NAV_FADE_OUT) + val fadeOutProgress = + LaunchAnimator.getProgress( + TIMINGS, + linearProgress, + 0, + ANIMATION_DURATION_NAV_FADE_OUT + ) params.withAlpha(1f - NAV_FADE_OUT_INTERPOLATOR.getInterpolation(fadeOutProgress)) } diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/DelegateLaunchAnimatorController.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/DelegateLaunchAnimatorController.kt index 258ca6bdf79b..b879ba0b1ece 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/DelegateLaunchAnimatorController.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/DelegateLaunchAnimatorController.kt @@ -23,4 +23,4 @@ package com.android.systemui.animation */ open class DelegateLaunchAnimatorController( protected val delegate: ActivityLaunchAnimator.Controller -) : ActivityLaunchAnimator.Controller by delegate
\ No newline at end of file +) : ActivityLaunchAnimator.Controller by delegate diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt index 48231e30ba6d..cb16d7c3471f 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt @@ -51,7 +51,9 @@ private const val TAG = "DialogLaunchAnimator" * @see showFromDialog * @see createActivityLaunchController */ -class DialogLaunchAnimator @JvmOverloads constructor( +class DialogLaunchAnimator +@JvmOverloads +constructor( private val dreamManager: IDreamManager, private val interactionJankMonitor: InteractionJankMonitor, private val launchAnimator: LaunchAnimator = LaunchAnimator(TIMINGS, INTERPOLATORS), @@ -62,9 +64,10 @@ class DialogLaunchAnimator @JvmOverloads constructor( // We use the same interpolator for X and Y axis to make sure the dialog does not move out // of the screen bounds during the animation. - private val INTERPOLATORS = ActivityLaunchAnimator.INTERPOLATORS.copy( - positionXInterpolator = ActivityLaunchAnimator.INTERPOLATORS.positionInterpolator - ) + private val INTERPOLATORS = + ActivityLaunchAnimator.INTERPOLATORS.copy( + positionXInterpolator = ActivityLaunchAnimator.INTERPOLATORS.positionInterpolator + ) private val TAG_LAUNCH_ANIMATION_RUNNING = R.id.tag_launch_animation_running } @@ -105,8 +108,10 @@ class DialogLaunchAnimator @JvmOverloads constructor( // If the view we are launching from belongs to another dialog, then this means the caller // intent is to launch a dialog from another dialog. - val animatedParent = openedDialogs - .firstOrNull { it.dialog.window.decorView.viewRootImpl == view.viewRootImpl } + val animatedParent = + openedDialogs.firstOrNull { + it.dialog.window.decorView.viewRootImpl == view.viewRootImpl + } val animateFrom = animatedParent?.dialogContentWithBackground ?: view // Make sure we don't run the launch animation from the same view twice at the same time. @@ -118,18 +123,19 @@ class DialogLaunchAnimator @JvmOverloads constructor( animateFrom.setTag(TAG_LAUNCH_ANIMATION_RUNNING, true) - val animatedDialog = AnimatedDialog( - launchAnimator, - dreamManager, - interactionJankMonitor, - animateFrom, - onDialogDismissed = { openedDialogs.remove(it) }, - dialog = dialog, - animateBackgroundBoundsChange, - animatedParent, - isForTesting, - cuj - ) + val animatedDialog = + AnimatedDialog( + launchAnimator, + dreamManager, + interactionJankMonitor, + animateFrom, + onDialogDismissed = { openedDialogs.remove(it) }, + dialog = dialog, + animateBackgroundBoundsChange, + animatedParent, + isForTesting, + cuj + ) openedDialogs.add(animatedDialog) animatedDialog.start() @@ -146,13 +152,12 @@ class DialogLaunchAnimator @JvmOverloads constructor( animateFrom: Dialog, animateBackgroundBoundsChange: Boolean = false ) { - val view = openedDialogs - .firstOrNull { it.dialog == animateFrom } - ?.dialogContentWithBackground - ?: throw IllegalStateException( - "The animateFrom dialog was not animated using " + - "DialogLaunchAnimator.showFrom(View|Dialog)" - ) + val view = + openedDialogs.firstOrNull { it.dialog == animateFrom }?.dialogContentWithBackground + ?: throw IllegalStateException( + "The animateFrom dialog was not animated using " + + "DialogLaunchAnimator.showFrom(View|Dialog)" + ) showFromView(dialog, view, animateBackgroundBoundsChange = animateBackgroundBoundsChange) } @@ -175,9 +180,11 @@ class DialogLaunchAnimator @JvmOverloads constructor( view: View, cujType: Int? = null ): ActivityLaunchAnimator.Controller? { - val animatedDialog = openedDialogs - .firstOrNull { it.dialog.window.decorView.viewRootImpl == view.viewRootImpl } - ?: return null + val animatedDialog = + openedDialogs.firstOrNull { + it.dialog.window.decorView.viewRootImpl == view.viewRootImpl + } + ?: return null // At this point, we know that the intent of the caller is to dismiss the dialog to show // an app, so we disable the exit animation into the touch surface because we will never @@ -240,7 +247,7 @@ class DialogLaunchAnimator @JvmOverloads constructor( } private fun disableDialogDismiss() { - dialog.setDismissOverride { /* Do nothing */ } + dialog.setDismissOverride { /* Do nothing */} } private fun enableDialogDismiss() { @@ -257,9 +264,9 @@ class DialogLaunchAnimator @JvmOverloads constructor( * Ensure that all dialogs currently shown won't animate into their touch surface when * dismissed. * - * This is a temporary API meant to be called right before we both dismiss a dialog and start - * an activity, which currently does not look good if we animate the dialog into the touch - * surface at the same time as the activity starts. + * This is a temporary API meant to be called right before we both dismiss a dialog and start an + * activity, which currently does not look good if we animate the dialog into the touch surface + * at the same time as the activity starts. * * TODO(b/193634619): Remove this function and animate dialog into opening activity instead. */ @@ -295,8 +302,8 @@ private class AnimatedDialog( var touchSurface: View, /** - * A callback that will be called with this [AnimatedDialog] after the dialog was - * dismissed and the exit animation is done. + * A callback that will be called with this [AnimatedDialog] after the dialog was dismissed and + * the exit animation is done. */ private val onDialogDismissed: (AnimatedDialog) -> Unit, @@ -333,9 +340,7 @@ private class AnimatedDialog( */ var dialogContentWithBackground: ViewGroup? = null - /** - * The background color of [dialog], taking into consideration its window background color. - */ + /** The background color of [dialog], taking into consideration its window background color. */ private var originalDialogBackgroundColor = Color.BLACK /** @@ -353,11 +358,12 @@ private class AnimatedDialog( private var isOriginalDialogViewLaidOut = false /** A layout listener to animate the dialog height change. */ - private val backgroundLayoutListener = if (animateBackgroundBoundsChange) { - AnimatedBoundsLayoutListener() - } else { - null - } + private val backgroundLayoutListener = + if (animateBackgroundBoundsChange) { + AnimatedBoundsLayoutListener() + } else { + null + } /* * A layout listener in case the dialog (window) size changes (for instance because of a @@ -381,100 +387,117 @@ private class AnimatedDialog( val window = dialog.window!! val isWindowFullScreen = window.attributes.width == MATCH_PARENT && window.attributes.height == MATCH_PARENT - val dialogContentWithBackground = if (isWindowFullScreen) { - // If the dialog window is already fullscreen, then we look for the first ViewGroup that - // has a background (and is not the DecorView, which always has a background) and - // animate towards that ViewGroup given that this is probably what represents the actual - // dialog view. - var viewGroupWithBackground: ViewGroup? = null - for (i in 0 until decorView.childCount) { - viewGroupWithBackground = findFirstViewGroupWithBackground(decorView.getChildAt(i)) - if (viewGroupWithBackground != null) { - break + val dialogContentWithBackground = + if (isWindowFullScreen) { + // If the dialog window is already fullscreen, then we look for the first ViewGroup + // that has a background (and is not the DecorView, which always has a background) + // and animate towards that ViewGroup given that this is probably what represents + // the actual dialog view. + var viewGroupWithBackground: ViewGroup? = null + for (i in 0 until decorView.childCount) { + viewGroupWithBackground = + findFirstViewGroupWithBackground(decorView.getChildAt(i)) + if (viewGroupWithBackground != null) { + break + } } - } - // Animate that view with the background. Throw if we didn't find one, because otherwise - // it's not clear what we should animate. - viewGroupWithBackground - ?: throw IllegalStateException("Unable to find ViewGroup with background") - } else { - // We will make the dialog window (and therefore its DecorView) fullscreen to make it - // possible to animate outside its bounds. - // - // Before that, we add a new View as a child of the DecorView with the same size and - // gravity as that DecorView, then we add all original children of the DecorView to that - // new View. Finally we remove the background of the DecorView and add it to the new - // View, then we make the DecorView fullscreen. This new View now acts as a fake (non - // fullscreen) window. - // - // On top of that, we also add a fullscreen transparent background between the DecorView - // and the view that we added so that we can dismiss the dialog when this view is - // clicked. This is necessary because DecorView overrides onTouchEvent and therefore we - // can't set the click listener directly on the (now fullscreen) DecorView. - val fullscreenTransparentBackground = FrameLayout(dialog.context) - decorView.addView( - fullscreenTransparentBackground, - 0 /* index */, - FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT) - ) - - val dialogContentWithBackground = FrameLayout(dialog.context) - dialogContentWithBackground.background = decorView.background - - // Make the window background transparent. Note that setting the window (or DecorView) - // background drawable to null leads to issues with background color (not being - // transparent) or with insets that are not refreshed. Therefore we need to set it to - // something not null, hence we are using android.R.color.transparent here. - window.setBackgroundDrawableResource(android.R.color.transparent) - - // Close the dialog when clicking outside of it. - fullscreenTransparentBackground.setOnClickListener { dialog.dismiss() } - dialogContentWithBackground.isClickable = true - - // Make sure the transparent and dialog backgrounds are not focusable by accessibility - // features. - fullscreenTransparentBackground.importantForAccessibility = - View.IMPORTANT_FOR_ACCESSIBILITY_NO - dialogContentWithBackground.importantForAccessibility = - View.IMPORTANT_FOR_ACCESSIBILITY_NO - - fullscreenTransparentBackground.addView( - dialogContentWithBackground, - FrameLayout.LayoutParams( - window.attributes.width, - window.attributes.height, - window.attributes.gravity + // Animate that view with the background. Throw if we didn't find one, because + // otherwise + // it's not clear what we should animate. + viewGroupWithBackground + ?: throw IllegalStateException("Unable to find ViewGroup with background") + } else { + // We will make the dialog window (and therefore its DecorView) fullscreen to make + // it possible to animate outside its bounds. + // + // Before that, we add a new View as a child of the DecorView with the same size and + // gravity as that DecorView, then we add all original children of the DecorView to + // that new View. Finally we remove the background of the DecorView and add it to + // the new View, then we make the DecorView fullscreen. This new View now acts as a + // fake (non fullscreen) window. + // + // On top of that, we also add a fullscreen transparent background between the + // DecorView and the view that we added so that we can dismiss the dialog when this + // view is clicked. This is necessary because DecorView overrides onTouchEvent and + // therefore we can't set the click listener directly on the (now fullscreen) + // DecorView. + val fullscreenTransparentBackground = FrameLayout(dialog.context) + decorView.addView( + fullscreenTransparentBackground, + 0 /* index */, + FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT) ) - ) - // Move all original children of the DecorView to the new View we just added. - for (i in 1 until decorView.childCount) { - val view = decorView.getChildAt(1) - decorView.removeViewAt(1) - dialogContentWithBackground.addView(view) - } + val dialogContentWithBackground = FrameLayout(dialog.context) + dialogContentWithBackground.background = decorView.background + + // Make the window background transparent. Note that setting the window (or + // DecorView) background drawable to null leads to issues with background color (not + // being transparent) or with insets that are not refreshed. Therefore we need to + // set it to something not null, hence we are using android.R.color.transparent + // here. + window.setBackgroundDrawableResource(android.R.color.transparent) + + // Close the dialog when clicking outside of it. + fullscreenTransparentBackground.setOnClickListener { dialog.dismiss() } + dialogContentWithBackground.isClickable = true + + // Make sure the transparent and dialog backgrounds are not focusable by + // accessibility + // features. + fullscreenTransparentBackground.importantForAccessibility = + View.IMPORTANT_FOR_ACCESSIBILITY_NO + dialogContentWithBackground.importantForAccessibility = + View.IMPORTANT_FOR_ACCESSIBILITY_NO + + fullscreenTransparentBackground.addView( + dialogContentWithBackground, + FrameLayout.LayoutParams( + window.attributes.width, + window.attributes.height, + window.attributes.gravity + ) + ) - // Make the window fullscreen and add a layout listener to ensure it stays fullscreen. - window.setLayout(MATCH_PARENT, MATCH_PARENT) - decorViewLayoutListener = View.OnLayoutChangeListener { - v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom -> - if (window.attributes.width != MATCH_PARENT || - window.attributes.height != MATCH_PARENT - ) { - // The dialog size changed, copy its size to dialogContentWithBackground and - // make the dialog window full screen again. - val layoutParams = dialogContentWithBackground.layoutParams - layoutParams.width = window.attributes.width - layoutParams.height = window.attributes.height - dialogContentWithBackground.layoutParams = layoutParams - window.setLayout(MATCH_PARENT, MATCH_PARENT) + // Move all original children of the DecorView to the new View we just added. + for (i in 1 until decorView.childCount) { + val view = decorView.getChildAt(1) + decorView.removeViewAt(1) + dialogContentWithBackground.addView(view) } - } - decorView.addOnLayoutChangeListener(decorViewLayoutListener) - dialogContentWithBackground - } + // Make the window fullscreen and add a layout listener to ensure it stays + // fullscreen. + window.setLayout(MATCH_PARENT, MATCH_PARENT) + decorViewLayoutListener = + View.OnLayoutChangeListener { + v, + left, + top, + right, + bottom, + oldLeft, + oldTop, + oldRight, + oldBottom -> + if ( + window.attributes.width != MATCH_PARENT || + window.attributes.height != MATCH_PARENT + ) { + // The dialog size changed, copy its size to dialogContentWithBackground + // and make the dialog window full screen again. + val layoutParams = dialogContentWithBackground.layoutParams + layoutParams.width = window.attributes.width + layoutParams.height = window.attributes.height + dialogContentWithBackground.layoutParams = layoutParams + window.setLayout(MATCH_PARENT, MATCH_PARENT) + } + } + decorView.addOnLayoutChangeListener(decorViewLayoutListener) + + dialogContentWithBackground + } this.dialogContentWithBackground = dialogContentWithBackground dialogContentWithBackground.setTag(R.id.tag_dialog_background, true) @@ -482,7 +505,8 @@ private class AnimatedDialog( originalDialogBackgroundColor = GhostedViewLaunchAnimatorController.findGradientDrawable(background) ?.color - ?.defaultColor ?: Color.BLACK + ?.defaultColor + ?: Color.BLACK // Make the background view invisible until we start the animation. We use the transition // visibility like GhostView does so that we don't mess up with the accessibility tree (see @@ -508,24 +532,26 @@ private class AnimatedDialog( } // Start the animation once the background view is properly laid out. - dialogContentWithBackground.addOnLayoutChangeListener(object : View.OnLayoutChangeListener { - override fun onLayoutChange( - v: View, - left: Int, - top: Int, - right: Int, - bottom: Int, - oldLeft: Int, - oldTop: Int, - oldRight: Int, - oldBottom: Int - ) { - dialogContentWithBackground.removeOnLayoutChangeListener(this) - - isOriginalDialogViewLaidOut = true - maybeStartLaunchAnimation() + dialogContentWithBackground.addOnLayoutChangeListener( + object : View.OnLayoutChangeListener { + override fun onLayoutChange( + v: View, + left: Int, + top: Int, + right: Int, + bottom: Int, + oldLeft: Int, + oldTop: Int, + oldRight: Int, + oldBottom: Int + ) { + dialogContentWithBackground.removeOnLayoutChangeListener(this) + + isOriginalDialogViewLaidOut = true + maybeStartLaunchAnimation() + } } - }) + ) // Disable the dim. We will enable it once we start the animation. window.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND) @@ -551,10 +577,12 @@ private class AnimatedDialog( // Create a ghost of the touch surface (which will make the touch surface invisible) and add // it to the host dialog. We trigger a one off synchronization to make sure that this is // done in sync between the two different windows. - synchronizeNextDraw(then = { - isTouchSurfaceGhostDrawn = true - maybeStartLaunchAnimation() - }) + synchronizeNextDraw( + then = { + isTouchSurfaceGhostDrawn = true + maybeStartLaunchAnimation() + } + ) GhostView.addGhost(touchSurface, decorView) // The ghost of the touch surface was just created, so the touch surface is currently @@ -616,14 +644,13 @@ private class AnimatedDialog( onLaunchAnimationEnd = { touchSurface.setTag(R.id.tag_launch_animation_running, null) - // We hide the touch surface when the dialog is showing. We will make this - // view visible again when dismissing the dialog. + // We hide the touch surface when the dialog is showing. We will make this view + // visible again when dismissing the dialog. touchSurface.visibility = View.INVISIBLE isLaunching = false - // dismiss was called during the animation, dismiss again now to actually - // dismiss. + // dismiss was called during the animation, dismiss again now to actually dismiss. if (dismissRequested) { dialog.dismiss() } @@ -632,8 +659,9 @@ private class AnimatedDialog( // at the end of the launch animation, because the lauch animation already correctly // handles bounds changes. if (backgroundLayoutListener != null) { - dialogContentWithBackground!! - .addOnLayoutChangeListener(backgroundLayoutListener) + dialogContentWithBackground!!.addOnLayoutChangeListener( + backgroundLayoutListener + ) } cuj?.run { interactionJankMonitor.end(cujType) } } @@ -711,16 +739,19 @@ private class AnimatedDialog( dialogContentWithBackground.visibility = View.INVISIBLE if (backgroundLayoutListener != null) { - dialogContentWithBackground - .removeOnLayoutChangeListener(backgroundLayoutListener) + dialogContentWithBackground.removeOnLayoutChangeListener( + backgroundLayoutListener + ) } // Make sure that the removal of the ghost and making the touch surface visible is // done at the same time. - synchronizeNextDraw(then = { - onAnimationFinished(true /* instantDismiss */) - onDialogDismissed(this@AnimatedDialog) - }) + synchronizeNextDraw( + then = { + onAnimationFinished(true /* instantDismiss */) + onDialogDismissed(this@AnimatedDialog) + } + ) } ) } @@ -740,55 +771,56 @@ private class AnimatedDialog( endViewController.launchContainer = decorView val endState = endViewController.createAnimatorState() - val controller = object : LaunchAnimator.Controller { - override var launchContainer: ViewGroup - get() = startViewController.launchContainer - set(value) { - startViewController.launchContainer = value - endViewController.launchContainer = value - } + val controller = + object : LaunchAnimator.Controller { + override var launchContainer: ViewGroup + get() = startViewController.launchContainer + set(value) { + startViewController.launchContainer = value + endViewController.launchContainer = value + } - override fun createAnimatorState(): LaunchAnimator.State { - return startViewController.createAnimatorState() - } + override fun createAnimatorState(): LaunchAnimator.State { + return startViewController.createAnimatorState() + } - override fun onLaunchAnimationStart(isExpandingFullyAbove: Boolean) { - // During launch, onLaunchAnimationStart will be used to remove the temporary touch - // surface ghost so it is important to call this before calling - // onLaunchAnimationStart on the controller (which will create its own ghost). - onLaunchAnimationStart() + override fun onLaunchAnimationStart(isExpandingFullyAbove: Boolean) { + // During launch, onLaunchAnimationStart will be used to remove the temporary + // touch surface ghost so it is important to call this before calling + // onLaunchAnimationStart on the controller (which will create its own ghost). + onLaunchAnimationStart() - startViewController.onLaunchAnimationStart(isExpandingFullyAbove) - endViewController.onLaunchAnimationStart(isExpandingFullyAbove) - } + startViewController.onLaunchAnimationStart(isExpandingFullyAbove) + endViewController.onLaunchAnimationStart(isExpandingFullyAbove) + } - override fun onLaunchAnimationEnd(isExpandingFullyAbove: Boolean) { - startViewController.onLaunchAnimationEnd(isExpandingFullyAbove) - endViewController.onLaunchAnimationEnd(isExpandingFullyAbove) + override fun onLaunchAnimationEnd(isExpandingFullyAbove: Boolean) { + startViewController.onLaunchAnimationEnd(isExpandingFullyAbove) + endViewController.onLaunchAnimationEnd(isExpandingFullyAbove) - onLaunchAnimationEnd() - } + onLaunchAnimationEnd() + } - override fun onLaunchAnimationProgress( - state: LaunchAnimator.State, - progress: Float, - linearProgress: Float - ) { - startViewController.onLaunchAnimationProgress(state, progress, linearProgress) - - // The end view is visible only iff the starting view is not visible. - state.visible = !state.visible - endViewController.onLaunchAnimationProgress(state, progress, linearProgress) - - // If the dialog content is complex, its dimension might change during the launch - // animation. The animation end position might also change during the exit - // animation, for instance when locking the phone when the dialog is open. Therefore - // we update the end state to the new position/size. Usually the dialog dimension or - // position will change in the early frames, so changing the end state shouldn't - // really be noticeable. - endViewController.fillGhostedViewState(endState) + override fun onLaunchAnimationProgress( + state: LaunchAnimator.State, + progress: Float, + linearProgress: Float + ) { + startViewController.onLaunchAnimationProgress(state, progress, linearProgress) + + // The end view is visible only iff the starting view is not visible. + state.visible = !state.visible + endViewController.onLaunchAnimationProgress(state, progress, linearProgress) + + // If the dialog content is complex, its dimension might change during the + // launch animation. The animation end position might also change during the + // exit animation, for instance when locking the phone when the dialog is open. + // Therefore we update the end state to the new position/size. Usually the + // dialog dimension or position will change in the early frames, so changing the + // end state shouldn't really be noticeable. + endViewController.fillGhostedViewState(endState) + } } - } launchAnimator.startAnimation(controller, endState, originalDialogBackgroundColor) } @@ -821,7 +853,7 @@ private class AnimatedDialog( return (touchSurface.parent as? View)?.isShown ?: true } - /** A layout listener to animate the change of bounds of the dialog background. */ + /** A layout listener to animate the change of bounds of the dialog background. */ class AnimatedBoundsLayoutListener : View.OnLayoutChangeListener { companion object { private const val ANIMATION_DURATION = 500L @@ -866,32 +898,35 @@ private class AnimatedDialog( currentAnimator?.cancel() currentAnimator = null - val animator = ValueAnimator.ofFloat(0f, 1f).apply { - duration = ANIMATION_DURATION - interpolator = Interpolators.STANDARD - - addListener(object : AnimatorListenerAdapter() { - override fun onAnimationEnd(animation: Animator) { - currentAnimator = null + val animator = + ValueAnimator.ofFloat(0f, 1f).apply { + duration = ANIMATION_DURATION + interpolator = Interpolators.STANDARD + + addListener( + object : AnimatorListenerAdapter() { + override fun onAnimationEnd(animation: Animator) { + currentAnimator = null + } + } + ) + + addUpdateListener { animatedValue -> + val progress = animatedValue.animatedFraction + + // Compute new bounds. + bounds.left = MathUtils.lerp(startLeft, left, progress).roundToInt() + bounds.top = MathUtils.lerp(startTop, top, progress).roundToInt() + bounds.right = MathUtils.lerp(startRight, right, progress).roundToInt() + bounds.bottom = MathUtils.lerp(startBottom, bottom, progress).roundToInt() + + // Set the new bounds. + view.left = bounds.left + view.top = bounds.top + view.right = bounds.right + view.bottom = bounds.bottom } - }) - - addUpdateListener { animatedValue -> - val progress = animatedValue.animatedFraction - - // Compute new bounds. - bounds.left = MathUtils.lerp(startLeft, left, progress).roundToInt() - bounds.top = MathUtils.lerp(startTop, top, progress).roundToInt() - bounds.right = MathUtils.lerp(startRight, right, progress).roundToInt() - bounds.bottom = MathUtils.lerp(startBottom, bottom, progress).roundToInt() - - // Set the new bounds. - view.left = bounds.left - view.top = bounds.top - view.right = bounds.right - view.bottom = bounds.bottom } - } currentAnimator = animator animator.start() diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt index 3f7e0f0fb527..47f448d503c6 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt @@ -105,9 +105,7 @@ open class GhostedViewLaunchAnimatorController( } // Perform a BFS to find the largest View with background. - val views = LinkedList<View>().apply { - add(view) - } + val views = LinkedList<View>().apply { add(view) } while (views.isNotEmpty()) { val v = views.removeFirst() @@ -161,10 +159,11 @@ open class GhostedViewLaunchAnimatorController( } override fun createAnimatorState(): LaunchAnimator.State { - val state = LaunchAnimator.State( - topCornerRadius = getCurrentTopCornerRadius(), - bottomCornerRadius = getCurrentBottomCornerRadius() - ) + val state = + LaunchAnimator.State( + topCornerRadius = getCurrentTopCornerRadius(), + bottomCornerRadius = getCurrentBottomCornerRadius() + ) fillGhostedViewState(state) return state } @@ -255,13 +254,14 @@ open class GhostedViewLaunchAnimatorController( launchContainer.getLocationOnScreen(launchContainerLocation) ghostViewMatrix.postScale( - scale, scale, + scale, + scale, ghostedViewState.centerX - launchContainerLocation[0], ghostedViewState.centerY - launchContainerLocation[1] ) ghostViewMatrix.postTranslate( - (leftChange + rightChange) / 2f, - (topChange + bottomChange) / 2f + (leftChange + rightChange) / 2f, + (topChange + bottomChange) / 2f ) ghostView.animationMatrix = ghostViewMatrix diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchAnimator.kt index a4c5c3025220..9668066be125 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchAnimator.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchAnimator.kt @@ -34,10 +34,7 @@ import kotlin.math.roundToInt private const val TAG = "LaunchAnimator" /** A base class to animate a window launch (activity or dialog) from a view . */ -class LaunchAnimator( - private val timings: Timings, - private val interpolators: Interpolators -) { +class LaunchAnimator(private val timings: Timings, private val interpolators: Interpolators) { companion object { internal const val DEBUG = false private val SRC_MODE = PorterDuffXfermode(PorterDuff.Mode.SRC) @@ -75,10 +72,10 @@ class LaunchAnimator( * with the opening window. * * This will be used to: - * - Get the associated [Context]. - * - Compute whether we are expanding fully above the launch container. - * - Get to overlay to which we initially put the window background layer, until the - * opening window is made visible (see [openingWindowSyncView]). + * - Get the associated [Context]. + * - Compute whether we are expanding fully above the launch container. + * - Get to overlay to which we initially put the window background layer, until the opening + * window is made visible (see [openingWindowSyncView]). * * This container can be changed to force this [Controller] to animate the expanding view * inside a different location, for instance to ensure correct layering during the @@ -132,7 +129,6 @@ class LaunchAnimator( var bottom: Int = 0, var left: Int = 0, var right: Int = 0, - var topCornerRadius: Float = 0f, var bottomCornerRadius: Float = 0f ) { @@ -202,18 +198,20 @@ class LaunchAnimator( ) /** - * Start a launch animation controlled by [controller] towards [endState]. An intermediary - * layer with [windowBackgroundColor] will fade in then fade out above the expanding view, and - * should be the same background color as the opening (or closing) window. If [drawHole] is - * true, then this intermediary layer will be drawn with SRC blending mode while it fades out. + * Start a launch animation controlled by [controller] towards [endState]. An intermediary layer + * with [windowBackgroundColor] will fade in then (optionally) fade out above the expanding + * view, and should be the same background color as the opening (or closing) window. * - * TODO(b/184121838): Remove [drawHole] and instead make the StatusBar draw this hole instead. + * If [fadeOutWindowBackgroundLayer] is true, then this intermediary layer will fade out during + * the second half of the animation, and will have SRC blending mode (ultimately punching a hole + * in the [launch container][Controller.launchContainer]) iff [drawHole] is true. */ fun startAnimation( controller: Controller, endState: State, windowBackgroundColor: Int, - drawHole: Boolean = false + fadeOutWindowBackgroundLayer: Boolean = true, + drawHole: Boolean = false, ): Animation { val state = controller.createAnimatorState() @@ -238,8 +236,12 @@ class LaunchAnimator( val endBottomCornerRadius = endState.bottomCornerRadius fun maybeUpdateEndState() { - if (endTop != endState.top || endBottom != endState.bottom || - endLeft != endState.left || endRight != endState.right) { + if ( + endTop != endState.top || + endBottom != endState.bottom || + endLeft != endState.left || + endRight != endState.right + ) { endTop = endState.top endBottom = endState.bottom endLeft = endState.left @@ -256,10 +258,11 @@ class LaunchAnimator( // color, which is usually the same color of the app background. We first fade in this layer // to hide the expanding view, then we fade it out with SRC mode to draw a hole in the // launch container and reveal the opening window. - val windowBackgroundLayer = GradientDrawable().apply { - setColor(windowBackgroundColor) - alpha = 0 - } + val windowBackgroundLayer = + GradientDrawable().apply { + setColor(windowBackgroundColor) + alpha = 0 + } // Update state. val animator = ValueAnimator.ofFloat(0f, 1f) @@ -270,38 +273,41 @@ class LaunchAnimator( // [Controller.openingWindowSyncView] once the opening app window starts to be visible. val openingWindowSyncView = controller.openingWindowSyncView val openingWindowSyncViewOverlay = openingWindowSyncView?.overlay - val moveBackgroundLayerWhenAppIsVisible = openingWindowSyncView != null && - openingWindowSyncView.viewRootImpl != controller.launchContainer.viewRootImpl + val moveBackgroundLayerWhenAppIsVisible = + openingWindowSyncView != null && + openingWindowSyncView.viewRootImpl != controller.launchContainer.viewRootImpl val launchContainerOverlay = launchContainer.overlay var cancelled = false var movedBackgroundLayer = false - animator.addListener(object : AnimatorListenerAdapter() { - override fun onAnimationStart(animation: Animator?, isReverse: Boolean) { - if (DEBUG) { - Log.d(TAG, "Animation started") + animator.addListener( + object : AnimatorListenerAdapter() { + override fun onAnimationStart(animation: Animator?, isReverse: Boolean) { + if (DEBUG) { + Log.d(TAG, "Animation started") + } + controller.onLaunchAnimationStart(isExpandingFullyAbove) + + // Add the drawable to the launch container overlay. Overlays always draw + // drawables after views, so we know that it will be drawn above any view added + // by the controller. + launchContainerOverlay.add(windowBackgroundLayer) } - controller.onLaunchAnimationStart(isExpandingFullyAbove) - // Add the drawable to the launch container overlay. Overlays always draw - // drawables after views, so we know that it will be drawn above any view added - // by the controller. - launchContainerOverlay.add(windowBackgroundLayer) - } - - override fun onAnimationEnd(animation: Animator?) { - if (DEBUG) { - Log.d(TAG, "Animation ended") - } - controller.onLaunchAnimationEnd(isExpandingFullyAbove) - launchContainerOverlay.remove(windowBackgroundLayer) + override fun onAnimationEnd(animation: Animator?) { + if (DEBUG) { + Log.d(TAG, "Animation ended") + } + controller.onLaunchAnimationEnd(isExpandingFullyAbove) + launchContainerOverlay.remove(windowBackgroundLayer) - if (moveBackgroundLayerWhenAppIsVisible) { - openingWindowSyncViewOverlay?.remove(windowBackgroundLayer) + if (moveBackgroundLayerWhenAppIsVisible) { + openingWindowSyncViewOverlay?.remove(windowBackgroundLayer) + } } } - }) + ) animator.addUpdateListener { animation -> if (cancelled) { @@ -333,12 +339,13 @@ class LaunchAnimator( // The expanding view can/should be hidden once it is completely covered by the opening // window. - state.visible = getProgress( - timings, - linearProgress, - timings.contentBeforeFadeOutDelay, - timings.contentBeforeFadeOutDuration - ) < 1 + state.visible = + getProgress( + timings, + linearProgress, + timings.contentBeforeFadeOutDelay, + timings.contentBeforeFadeOutDuration + ) < 1 if (moveBackgroundLayerWhenAppIsVisible && !state.visible && !movedBackgroundLayer) { // The expanding view is not visible, so the opening app is visible. If this is the @@ -352,17 +359,19 @@ class LaunchAnimator( ViewRootSync.synchronizeNextDraw(launchContainer, openingWindowSyncView, then = {}) } - val container = if (movedBackgroundLayer) { - openingWindowSyncView!! - } else { - controller.launchContainer - } + val container = + if (movedBackgroundLayer) { + openingWindowSyncView!! + } else { + controller.launchContainer + } applyStateToWindowBackgroundLayer( windowBackgroundLayer, state, linearProgress, container, + fadeOutWindowBackgroundLayer, drawHole ) controller.onLaunchAnimationProgress(state, progress, linearProgress) @@ -391,6 +400,7 @@ class LaunchAnimator( state: State, linearProgress: Float, launchContainer: View, + fadeOutWindowBackgroundLayer: Boolean, drawHole: Boolean ) { // Update position. @@ -415,23 +425,25 @@ class LaunchAnimator( // We first fade in the background layer to hide the expanding view, then fade it out // with SRC mode to draw a hole punch in the status bar and reveal the opening window. - val fadeInProgress = getProgress( - timings, - linearProgress, - timings.contentBeforeFadeOutDelay, - timings.contentBeforeFadeOutDuration - ) + val fadeInProgress = + getProgress( + timings, + linearProgress, + timings.contentBeforeFadeOutDelay, + timings.contentBeforeFadeOutDuration + ) if (fadeInProgress < 1) { val alpha = interpolators.contentBeforeFadeOutInterpolator.getInterpolation(fadeInProgress) drawable.alpha = (alpha * 0xFF).roundToInt() - } else { - val fadeOutProgress = getProgress( - timings, - linearProgress, - timings.contentAfterFadeInDelay, - timings.contentAfterFadeInDuration - ) + } else if (fadeOutWindowBackgroundLayer) { + val fadeOutProgress = + getProgress( + timings, + linearProgress, + timings.contentAfterFadeInDelay, + timings.contentAfterFadeInDuration + ) val alpha = 1 - interpolators.contentAfterFadeInInterpolator.getInterpolation(fadeOutProgress) drawable.alpha = (alpha * 0xFF).roundToInt() @@ -439,6 +451,8 @@ class LaunchAnimator( if (drawHole) { drawable.setXfermode(SRC_MODE) } + } else { + drawable.alpha = 0xFF } } } diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchableView.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchableView.kt index 80a3eb839940..7499302c06b2 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchableView.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchableView.kt @@ -27,4 +27,4 @@ interface LaunchableView { * [transition][android.view.View.setTransitionVisibility] visibility changes must be blocked. */ fun setShouldBlockVisibilityChanges(block: Boolean) -}
\ No newline at end of file +} diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteTransitionAdapter.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteTransitionAdapter.kt index 47c11010c072..f9c6841f96b5 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteTransitionAdapter.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteTransitionAdapter.kt @@ -40,6 +40,7 @@ class RemoteTransitionAdapter { companion object { /** * Almost a copy of Transitions#setupStartState. + * * TODO: remove when there is proper cross-process transaction sync. */ @SuppressLint("NewApi") @@ -50,7 +51,8 @@ class RemoteTransitionAdapter { info: TransitionInfo, t: SurfaceControl.Transaction ) { - val isOpening = info.type == WindowManager.TRANSIT_OPEN || + val isOpening = + info.type == WindowManager.TRANSIT_OPEN || info.type == WindowManager.TRANSIT_TO_FRONT // Put animating stuff above this line and put static stuff below it. val zSplitLine = info.changes.size @@ -59,15 +61,19 @@ class RemoteTransitionAdapter { // Launcher animates leaf tasks directly, so always reparent all task leashes to root. t.reparent(leash, info.rootLeash) - t.setPosition(leash, (change.startAbsBounds.left - info.rootOffset.x).toFloat(), ( - change.startAbsBounds.top - info.rootOffset.y).toFloat()) + t.setPosition( + leash, + (change.startAbsBounds.left - info.rootOffset.x).toFloat(), + (change.startAbsBounds.top - info.rootOffset.y).toFloat() + ) t.show(leash) // Put all the OPEN/SHOW on top if (mode == WindowManager.TRANSIT_OPEN || mode == WindowManager.TRANSIT_TO_FRONT) { if (isOpening) { t.setLayer(leash, zSplitLine + info.changes.size - layer) - if (change.flags - and TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT == 0) { + if ( + change.flags and TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT == 0 + ) { // if transferred, it should be left visible. t.setAlpha(leash, 0f) } @@ -75,8 +81,9 @@ class RemoteTransitionAdapter { // put on bottom and leave it visible t.setLayer(leash, zSplitLine - layer) } - } else if (mode == WindowManager.TRANSIT_CLOSE || - mode == WindowManager.TRANSIT_TO_BACK) { + } else if ( + mode == WindowManager.TRANSIT_CLOSE || mode == WindowManager.TRANSIT_TO_BACK + ) { if (isOpening) { // put on bottom and leave visible t.setLayer(leash, zSplitLine - layer) @@ -102,10 +109,15 @@ class RemoteTransitionAdapter { // making leashes means we have to handle them specially. return change.leash } - val leashSurface = SurfaceControl.Builder() + val leashSurface = + SurfaceControl.Builder() .setName(change.leash.toString() + "_transition-leash") - .setContainerLayer().setParent(if (change.parent == null) - info.rootLeash else info.getChange(change.parent!!)!!.leash).build() + .setContainerLayer() + .setParent( + if (change.parent == null) info.rootLeash + else info.getChange(change.parent!!)!!.leash + ) + .build() // Copied Transitions setup code (which expects bottom-to-top order, so we swap here) setupLeash(leashSurface, change, info.changes.size - order, info, t) t.reparent(change.leash, leashSurface) @@ -118,10 +130,10 @@ class RemoteTransitionAdapter { private fun newModeToLegacyMode(newMode: Int): Int { return when (newMode) { - WindowManager.TRANSIT_OPEN, WindowManager.TRANSIT_TO_FRONT - -> RemoteAnimationTarget.MODE_OPENING - WindowManager.TRANSIT_CLOSE, WindowManager.TRANSIT_TO_BACK - -> RemoteAnimationTarget.MODE_CLOSING + WindowManager.TRANSIT_OPEN, + WindowManager.TRANSIT_TO_FRONT -> RemoteAnimationTarget.MODE_OPENING + WindowManager.TRANSIT_CLOSE, + WindowManager.TRANSIT_TO_BACK -> RemoteAnimationTarget.MODE_CLOSING else -> RemoteAnimationTarget.MODE_CHANGING } } @@ -138,12 +150,13 @@ class RemoteTransitionAdapter { info: TransitionInfo, t: SurfaceControl.Transaction ): RemoteAnimationTarget { - val target = RemoteAnimationTarget( + val target = + RemoteAnimationTarget( /* taskId */ if (change.taskInfo != null) change.taskInfo!!.taskId else -1, /* mode */ newModeToLegacyMode(change.mode), /* leash */ createLeash(info, change, order, t), /* isTranslucent */ (change.flags and TransitionInfo.FLAG_TRANSLUCENT != 0 || - change.flags and TransitionInfo.FLAG_SHOW_WALLPAPER != 0), + change.flags and TransitionInfo.FLAG_SHOW_WALLPAPER != 0), /* clipRect */ null, /* contentInsets */ Rect(0, 0, 0, 0), /* prefixOrderIndex */ order, @@ -151,15 +164,16 @@ class RemoteTransitionAdapter { /* localBounds */ rectOffsetTo(change.endAbsBounds, change.endRelOffset), /* screenSpaceBounds */ Rect(change.endAbsBounds), /* windowConfig */ if (change.taskInfo != null) - change.taskInfo!!.configuration.windowConfiguration else - WindowConfiguration(), - /* isNotInRecents */ if (change.taskInfo != null) - !change.taskInfo!!.isRunning else true, + change.taskInfo!!.configuration.windowConfiguration + else WindowConfiguration(), + /* isNotInRecents */ if (change.taskInfo != null) !change.taskInfo!!.isRunning + else true, /* startLeash */ null, /* startBounds */ Rect(change.startAbsBounds), /* taskInfo */ change.taskInfo, /* allowEnterPip */ change.allowEnterPip, - /* windowType */ WindowManager.LayoutParams.INVALID_WINDOW_TYPE) + /* windowType */ WindowManager.LayoutParams.INVALID_WINDOW_TYPE + ) target.backgroundColor = change.backgroundColor return target } @@ -192,9 +206,7 @@ class RemoteTransitionAdapter { } @JvmStatic - fun adaptRemoteRunner( - runner: IRemoteAnimationRunner - ): IRemoteTransition.Stub { + fun adaptRemoteRunner(runner: IRemoteAnimationRunner): IRemoteTransition.Stub { return object : IRemoteTransition.Stub() { override fun startAnimation( token: IBinder, @@ -218,18 +230,24 @@ class RemoteTransitionAdapter { var displayH = 0f for (i in info.changes.indices.reversed()) { val change = info.changes[i] - if (change.taskInfo != null && - change.taskInfo!!.activityType - == WindowConfiguration.ACTIVITY_TYPE_HOME) { - isReturnToHome = (change.mode == WindowManager.TRANSIT_OPEN || + if ( + change.taskInfo != null && + change.taskInfo!!.activityType == + WindowConfiguration.ACTIVITY_TYPE_HOME + ) { + isReturnToHome = + (change.mode == WindowManager.TRANSIT_OPEN || change.mode == WindowManager.TRANSIT_TO_FRONT) launcherTask = change launcherLayer = info.changes.size - i } else if (change.flags and TransitionInfo.FLAG_IS_WALLPAPER != 0) { wallpaper = change } - if (change.parent == null && change.endRotation >= 0 && - change.endRotation != change.startRotation) { + if ( + change.parent == null && + change.endRotation >= 0 && + change.endRotation != change.startRotation + ) { rotateDelta = change.endRotation - change.startRotation displayW = change.endAbsBounds.width().toFloat() displayH = change.endAbsBounds.height().toFloat() @@ -240,8 +258,13 @@ class RemoteTransitionAdapter { val counterLauncher = CounterRotator() val counterWallpaper = CounterRotator() if (launcherTask != null && rotateDelta != 0 && launcherTask.parent != null) { - counterLauncher.setup(t, info.getChange(launcherTask.parent!!)!!.leash, - rotateDelta, displayW, displayH) + counterLauncher.setup( + t, + info.getChange(launcherTask.parent!!)!!.leash, + rotateDelta, + displayW, + displayH + ) if (counterLauncher.surface != null) { t.setLayer(counterLauncher.surface!!, launcherLayer) } @@ -257,8 +280,10 @@ class RemoteTransitionAdapter { val mode = info.changes[i].mode // Only deal with independent layers if (!TransitionInfo.isIndependent(change, info)) continue - if (mode == WindowManager.TRANSIT_CLOSE || - mode == WindowManager.TRANSIT_TO_BACK) { + if ( + mode == WindowManager.TRANSIT_CLOSE || + mode == WindowManager.TRANSIT_TO_BACK + ) { t.setLayer(leash!!, info.changes.size * 3 - i) counterLauncher.addChild(t, leash) } @@ -273,8 +298,13 @@ class RemoteTransitionAdapter { counterLauncher.addChild(t, leashMap[launcherTask.leash]) } if (wallpaper != null && rotateDelta != 0 && wallpaper.parent != null) { - counterWallpaper.setup(t, info.getChange(wallpaper.parent!!)!!.leash, - rotateDelta, displayW, displayH) + counterWallpaper.setup( + t, + info.getChange(wallpaper.parent!!)!!.leash, + rotateDelta, + displayW, + displayH + ) if (counterWallpaper.surface != null) { t.setLayer(counterWallpaper.surface!!, -1) counterWallpaper.addChild(t, leashMap[wallpaper.leash]) @@ -282,37 +312,47 @@ class RemoteTransitionAdapter { } } t.apply() - val animationFinishedCallback = object : IRemoteAnimationFinishedCallback { - override fun onAnimationFinished() { - val finishTransaction = SurfaceControl.Transaction() - counterLauncher.cleanUp(finishTransaction) - counterWallpaper.cleanUp(finishTransaction) - // Release surface references now. This is apparently to free GPU memory - // while doing quick operations (eg. during CTS). - for (i in info.changes.indices.reversed()) { - info.changes[i].leash.release() - } - for (i in leashMap.size - 1 downTo 0) { - leashMap.valueAt(i).release() - } - try { - finishCallback.onTransitionFinished(null /* wct */, - finishTransaction) - } catch (e: RemoteException) { - Log.e("ActivityOptionsCompat", "Failed to call app controlled" + - " animation finished callback", e) + val animationFinishedCallback = + object : IRemoteAnimationFinishedCallback { + override fun onAnimationFinished() { + val finishTransaction = SurfaceControl.Transaction() + counterLauncher.cleanUp(finishTransaction) + counterWallpaper.cleanUp(finishTransaction) + // Release surface references now. This is apparently to free GPU + // memory while doing quick operations (eg. during CTS). + for (i in info.changes.indices.reversed()) { + info.changes[i].leash.release() + } + for (i in leashMap.size - 1 downTo 0) { + leashMap.valueAt(i).release() + } + try { + finishCallback.onTransitionFinished( + null /* wct */, + finishTransaction + ) + } catch (e: RemoteException) { + Log.e( + "ActivityOptionsCompat", + "Failed to call app controlled" + + " animation finished callback", + e + ) + } } - } - override fun asBinder(): IBinder? { - return null + override fun asBinder(): IBinder? { + return null + } } - } // TODO(bc-unlcok): Pass correct transit type. runner.onAnimationStart( - WindowManager.TRANSIT_OLD_NONE, - appsCompat, wallpapersCompat, nonAppsCompat, - animationFinishedCallback) + WindowManager.TRANSIT_OLD_NONE, + appsCompat, + wallpapersCompat, + nonAppsCompat, + animationFinishedCallback + ) } override fun mergeAnimation( @@ -329,18 +369,14 @@ class RemoteTransitionAdapter { } @JvmStatic - fun adaptRemoteAnimation( - adapter: RemoteAnimationAdapter - ): RemoteTransition { + fun adaptRemoteAnimation(adapter: RemoteAnimationAdapter): RemoteTransition { return RemoteTransition(adaptRemoteRunner(adapter.runner), adapter.callingApplication) } } - /** - * Utility class that takes care of counter-rotating surfaces during a transition animation. - */ + /** Utility class that takes care of counter-rotating surfaces during a transition animation. */ class CounterRotator { - /** Gets the surface with the counter-rotation. */ + /** Gets the surface with the counter-rotation. */ var surface: SurfaceControl? = null private set @@ -358,7 +394,8 @@ class RemoteTransitionAdapter { parentH: Float ) { if (rotateDelta == 0) return - val surface = SurfaceControl.Builder() + val surface = + SurfaceControl.Builder() .setName("Transition Unrotate") .setContainerLayer() .setParent(parent) @@ -378,21 +415,19 @@ class RemoteTransitionAdapter { t.show(surface) } - /** - * Adds a surface that needs to be counter-rotate. - */ + /** Adds a surface that needs to be counter-rotate. */ fun addChild(t: SurfaceControl.Transaction, child: SurfaceControl?) { if (surface == null) return t.reparent(child!!, surface) } /** - * Clean-up. Since finishTransaction should reset all change leashes, we only need to remove the - * counter rotation surface. + * Clean-up. Since finishTransaction should reset all change leashes, we only need to remove + * the counter rotation surface. */ fun cleanUp(finishTransaction: SurfaceControl.Transaction) { if (surface == null) return finishTransaction.remove(surface!!) } } -}
\ No newline at end of file +} diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ShadeInterpolation.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ShadeInterpolation.kt index 0ee2bfea55c5..a96f893a8db4 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/ShadeInterpolation.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ShadeInterpolation.kt @@ -31,7 +31,7 @@ object ShadeInterpolation { } else { val oneMinusFrac = 1f - mappedFraction (1f - 0.5f * (1f - Math.cos((3.14159f * oneMinusFrac * oneMinusFrac).toDouble()))) - .toFloat() + .toFloat() } } -}
\ No newline at end of file +} diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ViewHierarchyAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ViewHierarchyAnimator.kt index 093589f8c636..bba74793f3ed 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/ViewHierarchyAnimator.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ViewHierarchyAnimator.kt @@ -41,12 +41,13 @@ class ViewHierarchyAnimator { private val DEFAULT_REMOVAL_INTERPOLATOR = Interpolators.STANDARD_ACCELERATE /** The properties used to animate the view bounds. */ - private val PROPERTIES = mapOf( - Bound.LEFT to createViewProperty(Bound.LEFT), - Bound.TOP to createViewProperty(Bound.TOP), - Bound.RIGHT to createViewProperty(Bound.RIGHT), - Bound.BOTTOM to createViewProperty(Bound.BOTTOM) - ) + private val PROPERTIES = + mapOf( + Bound.LEFT to createViewProperty(Bound.LEFT), + Bound.TOP to createViewProperty(Bound.TOP), + Bound.RIGHT to createViewProperty(Bound.RIGHT), + Bound.BOTTOM to createViewProperty(Bound.BOTTOM) + ) private fun createViewProperty(bound: Bound): IntProperty<View> { return object : IntProperty<View>(bound.label) { @@ -103,7 +104,8 @@ class ViewHierarchyAnimator { duration: Long, ephemeral: Boolean ): Boolean { - if (!isVisible( + if ( + !isVisible( rootView.visibility, rootView.left, rootView.top, @@ -131,11 +133,7 @@ class ViewHierarchyAnimator { duration: Long, ephemeral: Boolean ): View.OnLayoutChangeListener { - return createListener( - interpolator, - duration, - ephemeral - ) + return createListener(interpolator, duration, ephemeral) } /** @@ -171,7 +169,8 @@ class ViewHierarchyAnimator { duration: Long = DEFAULT_DURATION, includeMargins: Boolean = false ): Boolean { - if (isVisible( + if ( + isVisible( rootView.visibility, rootView.left, rootView.top, @@ -182,9 +181,13 @@ class ViewHierarchyAnimator { return false } - val listener = createAdditionListener( - origin, interpolator, duration, ignorePreviousValues = !includeMargins - ) + val listener = + createAdditionListener( + origin, + interpolator, + duration, + ignorePreviousValues = !includeMargins + ) addListener(rootView, listener, recursive = true) return true } @@ -257,24 +260,26 @@ class ViewHierarchyAnimator { return } - val startValues = processStartValues( - origin, - left, - top, - right, - bottom, - startLeft, - startTop, - startRight, - startBottom, - ignorePreviousValues - ) - val endValues = mapOf( - Bound.LEFT to left, - Bound.TOP to top, - Bound.RIGHT to right, - Bound.BOTTOM to bottom - ) + val startValues = + processStartValues( + origin, + left, + top, + right, + bottom, + startLeft, + startTop, + startRight, + startBottom, + ignorePreviousValues + ) + val endValues = + mapOf( + Bound.LEFT to left, + Bound.TOP to top, + Bound.RIGHT to right, + Bound.BOTTOM to bottom + ) val boundsToAnimate = mutableSetOf<Bound>() if (startValues.getValue(Bound.LEFT) != left) boundsToAnimate.add(Bound.LEFT) @@ -313,7 +318,8 @@ class ViewHierarchyAnimator { interpolator: Interpolator = DEFAULT_REMOVAL_INTERPOLATOR, duration: Long = DEFAULT_DURATION ): Boolean { - if (!isVisible( + if ( + !isVisible( rootView.visibility, rootView.left, rootView.top, @@ -327,11 +333,7 @@ class ViewHierarchyAnimator { val parent = rootView.parent as ViewGroup // Ensure that rootView's siblings animate nicely around the removal. - val listener = createUpdateListener( - interpolator, - duration, - ephemeral = true - ) + val listener = createUpdateListener(interpolator, duration, ephemeral = true) for (i in 0 until parent.childCount) { val child = parent.getChildAt(i) if (child == rootView) continue @@ -346,19 +348,21 @@ class ViewHierarchyAnimator { // them manually during the animation. parent.overlay.add(rootView) - val startValues = mapOf( - Bound.LEFT to rootView.left, - Bound.TOP to rootView.top, - Bound.RIGHT to rootView.right, - Bound.BOTTOM to rootView.bottom - ) - val endValues = processEndValuesForRemoval( - destination, - rootView.left, - rootView.top, - rootView.right, - rootView.bottom - ) + val startValues = + mapOf( + Bound.LEFT to rootView.left, + Bound.TOP to rootView.top, + Bound.RIGHT to rootView.right, + Bound.BOTTOM to rootView.bottom + ) + val endValues = + processEndValuesForRemoval( + destination, + rootView.left, + rootView.top, + rootView.right, + rootView.bottom + ) val boundsToAnimate = mutableSetOf<Bound>() if (rootView.left != endValues.getValue(Bound.LEFT)) boundsToAnimate.add(Bound.LEFT) @@ -400,20 +404,24 @@ class ViewHierarchyAnimator { (animation.animatedValue as Float) * startAlphas[i] } } - animator.addListener(object : AnimatorListenerAdapter() { - override fun onAnimationEnd(animation: Animator) { - rootView.animate() - .alpha(0f) - .setInterpolator(Interpolators.ALPHA_OUT) - .setDuration(duration / 2) - .withEndAction { parent.overlay.remove(rootView) } - .start() + animator.addListener( + object : AnimatorListenerAdapter() { + override fun onAnimationEnd(animation: Animator) { + rootView + .animate() + .alpha(0f) + .setInterpolator(Interpolators.ALPHA_OUT) + .setDuration(duration / 2) + .withEndAction { parent.overlay.remove(rootView) } + .start() + } } - }) + ) animator.start() } else { // Fade out the view during the second half of the removal. - rootView.animate() + rootView + .animate() .alpha(0f) .setInterpolator(Interpolators.ALPHA_OUT) .setDuration(duration / 2) @@ -440,21 +448,23 @@ class ViewHierarchyAnimator { ) { for (i in 0 until rootView.childCount) { val child = rootView.getChildAt(i) - val childStartValues = mapOf( - Bound.LEFT to child.left, - Bound.TOP to child.top, - Bound.RIGHT to child.right, - Bound.BOTTOM to child.bottom - ) - val childEndValues = processChildEndValuesForRemoval( - destination, - child.left, - child.top, - child.right, - child.bottom, - endValues.getValue(Bound.RIGHT) - endValues.getValue(Bound.LEFT), - endValues.getValue(Bound.BOTTOM) - endValues.getValue(Bound.TOP) - ) + val childStartValues = + mapOf( + Bound.LEFT to child.left, + Bound.TOP to child.top, + Bound.RIGHT to child.right, + Bound.BOTTOM to child.bottom + ) + val childEndValues = + processChildEndValuesForRemoval( + destination, + child.left, + child.top, + child.right, + child.bottom, + endValues.getValue(Bound.RIGHT) - endValues.getValue(Bound.LEFT), + endValues.getValue(Bound.BOTTOM) - endValues.getValue(Bound.TOP) + ) val boundsToAnimate = mutableSetOf<Bound>() if (child.left != endValues.getValue(Bound.LEFT)) boundsToAnimate.add(Bound.LEFT) @@ -500,6 +510,7 @@ class ViewHierarchyAnimator { * not newly introduced margins are included. * * Base case + * ``` * 1) origin=TOP * x---------x x---------x x---------x x---------x x---------x * x---------x | | | | | | @@ -518,11 +529,11 @@ class ViewHierarchyAnimator { * x -> x---x -> | | -> | | -> | | * x-----x x-------x | | * x---------x - * + * ``` * In case the start and end values differ in the direction of the origin, and * [ignorePreviousValues] is false, the previous values are used and a translation is * included in addition to the view expansion. - * + * ``` * origin=TOP_LEFT - (0,0,0,0) -> (30,30,70,70) * x * x--x @@ -531,6 +542,7 @@ class ViewHierarchyAnimator { * x----x | | * | | * x------x + * ``` */ private fun processStartValues( origin: Hotspot?, @@ -555,42 +567,54 @@ class ViewHierarchyAnimator { var bottom = startBottom if (origin != null) { - left = when (origin) { - Hotspot.CENTER -> (newLeft + newRight) / 2 - Hotspot.BOTTOM_LEFT, Hotspot.LEFT, Hotspot.TOP_LEFT -> min(startLeft, newLeft) - Hotspot.TOP, Hotspot.BOTTOM -> newLeft - Hotspot.TOP_RIGHT, Hotspot.RIGHT, Hotspot.BOTTOM_RIGHT -> max( - startRight, - newRight - ) - } - top = when (origin) { - Hotspot.CENTER -> (newTop + newBottom) / 2 - Hotspot.TOP_LEFT, Hotspot.TOP, Hotspot.TOP_RIGHT -> min(startTop, newTop) - Hotspot.LEFT, Hotspot.RIGHT -> newTop - Hotspot.BOTTOM_RIGHT, Hotspot.BOTTOM, Hotspot.BOTTOM_LEFT -> max( - startBottom, - newBottom - ) - } - right = when (origin) { - Hotspot.CENTER -> (newLeft + newRight) / 2 - Hotspot.TOP_RIGHT, Hotspot.RIGHT, Hotspot.BOTTOM_RIGHT -> max( - startRight, - newRight - ) - Hotspot.TOP, Hotspot.BOTTOM -> newRight - Hotspot.BOTTOM_LEFT, Hotspot.LEFT, Hotspot.TOP_LEFT -> min(startLeft, newLeft) - } - bottom = when (origin) { - Hotspot.CENTER -> (newTop + newBottom) / 2 - Hotspot.BOTTOM_RIGHT, Hotspot.BOTTOM, Hotspot.BOTTOM_LEFT -> max( - startBottom, - newBottom - ) - Hotspot.LEFT, Hotspot.RIGHT -> newBottom - Hotspot.TOP_LEFT, Hotspot.TOP, Hotspot.TOP_RIGHT -> min(startTop, newTop) - } + left = + when (origin) { + Hotspot.CENTER -> (newLeft + newRight) / 2 + Hotspot.BOTTOM_LEFT, + Hotspot.LEFT, + Hotspot.TOP_LEFT -> min(startLeft, newLeft) + Hotspot.TOP, + Hotspot.BOTTOM -> newLeft + Hotspot.TOP_RIGHT, + Hotspot.RIGHT, + Hotspot.BOTTOM_RIGHT -> max(startRight, newRight) + } + top = + when (origin) { + Hotspot.CENTER -> (newTop + newBottom) / 2 + Hotspot.TOP_LEFT, + Hotspot.TOP, + Hotspot.TOP_RIGHT -> min(startTop, newTop) + Hotspot.LEFT, + Hotspot.RIGHT -> newTop + Hotspot.BOTTOM_RIGHT, + Hotspot.BOTTOM, + Hotspot.BOTTOM_LEFT -> max(startBottom, newBottom) + } + right = + when (origin) { + Hotspot.CENTER -> (newLeft + newRight) / 2 + Hotspot.TOP_RIGHT, + Hotspot.RIGHT, + Hotspot.BOTTOM_RIGHT -> max(startRight, newRight) + Hotspot.TOP, + Hotspot.BOTTOM -> newRight + Hotspot.BOTTOM_LEFT, + Hotspot.LEFT, + Hotspot.TOP_LEFT -> min(startLeft, newLeft) + } + bottom = + when (origin) { + Hotspot.CENTER -> (newTop + newBottom) / 2 + Hotspot.BOTTOM_RIGHT, + Hotspot.BOTTOM, + Hotspot.BOTTOM_LEFT -> max(startBottom, newBottom) + Hotspot.LEFT, + Hotspot.RIGHT -> newBottom + Hotspot.TOP_LEFT, + Hotspot.TOP, + Hotspot.TOP_RIGHT -> min(startTop, newTop) + } } return mapOf( @@ -606,6 +630,7 @@ class ViewHierarchyAnimator { * view's starting bounds. * * Examples: + * ``` * 1) destination=TOP * x---------x x---------x x---------x x---------x x---------x * | | | | | | x---------x @@ -624,6 +649,7 @@ class ViewHierarchyAnimator { * | | -> | | -> | | -> x---x -> x * | | x-------x x-----x * x---------x + * ``` */ private fun processEndValuesForRemoval( destination: Hotspot, @@ -632,32 +658,54 @@ class ViewHierarchyAnimator { right: Int, bottom: Int ): Map<Bound, Int> { - val endLeft = when (destination) { - Hotspot.CENTER -> (left + right) / 2 - Hotspot.BOTTOM, Hotspot.BOTTOM_LEFT, Hotspot.LEFT, Hotspot.TOP_LEFT, Hotspot.TOP -> - left - Hotspot.TOP_RIGHT, Hotspot.RIGHT, Hotspot.BOTTOM_RIGHT -> right - } - val endTop = when (destination) { - Hotspot.CENTER -> (top + bottom) / 2 - Hotspot.LEFT, Hotspot.TOP_LEFT, Hotspot.TOP, Hotspot.TOP_RIGHT, Hotspot.RIGHT -> - top - Hotspot.BOTTOM_RIGHT, Hotspot.BOTTOM, Hotspot.BOTTOM_LEFT -> bottom - } - val endRight = when (destination) { - Hotspot.CENTER -> (left + right) / 2 - Hotspot.TOP, Hotspot.TOP_RIGHT, Hotspot.RIGHT, - Hotspot.BOTTOM_RIGHT, Hotspot.BOTTOM -> - right - Hotspot.BOTTOM_LEFT, Hotspot.LEFT, Hotspot.TOP_LEFT -> left - } - val endBottom = when (destination) { - Hotspot.CENTER -> (top + bottom) / 2 - Hotspot.RIGHT, Hotspot.BOTTOM_RIGHT, Hotspot.BOTTOM, - Hotspot.BOTTOM_LEFT, Hotspot.LEFT -> - bottom - Hotspot.TOP_LEFT, Hotspot.TOP, Hotspot.TOP_RIGHT -> top - } + val endLeft = + when (destination) { + Hotspot.CENTER -> (left + right) / 2 + Hotspot.BOTTOM, + Hotspot.BOTTOM_LEFT, + Hotspot.LEFT, + Hotspot.TOP_LEFT, + Hotspot.TOP -> left + Hotspot.TOP_RIGHT, + Hotspot.RIGHT, + Hotspot.BOTTOM_RIGHT -> right + } + val endTop = + when (destination) { + Hotspot.CENTER -> (top + bottom) / 2 + Hotspot.LEFT, + Hotspot.TOP_LEFT, + Hotspot.TOP, + Hotspot.TOP_RIGHT, + Hotspot.RIGHT -> top + Hotspot.BOTTOM_RIGHT, + Hotspot.BOTTOM, + Hotspot.BOTTOM_LEFT -> bottom + } + val endRight = + when (destination) { + Hotspot.CENTER -> (left + right) / 2 + Hotspot.TOP, + Hotspot.TOP_RIGHT, + Hotspot.RIGHT, + Hotspot.BOTTOM_RIGHT, + Hotspot.BOTTOM -> right + Hotspot.BOTTOM_LEFT, + Hotspot.LEFT, + Hotspot.TOP_LEFT -> left + } + val endBottom = + when (destination) { + Hotspot.CENTER -> (top + bottom) / 2 + Hotspot.RIGHT, + Hotspot.BOTTOM_RIGHT, + Hotspot.BOTTOM, + Hotspot.BOTTOM_LEFT, + Hotspot.LEFT -> bottom + Hotspot.TOP_LEFT, + Hotspot.TOP, + Hotspot.TOP_RIGHT -> top + } return mapOf( Bound.LEFT to endLeft, @@ -675,6 +723,7 @@ class ViewHierarchyAnimator { * its center is at the [destination]. * * Examples: + * ``` * 1) destination=TOP * The child maintains its left and right positions, but is shifted up so that its * center is on the parent's end top edge. @@ -682,6 +731,7 @@ class ViewHierarchyAnimator { * The child shifts so that its center is on the parent's end bottom left corner. * 3) destination=CENTER * The child shifts so that its own center is on the parent's end center. + * ``` */ private fun processChildEndValuesForRemoval( destination: Hotspot, @@ -695,32 +745,54 @@ class ViewHierarchyAnimator { val halfWidth = (right - left) / 2 val halfHeight = (bottom - top) / 2 - val endLeft = when (destination) { - Hotspot.CENTER -> (parentWidth / 2) - halfWidth - Hotspot.BOTTOM_LEFT, Hotspot.LEFT, Hotspot.TOP_LEFT -> -halfWidth - Hotspot.TOP_RIGHT, Hotspot.RIGHT, Hotspot.BOTTOM_RIGHT -> parentWidth - halfWidth - Hotspot.TOP, Hotspot.BOTTOM -> left - } - val endTop = when (destination) { - Hotspot.CENTER -> (parentHeight / 2) - halfHeight - Hotspot.TOP_LEFT, Hotspot.TOP, Hotspot.TOP_RIGHT -> -halfHeight - Hotspot.BOTTOM_RIGHT, Hotspot.BOTTOM, Hotspot.BOTTOM_LEFT -> - parentHeight - halfHeight - Hotspot.LEFT, Hotspot.RIGHT -> top - } - val endRight = when (destination) { - Hotspot.CENTER -> (parentWidth / 2) + halfWidth - Hotspot.TOP_RIGHT, Hotspot.RIGHT, Hotspot.BOTTOM_RIGHT -> parentWidth + halfWidth - Hotspot.BOTTOM_LEFT, Hotspot.LEFT, Hotspot.TOP_LEFT -> halfWidth - Hotspot.TOP, Hotspot.BOTTOM -> right - } - val endBottom = when (destination) { - Hotspot.CENTER -> (parentHeight / 2) + halfHeight - Hotspot.BOTTOM_RIGHT, Hotspot.BOTTOM, Hotspot.BOTTOM_LEFT -> - parentHeight + halfHeight - Hotspot.TOP_LEFT, Hotspot.TOP, Hotspot.TOP_RIGHT -> halfHeight - Hotspot.LEFT, Hotspot.RIGHT -> bottom - } + val endLeft = + when (destination) { + Hotspot.CENTER -> (parentWidth / 2) - halfWidth + Hotspot.BOTTOM_LEFT, + Hotspot.LEFT, + Hotspot.TOP_LEFT -> -halfWidth + Hotspot.TOP_RIGHT, + Hotspot.RIGHT, + Hotspot.BOTTOM_RIGHT -> parentWidth - halfWidth + Hotspot.TOP, + Hotspot.BOTTOM -> left + } + val endTop = + when (destination) { + Hotspot.CENTER -> (parentHeight / 2) - halfHeight + Hotspot.TOP_LEFT, + Hotspot.TOP, + Hotspot.TOP_RIGHT -> -halfHeight + Hotspot.BOTTOM_RIGHT, + Hotspot.BOTTOM, + Hotspot.BOTTOM_LEFT -> parentHeight - halfHeight + Hotspot.LEFT, + Hotspot.RIGHT -> top + } + val endRight = + when (destination) { + Hotspot.CENTER -> (parentWidth / 2) + halfWidth + Hotspot.TOP_RIGHT, + Hotspot.RIGHT, + Hotspot.BOTTOM_RIGHT -> parentWidth + halfWidth + Hotspot.BOTTOM_LEFT, + Hotspot.LEFT, + Hotspot.TOP_LEFT -> halfWidth + Hotspot.TOP, + Hotspot.BOTTOM -> right + } + val endBottom = + when (destination) { + Hotspot.CENTER -> (parentHeight / 2) + halfHeight + Hotspot.BOTTOM_RIGHT, + Hotspot.BOTTOM, + Hotspot.BOTTOM_LEFT -> parentHeight + halfHeight + Hotspot.TOP_LEFT, + Hotspot.TOP, + Hotspot.TOP_RIGHT -> halfHeight + Hotspot.LEFT, + Hotspot.RIGHT -> bottom + } return mapOf( Bound.LEFT to endLeft, @@ -790,44 +862,49 @@ class ViewHierarchyAnimator { duration: Long, ephemeral: Boolean ) { - val propertyValuesHolders = buildList { - bounds.forEach { bound -> - add( - PropertyValuesHolder.ofInt( - PROPERTIES[bound], - startValues.getValue(bound), - endValues.getValue(bound) - ) - ) - } - }.toTypedArray() + val propertyValuesHolders = + buildList { + bounds.forEach { bound -> + add( + PropertyValuesHolder.ofInt( + PROPERTIES[bound], + startValues.getValue(bound), + endValues.getValue(bound) + ) + ) + } + } + .toTypedArray() (view.getTag(R.id.tag_animator) as? ObjectAnimator)?.cancel() val animator = ObjectAnimator.ofPropertyValuesHolder(view, *propertyValuesHolders) animator.interpolator = interpolator animator.duration = duration - animator.addListener(object : AnimatorListenerAdapter() { - var cancelled = false - - override fun onAnimationEnd(animation: Animator) { - view.setTag(R.id.tag_animator, null /* tag */) - bounds.forEach { view.setTag(it.overrideTag, null /* tag */) } - - // When an animation is cancelled, a new one might be taking over. We shouldn't - // unregister the listener yet. - if (ephemeral && !cancelled) { - // The duration is the same for the whole hierarchy, so it's safe to remove - // the listener recursively. We do this because some descendant views might - // not change bounds, and therefore not animate and leak the listener. - recursivelyRemoveListener(view) + animator.addListener( + object : AnimatorListenerAdapter() { + var cancelled = false + + override fun onAnimationEnd(animation: Animator) { + view.setTag(R.id.tag_animator, null /* tag */) + bounds.forEach { view.setTag(it.overrideTag, null /* tag */) } + + // When an animation is cancelled, a new one might be taking over. We + // shouldn't unregister the listener yet. + if (ephemeral && !cancelled) { + // The duration is the same for the whole hierarchy, so it's safe to + // remove the listener recursively. We do this because some descendant + // views might not change bounds, and therefore not animate and leak the + // listener. + recursivelyRemoveListener(view) + } } - } - override fun onAnimationCancel(animation: Animator?) { - cancelled = true + override fun onAnimationCancel(animation: Animator?) { + cancelled = true + } } - }) + ) bounds.forEach { bound -> setBound(view, bound, startValues.getValue(bound)) } @@ -838,7 +915,15 @@ class ViewHierarchyAnimator { /** An enum used to determine the origin of addition animations. */ enum class Hotspot { - CENTER, LEFT, TOP_LEFT, TOP, TOP_RIGHT, RIGHT, BOTTOM_RIGHT, BOTTOM, BOTTOM_LEFT + CENTER, + LEFT, + TOP_LEFT, + TOP, + TOP_RIGHT, + RIGHT, + BOTTOM_RIGHT, + BOTTOM, + BOTTOM_LEFT } private enum class Bound(val label: String, val overrideTag: Int) { diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ViewRootSync.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ViewRootSync.kt index 76de7b503451..77640f1992e1 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/ViewRootSync.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ViewRootSync.kt @@ -11,37 +11,36 @@ object ViewRootSync { /** * Synchronize the next draw between the view roots of [view] and [otherView], then run [then]. * - * Note that in some cases, the synchronization might not be possible (e.g. WM consumed the - * next transactions) or disabled (temporarily, on low ram devices). In this case, [then] will - * be called without synchronizing. + * Note that in some cases, the synchronization might not be possible (e.g. WM consumed the next + * transactions) or disabled (temporarily, on low ram devices). In this case, [then] will be + * called without synchronizing. */ - fun synchronizeNextDraw( - view: View, - otherView: View, - then: () -> Unit - ) { - if (!view.isAttachedToWindow || view.viewRootImpl == null || - !otherView.isAttachedToWindow || otherView.viewRootImpl == null || - view.viewRootImpl == otherView.viewRootImpl) { + fun synchronizeNextDraw(view: View, otherView: View, then: () -> Unit) { + if ( + !view.isAttachedToWindow || + view.viewRootImpl == null || + !otherView.isAttachedToWindow || + otherView.viewRootImpl == null || + view.viewRootImpl == otherView.viewRootImpl + ) { // No need to synchronize if either the touch surface or dialog view is not attached // to a window. then() return } - surfaceSyncer = SurfaceSyncer().apply { - val syncId = setupSync(Runnable { then() }) - addToSync(syncId, view) - addToSync(syncId, otherView) - markSyncReady(syncId) - } + surfaceSyncer = + SurfaceSyncer().apply { + val syncId = setupSync(Runnable { then() }) + addToSync(syncId, view) + addToSync(syncId, otherView) + markSyncReady(syncId) + } } - /** - * A Java-friendly API for [synchronizeNextDraw]. - */ + /** A Java-friendly API for [synchronizeNextDraw]. */ @JvmStatic fun synchronizeNextDraw(view: View, otherView: View, then: Runnable) { synchronizeNextDraw(view, otherView, then::run) } -}
\ No newline at end of file +} |