1 /* 2 * Copyright (C) 2022 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.wm.shell.back; 18 19 import android.annotation.FloatRange; 20 import android.os.SystemProperties; 21 import android.util.MathUtils; 22 import android.view.MotionEvent; 23 import android.view.RemoteAnimationTarget; 24 import android.window.BackEvent; 25 import android.window.BackMotionEvent; 26 27 /** 28 * Helper class to record the touch location for gesture and generate back events. 29 */ 30 class TouchTracker { 31 private static final String PREDICTIVE_BACK_LINEAR_DISTANCE_PROP = 32 "persist.wm.debug.predictive_back_linear_distance"; 33 private static final int LINEAR_DISTANCE = SystemProperties 34 .getInt(PREDICTIVE_BACK_LINEAR_DISTANCE_PROP, -1); 35 private float mLinearDistance = LINEAR_DISTANCE; 36 private float mMaxDistance; 37 private float mNonLinearFactor; 38 /** 39 * Location of the latest touch event 40 */ 41 private float mLatestTouchX; 42 private float mLatestTouchY; 43 private boolean mTriggerBack; 44 45 /** 46 * Location of the initial touch event of the back gesture. 47 */ 48 private float mInitTouchX; 49 private float mInitTouchY; 50 private float mLatestVelocityX; 51 private float mLatestVelocityY; 52 private float mStartThresholdX; 53 private int mSwipeEdge; 54 private boolean mCancelled; 55 update(float touchX, float touchY, float velocityX, float velocityY)56 void update(float touchX, float touchY, float velocityX, float velocityY) { 57 /** 58 * If back was previously cancelled but the user has started swiping in the forward 59 * direction again, restart back. 60 */ 61 if (mCancelled && ((touchX > mLatestTouchX && mSwipeEdge == BackEvent.EDGE_LEFT) 62 || touchX < mLatestTouchX && mSwipeEdge == BackEvent.EDGE_RIGHT)) { 63 mCancelled = false; 64 mStartThresholdX = touchX; 65 } 66 mLatestTouchX = touchX; 67 mLatestTouchY = touchY; 68 mLatestVelocityX = velocityX; 69 mLatestVelocityY = velocityY; 70 } 71 setTriggerBack(boolean triggerBack)72 void setTriggerBack(boolean triggerBack) { 73 if (mTriggerBack != triggerBack && !triggerBack) { 74 mCancelled = true; 75 } 76 mTriggerBack = triggerBack; 77 } 78 setGestureStartLocation(float touchX, float touchY, int swipeEdge)79 void setGestureStartLocation(float touchX, float touchY, int swipeEdge) { 80 mInitTouchX = touchX; 81 mInitTouchY = touchY; 82 mSwipeEdge = swipeEdge; 83 mStartThresholdX = mInitTouchX; 84 } 85 reset()86 void reset() { 87 mInitTouchX = 0; 88 mInitTouchY = 0; 89 mStartThresholdX = 0; 90 mCancelled = false; 91 mTriggerBack = false; 92 mSwipeEdge = BackEvent.EDGE_LEFT; 93 } 94 createStartEvent(RemoteAnimationTarget target)95 BackMotionEvent createStartEvent(RemoteAnimationTarget target) { 96 return new BackMotionEvent( 97 /* touchX = */ mInitTouchX, 98 /* touchY = */ mInitTouchY, 99 /* progress = */ 0, 100 /* velocityX = */ 0, 101 /* velocityY = */ 0, 102 /* swipeEdge = */ mSwipeEdge, 103 /* departingAnimationTarget = */ target); 104 } 105 createProgressEvent()106 BackMotionEvent createProgressEvent() { 107 float progress = 0; 108 // Progress is always 0 when back is cancelled and not restarted. 109 if (!mCancelled) { 110 progress = getProgress(mLatestTouchX); 111 } 112 return createProgressEvent(progress); 113 } 114 115 /** 116 * Progress value computed from the touch position. 117 * 118 * @param touchX the X touch position of the {@link MotionEvent}. 119 * @return progress value 120 */ 121 @FloatRange(from = 0.0, to = 1.0) getProgress(float touchX)122 float getProgress(float touchX) { 123 // If back is committed, progress is the distance between the last and first touch 124 // point, divided by the max drag distance. Otherwise, it's the distance between 125 // the last touch point and the starting threshold, divided by max drag distance. 126 // The starting threshold is initially the first touch location, and updated to 127 // the location everytime back is restarted after being cancelled. 128 float startX = mTriggerBack ? mInitTouchX : mStartThresholdX; 129 float deltaX = Math.abs(startX - touchX); 130 float linearDistance = mLinearDistance; 131 float maxDistance = getMaxDistance(); 132 maxDistance = maxDistance == 0 ? 1 : maxDistance; 133 float progress; 134 if (linearDistance < maxDistance) { 135 // Up to linearDistance it behaves linearly, then slowly reaches 1f. 136 137 // maxDistance is composed of linearDistance + nonLinearDistance 138 float nonLinearDistance = maxDistance - linearDistance; 139 float initialTarget = linearDistance + nonLinearDistance * mNonLinearFactor; 140 141 boolean isLinear = deltaX <= linearDistance; 142 if (isLinear) { 143 progress = deltaX / initialTarget; 144 } else { 145 float nonLinearDeltaX = deltaX - linearDistance; 146 float nonLinearProgress = nonLinearDeltaX / nonLinearDistance; 147 float currentTarget = MathUtils.lerp( 148 /* start = */ initialTarget, 149 /* stop = */ maxDistance, 150 /* amount = */ nonLinearProgress); 151 progress = deltaX / currentTarget; 152 } 153 } else { 154 // Always linear behavior. 155 progress = deltaX / maxDistance; 156 } 157 return MathUtils.constrain(progress, 0, 1); 158 } 159 160 /** 161 * Maximum distance in pixels. 162 * Progress is considered to be completed (1f) when this limit is exceeded. 163 */ getMaxDistance()164 float getMaxDistance() { 165 return mMaxDistance; 166 } 167 createProgressEvent(float progress)168 BackMotionEvent createProgressEvent(float progress) { 169 return new BackMotionEvent( 170 /* touchX = */ mLatestTouchX, 171 /* touchY = */ mLatestTouchY, 172 /* progress = */ progress, 173 /* velocityX = */ mLatestVelocityX, 174 /* velocityY = */ mLatestVelocityY, 175 /* swipeEdge = */ mSwipeEdge, 176 /* departingAnimationTarget = */ null); 177 } 178 setProgressThresholds(float linearDistance, float maxDistance, float nonLinearFactor)179 public void setProgressThresholds(float linearDistance, float maxDistance, 180 float nonLinearFactor) { 181 if (LINEAR_DISTANCE >= 0) { 182 mLinearDistance = LINEAR_DISTANCE; 183 } else { 184 mLinearDistance = linearDistance; 185 } 186 mMaxDistance = maxDistance; 187 mNonLinearFactor = nonLinearFactor; 188 } 189 } 190