From 4bc0c59af03649374022aada14ac24f97099706a Mon Sep 17 00:00:00 2001 From: Jordan Demeulenaere Date: Fri, 10 Jun 2022 12:07:24 +0200 Subject: [PATCH 1/2] Format Kotlin code in SysUI Animation lib using ktfmt Test: Manual Bug: 235461679 Change-Id: Id14bee4bdecc02f3ed6cc4788282a4903d4b7149 --- .../animation/ActivityLaunchAnimator.kt | 268 +++++----- .../DelegateLaunchAnimatorController.kt | 2 +- .../animation/DialogLaunchAnimator.kt | 499 ++++++++++-------- .../GhostedViewLaunchAnimatorController.kt | 20 +- .../systemui/animation/LaunchAnimator.kt | 136 ++--- .../systemui/animation/LaunchableView.kt | 2 +- .../animation/RemoteTransitionAdapter.kt | 187 ++++--- .../systemui/animation/ShadeInterpolation.kt | 4 +- .../animation/ViewHierarchyAnimator.kt | 479 ++++++++++------- .../systemui/animation/ViewRootSync.kt | 41 +- 10 files changed, 913 insertions(+), 725 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 f934b1f3ab99a..02f8524a64f01 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) + launchResult == ActivityManager.START_SUCCESS || + (launchResult == ActivityManager.START_DELIVERED_TO_TOP && + hideKeyguardWithAnimation) - Log.i(TAG, "launchResult=$launchResult willAnimate=$willAnimate " + - "hideKeyguardWithAnimation=$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) {} } /** @@ -394,9 +400,7 @@ class ActivityLaunchAnimator( return } - context.mainExecutor.execute { - startAnimation(apps, nonApps, iCallback) - } + context.mainExecutor.execute { startAnimation(apps, nonApps, iCallback) } } private fun startAnimation( @@ -408,9 +412,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,77 +422,88 @@ 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) - } - - 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) + val controller = + object : LaunchAnimator.Controller by delegate { + override fun onLaunchAnimationStart(isExpandingFullyAbove: Boolean) { + listeners.forEach { it.onLaunchAnimationStart() } + delegate.onLaunchAnimationStart(isExpandingFullyAbove) } - navigationBar?.let { applyStateToNavigationBar(it, state, linearProgress) } - listeners.forEach { it.onLaunchAnimationProgress(linearProgress) } - delegate.onLaunchAnimationProgress(state, progress, linearProgress) + override fun onLaunchAnimationEnd(isExpandingFullyAbove: Boolean) { + listeners.forEach { it.onLaunchAnimationEnd() } + iCallback?.invoke() + delegate.onLaunchAnimationEnd(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) + } + 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, + drawHole = true + ) } private fun applyStateToWindow(window: RemoteAnimationTarget, state: LaunchAnimator.State) { @@ -541,13 +554,14 @@ class ActivityLaunchAnimator( // 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(1f) + .withMatrix(matrix) + .withWindowCrop(windowCrop) + .withCornerRadius(cornerRadius) + .withVisibility(true) + .build() transactionApplier.scheduleApply(params) } @@ -563,14 +577,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 +599,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 258ca6bdf79b3..b879ba0b1ece8 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 48231e30ba6da..cb16d7c3471f1 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 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) + // 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 + ) + ) + + // 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) + 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() + 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,56 +771,57 @@ 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() + + startViewController.onLaunchAnimationStart(isExpandingFullyAbove) + endViewController.onLaunchAnimationStart(isExpandingFullyAbove) + } + + override fun onLaunchAnimationEnd(isExpandingFullyAbove: Boolean) { + startViewController.onLaunchAnimationEnd(isExpandingFullyAbove) + endViewController.onLaunchAnimationEnd(isExpandingFullyAbove) + + 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 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) - } - - override fun onLaunchAnimationEnd(isExpandingFullyAbove: Boolean) { - startViewController.onLaunchAnimationEnd(isExpandingFullyAbove) - endViewController.onLaunchAnimationEnd(isExpandingFullyAbove) - - 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) - } - } - 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 + val animator = + ValueAnimator.ofFloat(0f, 1f).apply { + duration = ANIMATION_DURATION + interpolator = Interpolators.STANDARD - addListener(object : AnimatorListenerAdapter() { - override fun onAnimationEnd(animation: Animator) { - currentAnimator = null + 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 3f7e0f0fb5274..47f448d503c6e 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().apply { - add(view) - } + val views = LinkedList().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 a4c5c30252209..47a9802b7be85 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,10 +198,10 @@ 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 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. * * TODO(b/184121838): Remove [drawHole] and instead make the StatusBar draw this hole instead. */ @@ -238,8 +234,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 +256,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 +271,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 +337,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,11 +357,12 @@ 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, @@ -415,23 +421,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 - ) + val fadeOutProgress = + getProgress( + timings, + linearProgress, + timings.contentAfterFadeInDelay, + timings.contentAfterFadeInDuration + ) val alpha = 1 - interpolators.contentAfterFadeInInterpolator.getInterpolation(fadeOutProgress) drawable.alpha = (alpha * 0xFF).roundToInt() 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 80a3eb839940a..7499302c06b22 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 47c11010c072e..f9c6841f96b56 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() + 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 + ) + } } - 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 0ee2bfea55c51..a96f893a8db4f 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 093589f8c636a..bba74793f3edc 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 { return object : IntProperty(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() 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() 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() 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 { - 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 + 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 */) } + 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) + // 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 76de7b503451d..77640f1992e10 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 +} From 7bb867220fd86ca517b393030b61cfbc1dab3e36 Mon Sep 17 00:00:00 2001 From: Jordan Demeulenaere Date: Thu, 2 Jun 2022 15:02:38 +0200 Subject: [PATCH 2/2] Support Activity launches above the expandable This CL adds support for activity launches where the opening window is opening above the expandable surface. Before this CL, we would automatically assume that the opening window would be below the expandable surface, which is currently always the case given that the animator is used only in the shade and status bar at the moment (and those are above any app that we open). After this CL, the ActivityLaunchAnimator has an additional parameter for launches that happen above the expandable surface, which happen when launching an Activity from a normal app or from a launcher. Bug: 235576267 Test: Manual Change-Id: I49c6095ffe52119d117af6c597463542b4630bbd --- .../animation/ActivityLaunchAnimator.kt | 44 ++++++++++++++++--- .../systemui/animation/LaunchAnimator.kt | 18 +++++--- 2 files changed, 51 insertions(+), 11 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 02f8524a64f01..5b47ae5259190 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt @@ -332,6 +332,17 @@ class ActivityLaunchAnimator( val isDialogLaunch: Boolean 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. @@ -468,7 +479,7 @@ class ActivityLaunchAnimator( // We animate the opening window and delegate the view expansion to [this.controller]. val delegate = this.controller val controller = - object : LaunchAnimator.Controller by delegate { + object : Controller by delegate { override fun onLaunchAnimationStart(isExpandingFullyAbove: Boolean) { listeners.forEach { it.onLaunchAnimationStart() } delegate.onLaunchAnimationStart(isExpandingFullyAbove) @@ -488,7 +499,7 @@ class ActivityLaunchAnimator( // 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) + applyStateToWindow(window, state, linearProgress) } navigationBar?.let { applyStateToNavigationBar(it, state, linearProgress) } @@ -502,11 +513,16 @@ class ActivityLaunchAnimator( controller, endState, windowBackgroundColor, - drawHole = true + 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). @@ -548,6 +564,24 @@ 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 @@ -556,7 +590,7 @@ class ActivityLaunchAnimator( val cornerRadius = maxOf(state.topCornerRadius, state.bottomCornerRadius) / scale val params = SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(window.leash) - .withAlpha(1f) + .withAlpha(alpha) .withMatrix(matrix) .withWindowCrop(windowCrop) .withCornerRadius(cornerRadius) 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 47a9802b7be85..9668066be1259 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchAnimator.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchAnimator.kt @@ -199,17 +199,19 @@ class LaunchAnimator(private val timings: Timings, private val interpolators: In /** * 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. + * 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() @@ -369,6 +371,7 @@ class LaunchAnimator(private val timings: Timings, private val interpolators: In state, linearProgress, container, + fadeOutWindowBackgroundLayer, drawHole ) controller.onLaunchAnimationProgress(state, progress, linearProgress) @@ -397,6 +400,7 @@ class LaunchAnimator(private val timings: Timings, private val interpolators: In state: State, linearProgress: Float, launchContainer: View, + fadeOutWindowBackgroundLayer: Boolean, drawHole: Boolean ) { // Update position. @@ -432,7 +436,7 @@ class LaunchAnimator(private val timings: Timings, private val interpolators: In val alpha = interpolators.contentBeforeFadeOutInterpolator.getInterpolation(fadeInProgress) drawable.alpha = (alpha * 0xFF).roundToInt() - } else { + } else if (fadeOutWindowBackgroundLayer) { val fadeOutProgress = getProgress( timings, @@ -447,6 +451,8 @@ class LaunchAnimator(private val timings: Timings, private val interpolators: In if (drawHole) { drawable.setXfermode(SRC_MODE) } + } else { + drawable.alpha = 0xFF } } }