639 lines
24 KiB
Kotlin
639 lines
24 KiB
Kotlin
/*
|
|
* Copyright (C) 2024 The Android Open Source Project
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
package com.android.wm.shell.back
|
|
|
|
import android.animation.Animator
|
|
import android.animation.AnimatorListenerAdapter
|
|
import android.animation.ValueAnimator
|
|
import android.content.Context
|
|
import android.content.res.Configuration
|
|
import android.graphics.Color
|
|
import android.graphics.Matrix
|
|
import android.graphics.PointF
|
|
import android.graphics.Rect
|
|
import android.graphics.RectF
|
|
import android.os.Handler
|
|
import android.os.RemoteException
|
|
import android.util.TimeUtils
|
|
import android.view.Choreographer
|
|
import android.view.Display
|
|
import android.view.IRemoteAnimationFinishedCallback
|
|
import android.view.IRemoteAnimationRunner
|
|
import android.view.RemoteAnimationTarget
|
|
import android.view.SurfaceControl
|
|
import android.view.animation.DecelerateInterpolator
|
|
import android.view.animation.Interpolator
|
|
import android.view.animation.Transformation
|
|
import android.window.BackEvent
|
|
import android.window.BackEvent.EDGE_LEFT
|
|
import android.window.BackEvent.EDGE_RIGHT
|
|
import android.window.BackMotionEvent
|
|
import android.window.BackNavigationInfo
|
|
import android.window.BackProgressAnimator
|
|
import android.window.DesktopExperienceFlags
|
|
import android.window.IOnBackInvokedCallback
|
|
import com.android.internal.dynamicanimation.animation.FloatValueHolder
|
|
import com.android.internal.dynamicanimation.animation.SpringAnimation
|
|
import com.android.internal.dynamicanimation.animation.SpringForce
|
|
import com.android.internal.jank.Cuj
|
|
import com.android.internal.policy.ScreenDecorationsUtils
|
|
import com.android.internal.policy.SystemBarUtils
|
|
import com.android.internal.protolog.ProtoLog
|
|
import com.android.window.flags2.Flags.predictiveBackTimestampApi
|
|
import com.android.wm.shell.R
|
|
import com.android.wm.shell.RootTaskDisplayAreaOrganizer
|
|
import com.android.wm.shell.protolog.ShellProtoLogGroup
|
|
import com.android.wm.shell.shared.animation.Interpolators
|
|
import com.android.wm.shell.shared.annotations.ShellMainThread
|
|
import kotlin.math.abs
|
|
import kotlin.math.max
|
|
import kotlin.math.min
|
|
|
|
abstract class CrossActivityBackAnimation(
|
|
private val context: Context,
|
|
private val background: BackAnimationBackground,
|
|
private val rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer,
|
|
protected val transaction: SurfaceControl.Transaction,
|
|
@ShellMainThread handler: Handler,
|
|
) : ShellBackAnimation() {
|
|
|
|
protected val startClosingRect = RectF()
|
|
protected val targetClosingRect = RectF()
|
|
protected val currentClosingRect = RectF()
|
|
|
|
protected val startEnteringRect = RectF()
|
|
protected val targetEnteringRect = RectF()
|
|
protected val currentEnteringRect = RectF()
|
|
|
|
protected val backAnimRect = Rect()
|
|
private val cropRect = Rect()
|
|
private val tempRectF = RectF()
|
|
|
|
private var cornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(context)
|
|
private var statusbarHeight = SystemBarUtils.getStatusBarHeight(context)
|
|
|
|
private val backAnimationRunner =
|
|
BackAnimationRunner(
|
|
Callback(),
|
|
Runner(),
|
|
context,
|
|
Cuj.CUJ_PREDICTIVE_BACK_CROSS_ACTIVITY,
|
|
handler,
|
|
)
|
|
private val initialTouchPos = PointF()
|
|
private val transformMatrix = Matrix()
|
|
private val tmpFloat9 = FloatArray(9)
|
|
protected var enteringTarget: RemoteAnimationTarget? = null
|
|
protected var closingTarget: RemoteAnimationTarget? = null
|
|
private var triggerBack = false
|
|
private var finishCallback: IRemoteAnimationFinishedCallback? = null
|
|
private val progressAnimator = BackProgressAnimator()
|
|
protected val displayBoundsMargin =
|
|
context.resources.getDimension(R.dimen.cross_task_back_vertical_margin)
|
|
|
|
private val gestureInterpolator = Interpolators.BACK_GESTURE
|
|
private val verticalMoveInterpolator: Interpolator = DecelerateInterpolator()
|
|
|
|
private var scrimLayer: SurfaceControl? = null
|
|
private var maxScrimAlpha: Float = 0f
|
|
|
|
private var isLetterboxed = false
|
|
private var enteringHasSameLetterbox = false
|
|
private var leftLetterboxLayer: SurfaceControl? = null
|
|
private var rightLetterboxLayer: SurfaceControl? = null
|
|
private var letterboxColor: Int = 0
|
|
|
|
private val postCommitFlingScale = FloatValueHolder(SPRING_SCALE)
|
|
private var lastPostCommitFlingScale = SPRING_SCALE
|
|
private val postCommitFlingSpring = SpringForce(SPRING_SCALE)
|
|
.setStiffness(SpringForce.STIFFNESS_LOW)
|
|
.setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY)
|
|
private var swipeEdge = EDGE_LEFT
|
|
protected var gestureProgress = 0f
|
|
private val velocityTracker = ProgressVelocityTracker()
|
|
|
|
/** Background color to be used during the animation, also see [getBackgroundColor] */
|
|
protected var customizedBackgroundColor = 0
|
|
|
|
/**
|
|
* Whether the entering target should be shifted vertically with the user gesture in pre-commit
|
|
*/
|
|
abstract val allowEnteringYShift: Boolean
|
|
|
|
/**
|
|
* Subclasses must set the [startClosingRect] and [targetClosingRect] to define the movement
|
|
* of the closingTarget during pre-commit phase.
|
|
*/
|
|
abstract fun preparePreCommitClosingRectMovement(@BackEvent.SwipeEdge swipeEdge: Int)
|
|
|
|
/**
|
|
* Subclasses must set the [startEnteringRect] and [targetEnteringRect] to define the movement
|
|
* of the enteringTarget during pre-commit phase.
|
|
*/
|
|
abstract fun preparePreCommitEnteringRectMovement()
|
|
|
|
/**
|
|
* Subclasses must provide a duration (in ms) for the post-commit part of the animation
|
|
*/
|
|
abstract fun getPostCommitAnimationDuration(): Long
|
|
|
|
/**
|
|
* Returns a base transformation to apply to the entering target during pre-commit. The system
|
|
* will apply the default animation on top of it.
|
|
*/
|
|
protected open fun getPreCommitEnteringBaseTransformation(progress: Float): Transformation? =
|
|
null
|
|
|
|
override fun onConfigurationChanged(newConfiguration: Configuration) {
|
|
cornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(context)
|
|
statusbarHeight = SystemBarUtils.getStatusBarHeight(context)
|
|
}
|
|
|
|
override fun getRunner() = backAnimationRunner
|
|
|
|
private fun getBackgroundColor(): Int =
|
|
when {
|
|
customizedBackgroundColor != 0 -> customizedBackgroundColor
|
|
isLetterboxed -> letterboxColor
|
|
enteringTarget != null -> enteringTarget!!.taskInfo.taskDescription!!.backgroundColor
|
|
else -> 0
|
|
}
|
|
|
|
protected open fun startBackAnimation(backMotionEvent: BackMotionEvent) {
|
|
if (enteringTarget == null || closingTarget == null) {
|
|
ProtoLog.d(
|
|
ShellProtoLogGroup.WM_SHELL_BACK_PREVIEW,
|
|
"Entering target or closing target is null."
|
|
)
|
|
return
|
|
}
|
|
swipeEdge = backMotionEvent.swipeEdge
|
|
triggerBack = backMotionEvent.triggerBack
|
|
initialTouchPos.set(backMotionEvent.touchX, backMotionEvent.touchY)
|
|
|
|
transaction.setAnimationTransaction()
|
|
isLetterboxed = closingTarget!!.taskInfo.appCompatTaskInfo.isTopActivityLetterboxed
|
|
enteringHasSameLetterbox =
|
|
isLetterboxed && closingTarget!!.localBounds.equals(enteringTarget!!.localBounds)
|
|
|
|
if (isLetterboxed && !enteringHasSameLetterbox) {
|
|
// Play animation with letterboxes, if closing and entering target have mismatching
|
|
// letterboxes
|
|
backAnimRect.set(closingTarget!!.windowConfiguration.bounds)
|
|
} else {
|
|
// otherwise play animation on localBounds only
|
|
backAnimRect.set(closingTarget!!.localBounds)
|
|
}
|
|
// Offset start rectangle to align task bounds.
|
|
backAnimRect.offsetTo(0, 0)
|
|
|
|
preparePreCommitClosingRectMovement(backMotionEvent.swipeEdge)
|
|
preparePreCommitEnteringRectMovement()
|
|
|
|
background.ensureBackground(
|
|
closingTarget!!.windowConfiguration.bounds,
|
|
getBackgroundColor(),
|
|
transaction,
|
|
statusbarHeight,
|
|
if (closingTarget!!.windowConfiguration.tasksAreFloating())
|
|
closingTarget!!.localBounds else null,
|
|
cornerRadius,
|
|
closingTarget!!.taskInfo.getDisplayId()
|
|
)
|
|
ensureScrimLayer()
|
|
if (isLetterboxed && enteringHasSameLetterbox) {
|
|
// crop left and right letterboxes
|
|
cropRect.set(
|
|
closingTarget!!.localBounds.left,
|
|
0,
|
|
closingTarget!!.localBounds.right,
|
|
closingTarget!!.windowConfiguration.bounds.height()
|
|
)
|
|
// and add fake letterbox square surfaces instead
|
|
ensureLetterboxes()
|
|
} else {
|
|
cropRect.set(backAnimRect)
|
|
}
|
|
applyTransaction()
|
|
}
|
|
|
|
private fun onGestureProgress(backEvent: BackEvent) {
|
|
val progress = gestureInterpolator.getInterpolation(backEvent.progress)
|
|
gestureProgress = progress
|
|
currentClosingRect.setInterpolatedRectF(startClosingRect, targetClosingRect, progress)
|
|
val yOffset = getYOffset(currentClosingRect, backEvent.touchY)
|
|
currentClosingRect.offset(0f, yOffset)
|
|
applyTransform(closingTarget?.leash, currentClosingRect, 1f)
|
|
currentEnteringRect.setInterpolatedRectF(startEnteringRect, targetEnteringRect, progress)
|
|
if (allowEnteringYShift) currentEnteringRect.offset(0f, yOffset)
|
|
val enteringTransformation = getPreCommitEnteringBaseTransformation(progress)
|
|
applyTransform(
|
|
enteringTarget?.leash,
|
|
currentEnteringRect,
|
|
enteringTransformation?.alpha ?: 1f,
|
|
enteringTransformation
|
|
)
|
|
applyTransaction()
|
|
background.customizeStatusBarAppearance(currentClosingRect.top.toInt())
|
|
if (predictiveBackTimestampApi()) {
|
|
velocityTracker.addPosition(backEvent.frameTimeMillis, progress)
|
|
}
|
|
}
|
|
|
|
private fun getYOffset(centeredRect: RectF, touchY: Float): Float {
|
|
val screenHeight = backAnimRect.height()
|
|
// Base the window movement in the Y axis on the touch movement in the Y axis.
|
|
val rawYDelta = touchY - initialTouchPos.y
|
|
val yDirection = (if (rawYDelta < 0) -1 else 1)
|
|
// limit yDelta interpretation to 1/2 of screen height in either direction
|
|
val deltaYRatio = min(screenHeight / 2f, abs(rawYDelta)) / (screenHeight / 2f)
|
|
val interpolatedYRatio: Float = verticalMoveInterpolator.getInterpolation(deltaYRatio)
|
|
// limit y-shift so surface never passes 8dp screen margin
|
|
val deltaY =
|
|
max(0f, (screenHeight - centeredRect.height()) / 2f - displayBoundsMargin) *
|
|
interpolatedYRatio *
|
|
yDirection
|
|
return deltaY
|
|
}
|
|
|
|
protected open fun onGestureCommitted(velocity: Float) {
|
|
if (
|
|
closingTarget?.leash == null ||
|
|
enteringTarget?.leash == null ||
|
|
!enteringTarget!!.leash.isValid ||
|
|
!closingTarget!!.leash.isValid
|
|
) {
|
|
finishAnimation()
|
|
return
|
|
}
|
|
|
|
// kick off spring animation with the current velocity from the pre-commit phase, this
|
|
// affects the scaling of the closing and/or opening activity during post-commit
|
|
|
|
var startVelocity = if (predictiveBackTimestampApi()) {
|
|
// pronounce fling animation more for gestures
|
|
val velocityFactor = if (swipeEdge == EDGE_LEFT || swipeEdge == EDGE_RIGHT) 2f else 1f
|
|
velocity * SPRING_SCALE * (1f - MAX_SCALE) * velocityFactor
|
|
} else {
|
|
velocity * SPRING_SCALE
|
|
}
|
|
if (gestureProgress < 0.1f) {
|
|
startVelocity = startVelocity.coerceAtLeast(DEFAULT_FLING_VELOCITY)
|
|
}
|
|
val flingAnimation = SpringAnimation(postCommitFlingScale, SPRING_SCALE)
|
|
.setStartVelocity(-startVelocity.coerceIn(0f, MAX_FLING_VELOCITY))
|
|
.setStartValue(SPRING_SCALE)
|
|
.setSpring(postCommitFlingSpring)
|
|
flingAnimation.start()
|
|
// do an animation-frame immediately to prevent idle frame
|
|
flingAnimation.doAnimationFrame(
|
|
Choreographer.getInstance().lastFrameTimeNanos / TimeUtils.NANOS_PER_MS
|
|
)
|
|
|
|
val valueAnimator =
|
|
ValueAnimator.ofFloat(1f, 0f).setDuration(getPostCommitAnimationDuration())
|
|
valueAnimator.addUpdateListener { animation: ValueAnimator ->
|
|
val progress = animation.animatedFraction
|
|
onPostCommitProgress(progress)
|
|
if (progress > 1 - BackAnimationConstants.UPDATE_SYSUI_FLAGS_THRESHOLD) {
|
|
background.resetStatusBarCustomization()
|
|
}
|
|
}
|
|
valueAnimator.addListener(
|
|
object : AnimatorListenerAdapter() {
|
|
override fun onAnimationEnd(animation: Animator) {
|
|
background.resetStatusBarCustomization()
|
|
finishAnimation()
|
|
}
|
|
}
|
|
)
|
|
valueAnimator.start()
|
|
}
|
|
|
|
protected open fun onPostCommitProgress(linearProgress: Float) {
|
|
scrimLayer?.let { transaction.setAlpha(it, maxScrimAlpha * (1f - linearProgress)) }
|
|
}
|
|
|
|
protected open fun finishAnimation() {
|
|
enteringTarget?.let {
|
|
if (it.leash != null && it.leash.isValid) {
|
|
transaction.setCornerRadius(it.leash, 0f)
|
|
if (!triggerBack) transaction.setAlpha(it.leash, 0f)
|
|
it.leash.release()
|
|
}
|
|
enteringTarget = null
|
|
}
|
|
|
|
closingTarget?.leash?.release()
|
|
closingTarget = null
|
|
|
|
background.removeBackground(transaction)
|
|
applyTransaction()
|
|
transformMatrix.reset()
|
|
initialTouchPos.set(0f, 0f)
|
|
try {
|
|
finishCallback?.onAnimationFinished()
|
|
} catch (e: RemoteException) {
|
|
e.printStackTrace()
|
|
}
|
|
finishCallback = null
|
|
removeScrimLayer()
|
|
removeLetterbox()
|
|
isLetterboxed = false
|
|
enteringHasSameLetterbox = false
|
|
lastPostCommitFlingScale = SPRING_SCALE
|
|
gestureProgress = 0f
|
|
triggerBack = false
|
|
velocityTracker.resetTracking()
|
|
}
|
|
|
|
protected fun applyTransform(
|
|
leash: SurfaceControl?,
|
|
rect: RectF,
|
|
alpha: Float,
|
|
baseTransformation: Transformation? = null,
|
|
flingMode: FlingMode = FlingMode.NO_FLING
|
|
) {
|
|
if (leash == null || !leash.isValid) return
|
|
tempRectF.set(rect)
|
|
if (flingMode != FlingMode.NO_FLING) {
|
|
lastPostCommitFlingScale = min(
|
|
postCommitFlingScale.value / SPRING_SCALE,
|
|
if (flingMode == FlingMode.FLING_BOUNCE) 1f else lastPostCommitFlingScale
|
|
)
|
|
// apply an additional scale to the closing target to account for fling velocity
|
|
tempRectF.scaleCentered(lastPostCommitFlingScale)
|
|
}
|
|
val scale = tempRectF.width() / backAnimRect.width()
|
|
val matrix = baseTransformation?.matrix ?: transformMatrix.apply { reset() }
|
|
val scalePivotX =
|
|
if (isLetterboxed && enteringHasSameLetterbox) {
|
|
closingTarget!!.localBounds.left.toFloat()
|
|
} else {
|
|
0f
|
|
}
|
|
matrix.postScale(scale, scale, scalePivotX, 0f)
|
|
matrix.postTranslate(tempRectF.left, tempRectF.top)
|
|
transaction
|
|
.setAlpha(leash, alpha)
|
|
.setMatrix(leash, matrix, tmpFloat9)
|
|
.setCrop(leash, cropRect)
|
|
.setCornerRadius(leash, cornerRadius)
|
|
}
|
|
|
|
protected fun applyTransaction() {
|
|
transaction.setFrameTimelineVsync(Choreographer.getInstance().vsyncId)
|
|
transaction.apply()
|
|
}
|
|
|
|
private fun ensureScrimLayer() {
|
|
if (scrimLayer != null) return
|
|
val isDarkTheme: Boolean = isDarkMode(context)
|
|
val scrimBuilder =
|
|
SurfaceControl.Builder()
|
|
.setName("Cross-Activity back animation scrim")
|
|
.setCallsite("CrossActivityBackAnimation")
|
|
.setColorLayer()
|
|
.setOpaque(false)
|
|
.setHidden(false)
|
|
|
|
if (DesktopExperienceFlags.ENABLE_MULTIDISPLAY_TRACKPAD_BACK_GESTURE.isTrue()) {
|
|
rootTaskDisplayAreaOrganizer.attachToDisplayArea(
|
|
closingTarget!!.taskInfo.getDisplayId(), scrimBuilder)
|
|
} else {
|
|
rootTaskDisplayAreaOrganizer.attachToDisplayArea(Display.DEFAULT_DISPLAY, scrimBuilder)
|
|
}
|
|
scrimLayer = scrimBuilder.build()
|
|
val colorComponents = floatArrayOf(0f, 0f, 0f)
|
|
maxScrimAlpha = if (isDarkTheme) MAX_SCRIM_ALPHA_DARK else MAX_SCRIM_ALPHA_LIGHT
|
|
val scrimCrop =
|
|
if (isLetterboxed) {
|
|
closingTarget!!.windowConfiguration.bounds
|
|
} else {
|
|
closingTarget!!.localBounds
|
|
}
|
|
transaction
|
|
.setColor(scrimLayer, colorComponents)
|
|
.setAlpha(scrimLayer!!, maxScrimAlpha)
|
|
.setCrop(scrimLayer!!, scrimCrop)
|
|
.setRelativeLayer(scrimLayer!!, closingTarget!!.leash, -1)
|
|
.show(scrimLayer)
|
|
}
|
|
|
|
private fun removeScrimLayer() {
|
|
if (removeLayer(scrimLayer)) applyTransaction()
|
|
scrimLayer = null
|
|
}
|
|
|
|
/**
|
|
* Adds two "fake" letterbox square surfaces to the left and right of the localBounds of the
|
|
* closing target
|
|
*/
|
|
private fun ensureLetterboxes() {
|
|
closingTarget?.let { t ->
|
|
if (t.localBounds.left != 0 && leftLetterboxLayer == null) {
|
|
val bounds =
|
|
Rect(
|
|
0,
|
|
t.windowConfiguration.bounds.top,
|
|
t.localBounds.left,
|
|
t.windowConfiguration.bounds.bottom
|
|
)
|
|
leftLetterboxLayer = ensureLetterbox(bounds)
|
|
}
|
|
if (
|
|
t.localBounds.right != t.windowConfiguration.bounds.right &&
|
|
rightLetterboxLayer == null
|
|
) {
|
|
val bounds =
|
|
Rect(
|
|
t.localBounds.right,
|
|
t.windowConfiguration.bounds.top,
|
|
t.windowConfiguration.bounds.right,
|
|
t.windowConfiguration.bounds.bottom
|
|
)
|
|
rightLetterboxLayer = ensureLetterbox(bounds)
|
|
}
|
|
}
|
|
}
|
|
|
|
private fun ensureLetterbox(bounds: Rect): SurfaceControl {
|
|
val letterboxBuilder =
|
|
SurfaceControl.Builder()
|
|
.setName("Cross-Activity back animation letterbox")
|
|
.setCallsite("CrossActivityBackAnimation")
|
|
.setColorLayer()
|
|
.setOpaque(true)
|
|
.setHidden(false)
|
|
|
|
if (DesktopExperienceFlags.ENABLE_MULTIDISPLAY_TRACKPAD_BACK_GESTURE.isTrue()) {
|
|
rootTaskDisplayAreaOrganizer.attachToDisplayArea(
|
|
closingTarget!!.taskInfo.getDisplayId(), letterboxBuilder)
|
|
} else {
|
|
rootTaskDisplayAreaOrganizer.attachToDisplayArea(
|
|
Display.DEFAULT_DISPLAY, letterboxBuilder)
|
|
}
|
|
val layer = letterboxBuilder.build()
|
|
val colorComponents =
|
|
floatArrayOf(
|
|
Color.red(letterboxColor) / 255f,
|
|
Color.green(letterboxColor) / 255f,
|
|
Color.blue(letterboxColor) / 255f
|
|
)
|
|
transaction
|
|
.setColor(layer, colorComponents)
|
|
.setCrop(layer, bounds)
|
|
.setRelativeLayer(layer, closingTarget!!.leash, 1)
|
|
.show(layer)
|
|
return layer
|
|
}
|
|
|
|
private fun removeLetterbox() {
|
|
if (removeLayer(leftLetterboxLayer) || removeLayer(rightLetterboxLayer)) applyTransaction()
|
|
leftLetterboxLayer = null
|
|
rightLetterboxLayer = null
|
|
}
|
|
|
|
private fun removeLayer(layer: SurfaceControl?): Boolean {
|
|
layer?.let {
|
|
if (it.isValid) {
|
|
transaction.remove(it)
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
override fun prepareNextAnimation(
|
|
animationInfo: BackNavigationInfo.CustomAnimationInfo?,
|
|
letterboxColor: Int
|
|
): Boolean {
|
|
this.letterboxColor = letterboxColor
|
|
return false
|
|
}
|
|
|
|
private inner class Callback : IOnBackInvokedCallback.Default() {
|
|
override fun onBackStarted(backMotionEvent: BackMotionEvent) {
|
|
// in case we're still animating an onBackCancelled event, let's remove the finish-
|
|
// callback from the progress animator to prevent calling finishAnimation() before
|
|
// restarting a new animation
|
|
progressAnimator.removeOnBackCancelledFinishCallback()
|
|
|
|
startBackAnimation(backMotionEvent)
|
|
progressAnimator.onBackStarted(backMotionEvent) { backEvent: BackEvent ->
|
|
onGestureProgress(backEvent)
|
|
}
|
|
}
|
|
|
|
override fun onBackProgressed(backEvent: BackMotionEvent) {
|
|
triggerBack = backEvent.triggerBack
|
|
progressAnimator.onBackProgressed(backEvent)
|
|
}
|
|
|
|
override fun onBackCancelled() {
|
|
triggerBack = false
|
|
progressAnimator.onBackCancelled { finishAnimation() }
|
|
}
|
|
|
|
override fun onBackInvoked() {
|
|
triggerBack = true
|
|
progressAnimator.reset()
|
|
if (predictiveBackTimestampApi()) {
|
|
onGestureCommitted(velocityTracker.calculateVelocity())
|
|
} else {
|
|
onGestureCommitted(progressAnimator.velocity)
|
|
}
|
|
}
|
|
}
|
|
|
|
private inner class Runner : IRemoteAnimationRunner.Default() {
|
|
override fun onAnimationStart(
|
|
transit: Int,
|
|
apps: Array<RemoteAnimationTarget>,
|
|
wallpapers: Array<RemoteAnimationTarget>?,
|
|
nonApps: Array<RemoteAnimationTarget>?,
|
|
finishedCallback: IRemoteAnimationFinishedCallback
|
|
) {
|
|
ProtoLog.d(
|
|
ShellProtoLogGroup.WM_SHELL_BACK_PREVIEW,
|
|
"Start back to activity animation."
|
|
)
|
|
for (a in apps) {
|
|
when (a.mode) {
|
|
RemoteAnimationTarget.MODE_CLOSING -> closingTarget = a
|
|
RemoteAnimationTarget.MODE_OPENING -> enteringTarget = a
|
|
}
|
|
}
|
|
finishCallback = finishedCallback
|
|
}
|
|
|
|
override fun onAnimationCancelled() {
|
|
finishAnimation()
|
|
}
|
|
}
|
|
|
|
companion object {
|
|
/** Max scale of the closing window. */
|
|
internal const val MAX_SCALE = 0.9f
|
|
private const val MAX_SCRIM_ALPHA_DARK = 0.8f
|
|
private const val MAX_SCRIM_ALPHA_LIGHT = 0.2f
|
|
private const val SPRING_SCALE = 100f
|
|
private const val MAX_FLING_VELOCITY = 1000f
|
|
private const val DEFAULT_FLING_VELOCITY = 120f
|
|
}
|
|
|
|
enum class FlingMode {
|
|
NO_FLING,
|
|
|
|
/**
|
|
* This is used for the closing target in custom cross-activity back animations. When the
|
|
* back gesture is flung, the closing target shrinks a bit further with a spring motion.
|
|
*/
|
|
FLING_SHRINK,
|
|
|
|
/**
|
|
* This is used for the closing and opening target in the default cross-activity back
|
|
* animation. When the back gesture is flung, the closing and opening targets shrink a
|
|
* bit further and then bounce back with a spring motion.
|
|
*/
|
|
FLING_BOUNCE
|
|
}
|
|
}
|
|
|
|
private fun isDarkMode(context: Context): Boolean {
|
|
return context.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK ==
|
|
Configuration.UI_MODE_NIGHT_YES
|
|
}
|
|
|
|
internal fun RectF.setInterpolatedRectF(start: RectF, target: RectF, progress: Float) {
|
|
require(!(progress < 0 || progress > 1)) { "Progress value must be between 0 and 1" }
|
|
left = start.left + (target.left - start.left) * progress
|
|
top = start.top + (target.top - start.top) * progress
|
|
right = start.right + (target.right - start.right) * progress
|
|
bottom = start.bottom + (target.bottom - start.bottom) * progress
|
|
}
|
|
|
|
internal fun RectF.scaleCentered(
|
|
scale: Float,
|
|
pivotX: Float = left + width() / 2,
|
|
pivotY: Float = top + height() / 2
|
|
) {
|
|
offset(-pivotX, -pivotY) // move pivot to origin
|
|
scale(scale)
|
|
offset(pivotX, pivotY) // Move back to the original position
|
|
}
|