diff --git a/Android.bp b/Android.bp index 4ef905c15e..d10ff1a908 100644 --- a/Android.bp +++ b/Android.bp @@ -390,6 +390,7 @@ android_library { "//frameworks/libs/systemui:view_capture", "//frameworks/libs/systemui:animationlib", "//frameworks/libs/systemui:contextualeducationlib", + "//frameworks/libs/systemui:mechanics", "//frameworks/libs/systemui:msdl", "SystemUI-statsd", "WindowManager-Shell-shared-AOSP", diff --git a/quickstep/res/values/dimens.xml b/quickstep/res/values/dimens.xml index 067d784895..7519393454 100644 --- a/quickstep/res/values/dimens.xml +++ b/quickstep/res/values/dimens.xml @@ -94,6 +94,8 @@ 25dp + + 100dp 72dp 1.1 diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewDismissTouchController.kt b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewDismissTouchController.kt index f2507da24a..1a7f7b0a47 100644 --- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewDismissTouchController.kt +++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewDismissTouchController.kt @@ -34,6 +34,18 @@ import com.android.launcher3.statemanager.StatefulContainer import com.android.launcher3.touch.SingleAxisSwipeDetector import com.android.launcher3.util.MSDLPlayerWrapper import com.android.launcher3.util.TouchController +import com.android.mechanics.spec.Breakpoint +import com.android.mechanics.spec.Breakpoint.Companion.maxLimit +import com.android.mechanics.spec.Breakpoint.Companion.minLimit +import com.android.mechanics.spec.BreakpointKey +import com.android.mechanics.spec.DirectionalMotionSpec +import com.android.mechanics.spec.Guarantee +import com.android.mechanics.spec.InputDirection +import com.android.mechanics.spec.Mapping +import com.android.mechanics.spec.MotionSpec +import com.android.mechanics.spring.SpringParameters +import com.android.mechanics.view.DistanceGestureContext +import com.android.mechanics.view.ViewMotionValue import com.android.quickstep.views.RecentsView import com.android.quickstep.views.RecentsView.RECENTS_SCALE_PROPERTY import com.android.quickstep.views.RecentsViewContainer @@ -59,6 +71,10 @@ CONTAINER : StatefulContainer { ) private val isRtl = isRtl(container.resources) private val upDirection: Int = recentsView.pagedOrientationHandler.getUpDirection(isRtl) + private val maxUndershoot = + container.resources.getDimension(R.dimen.task_dismiss_max_undershoot) + private val detachThreshold = + container.resources.getDimension(R.dimen.task_dismiss_detach_threshold) private val stateListener = object : StateListener { override fun onStateTransitionStart(toState: T) { @@ -69,6 +85,7 @@ CONTAINER : StatefulContainer { private val tempTaskThumbnailBounds = Rect() private var taskBeingDragged: TaskView? = null + private var taskDragDisplacementValue: ViewMotionValue? = null private var springAnimation: SpringAnimation? = null private var dismissLength: Int = 0 private var verticalFactor: Int = 0 @@ -196,6 +213,24 @@ CONTAINER : StatefulContainer { initialDisplacement = taskBeingDragged.secondaryDismissTranslationProperty.get(taskBeingDragged) + taskDragDisplacementValue = + generateMotionValue( + initialDisplacement, + detachThreshold * verticalFactor, + container.asContext(), + ) { currentDisplacement -> + taskBeingDragged.secondaryDismissTranslationProperty.setValue( + taskBeingDragged, + currentDisplacement, + ) + if (taskBeingDragged.isRunningTask && recentsView.enableDrawingLiveTile) { + recentsView.runActionOnRemoteHandles { remoteTargetHandle -> + remoteTargetHandle.taskViewSimulator.taskSecondaryTranslation.value = + currentDisplacement + } + recentsView.redrawLiveTile() + } + } // Add a tiny bit of translation Z, so that it draws on top of other views. This is relevant // (e.g.) when we dismiss a task by sliding it upward: if there is a row of icons above, we @@ -205,35 +240,26 @@ CONTAINER : StatefulContainer { override fun onDrag(displacement: Float): Boolean { if (isBlockedDuringDismissal) return true - val taskBeingDragged = taskBeingDragged ?: return false + taskBeingDragged ?: return false val currentDisplacement = displacement + initialDisplacement val boundedDisplacement = boundToRange(abs(currentDisplacement), 0f, dismissLength.toFloat()) // When swiping below origin, allow slight undershoot to simulate resisting the movement. + val isAboveOrigin = + recentsView.pagedOrientationHandler.isGoingUp(currentDisplacement, isRtl) val totalDisplacement = - if (recentsView.pagedOrientationHandler.isGoingUp(currentDisplacement, isRtl)) - boundedDisplacement * verticalFactor + if (isAboveOrigin) boundedDisplacement * verticalFactor else mapToRange( boundedDisplacement, 0f, dismissLength.toFloat(), 0f, - container.resources.getDimension(R.dimen.task_dismiss_max_undershoot), + maxUndershoot, DECELERATE, ) * -verticalFactor - taskBeingDragged.secondaryDismissTranslationProperty.setValue( - taskBeingDragged, - totalDisplacement, - ) - if (taskBeingDragged.isRunningTask && recentsView.enableDrawingLiveTile) { - recentsView.runActionOnRemoteHandles { remoteTargetHandle -> - remoteTargetHandle.taskViewSimulator.taskSecondaryTranslation.value = - totalDisplacement - } - recentsView.redrawLiveTile() - } val dismissFraction = displacement / (dismissLength * verticalFactor).toFloat() + taskDragDisplacementValue?.input = totalDisplacement RECENTS_SCALE_PROPERTY.setValue(recentsView, getRecentsScale(dismissFraction)) playDismissThresholdHaptic(displacement) return true @@ -262,6 +288,7 @@ CONTAINER : StatefulContainer { override fun onDragEnd(velocity: Float) { if (isBlockedDuringDismissal) return val taskBeingDragged = taskBeingDragged ?: return + taskDragDisplacementValue?.dispose() val currentDisplacement = taskBeingDragged.secondaryDismissTranslationProperty.get(taskBeingDragged) @@ -299,6 +326,7 @@ CONTAINER : StatefulContainer { taskBeingDragged?.translationZ = 0f taskBeingDragged = null springAnimation = null + taskDragDisplacementValue = null isBlockedDuringDismissal = false } @@ -341,6 +369,43 @@ CONTAINER : StatefulContainer { } } + private fun generateMotionValue( + initialDisplacement: Float, + detachThreshold: Float, + context: Context, + updateCallback: (Float) -> Unit, + ): ViewMotionValue { + val direction = if (initialDisplacement < 0) InputDirection.Max else InputDirection.Min + val distanceGestureContext = + DistanceGestureContext.create(context, initialDisplacement, direction) + val viewMotionValue = + ViewMotionValue( + initialDisplacement, + distanceGestureContext, + generateMotionSpec(detachThreshold), + label = "taskDismiss::displacement", + ) + + viewMotionValue.addUpdateCallback { motionValue -> updateCallback(motionValue.output) } + return viewMotionValue + } + + /** Motion spec for an initial magnetic detach. Track linearly otherwise. No reattach. */ + private fun generateMotionSpec(detachThreshold: Float): MotionSpec { + val spring = SpringParameters(stiffness = 800f, dampingRatio = 0.95f) + val detachKey = BreakpointKey("TaskDismiss::Detach") + val breakpoints = mutableListOf() + val mappings = mutableListOf() + + breakpoints.add(minLimit) + mappings.add(Mapping.Identity) + breakpoints.add(Breakpoint(detachKey, detachThreshold, spring, Guarantee.None)) + mappings.add(Mapping.Linear(0.3f)) + breakpoints.add(maxLimit) + + return MotionSpec(DirectionalMotionSpec(breakpoints, mappings)) + } + companion object { private const val TAG = "TaskViewDismissTouchController"