1 /* 2 * Copyright (C) 2019 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 package com.android.quickstep.inputconsumers; 17 18 import static android.view.MotionEvent.ACTION_CANCEL; 19 import static android.view.MotionEvent.ACTION_POINTER_DOWN; 20 import static android.view.MotionEvent.ACTION_UP; 21 22 import static com.android.launcher3.Utilities.createHomeIntent; 23 import static com.android.launcher3.Utilities.squaredHypot; 24 import static com.android.launcher3.Utilities.squaredTouchSlop; 25 import static com.android.launcher3.util.VelocityUtils.PX_PER_MS; 26 import static com.android.quickstep.AbsSwipeUpHandler.MIN_PROGRESS_FOR_OVERVIEW; 27 import static com.android.quickstep.MultiStateCallback.DEBUG_STATES; 28 import static com.android.quickstep.util.ActiveGestureLog.INTENT_EXTRA_LOG_TRACE_ID; 29 30 import android.animation.Animator; 31 import android.animation.AnimatorListenerAdapter; 32 import android.animation.ObjectAnimator; 33 import android.content.Context; 34 import android.content.Intent; 35 import android.graphics.Matrix; 36 import android.graphics.Point; 37 import android.graphics.PointF; 38 import android.view.MotionEvent; 39 import android.view.VelocityTracker; 40 41 import com.android.launcher3.R; 42 import com.android.launcher3.anim.Interpolators; 43 import com.android.launcher3.testing.TestLogging; 44 import com.android.launcher3.testing.TestProtocol; 45 import com.android.launcher3.util.DisplayController; 46 import com.android.quickstep.AnimatedFloat; 47 import com.android.quickstep.GestureState; 48 import com.android.quickstep.InputConsumer; 49 import com.android.quickstep.MultiStateCallback; 50 import com.android.quickstep.RecentsAnimationCallbacks; 51 import com.android.quickstep.RecentsAnimationController; 52 import com.android.quickstep.RecentsAnimationDeviceState; 53 import com.android.quickstep.RecentsAnimationTargets; 54 import com.android.quickstep.TaskAnimationManager; 55 import com.android.quickstep.util.TransformParams; 56 import com.android.quickstep.util.TransformParams.BuilderProxy; 57 import com.android.systemui.shared.recents.model.ThumbnailData; 58 import com.android.systemui.shared.system.ActivityManagerWrapper; 59 import com.android.systemui.shared.system.InputMonitorCompat; 60 import com.android.systemui.shared.system.RemoteAnimationTargetCompat; 61 import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat.SurfaceParams.Builder; 62 63 import java.util.HashMap; 64 65 /** 66 * A placeholder input consumer used when the device is still locked, e.g. from secure camera. 67 */ 68 public class DeviceLockedInputConsumer implements InputConsumer, 69 RecentsAnimationCallbacks.RecentsAnimationListener, BuilderProxy { 70 71 private static final String[] STATE_NAMES = DEBUG_STATES ? new String[2] : null; getFlagForIndex(int index, String name)72 private static int getFlagForIndex(int index, String name) { 73 if (DEBUG_STATES) { 74 STATE_NAMES[index] = name; 75 } 76 return 1 << index; 77 } 78 79 private static final int STATE_TARGET_RECEIVED = 80 getFlagForIndex(0, "STATE_TARGET_RECEIVED"); 81 private static final int STATE_HANDLER_INVALIDATED = 82 getFlagForIndex(1, "STATE_HANDLER_INVALIDATED"); 83 84 private final Context mContext; 85 private final RecentsAnimationDeviceState mDeviceState; 86 private final TaskAnimationManager mTaskAnimationManager; 87 private final GestureState mGestureState; 88 private final float mTouchSlopSquared; 89 private final InputMonitorCompat mInputMonitorCompat; 90 91 private final PointF mTouchDown = new PointF(); 92 private final TransformParams mTransformParams; 93 private final MultiStateCallback mStateCallback; 94 95 private final Point mDisplaySize; 96 private final Matrix mMatrix = new Matrix(); 97 private final float mMaxTranslationY; 98 99 private VelocityTracker mVelocityTracker; 100 private final AnimatedFloat mProgress = new AnimatedFloat(this::applyTransform); 101 102 private boolean mThresholdCrossed = false; 103 private boolean mHomeLaunched = false; 104 105 private RecentsAnimationController mRecentsAnimationController; 106 DeviceLockedInputConsumer(Context context, RecentsAnimationDeviceState deviceState, TaskAnimationManager taskAnimationManager, GestureState gestureState, InputMonitorCompat inputMonitorCompat)107 public DeviceLockedInputConsumer(Context context, RecentsAnimationDeviceState deviceState, 108 TaskAnimationManager taskAnimationManager, GestureState gestureState, 109 InputMonitorCompat inputMonitorCompat) { 110 mContext = context; 111 mDeviceState = deviceState; 112 mTaskAnimationManager = taskAnimationManager; 113 mGestureState = gestureState; 114 mTouchSlopSquared = squaredTouchSlop(context); 115 mTransformParams = new TransformParams(); 116 mInputMonitorCompat = inputMonitorCompat; 117 mMaxTranslationY = context.getResources().getDimensionPixelSize( 118 R.dimen.device_locked_y_offset); 119 120 // Do not use DeviceProfile as the user data might be locked 121 mDisplaySize = DisplayController.INSTANCE.get(context).getInfo().currentSize; 122 123 // Init states 124 mStateCallback = new MultiStateCallback(STATE_NAMES); 125 mStateCallback.runOnceAtState(STATE_TARGET_RECEIVED | STATE_HANDLER_INVALIDATED, 126 this::endRemoteAnimation); 127 128 mVelocityTracker = VelocityTracker.obtain(); 129 } 130 131 @Override getType()132 public int getType() { 133 return TYPE_DEVICE_LOCKED; 134 } 135 136 @Override onMotionEvent(MotionEvent ev)137 public void onMotionEvent(MotionEvent ev) { 138 if (mVelocityTracker == null) { 139 return; 140 } 141 mVelocityTracker.addMovement(ev); 142 143 float x = ev.getX(); 144 float y = ev.getY(); 145 switch (ev.getAction()) { 146 case MotionEvent.ACTION_DOWN: 147 mTouchDown.set(x, y); 148 break; 149 case ACTION_POINTER_DOWN: { 150 if (!mThresholdCrossed) { 151 // Cancel interaction in case of multi-touch interaction 152 int ptrIdx = ev.getActionIndex(); 153 if (!mDeviceState.getRotationTouchHelper().isInSwipeUpTouchRegion(ev, ptrIdx)) { 154 int action = ev.getAction(); 155 ev.setAction(ACTION_CANCEL); 156 finishTouchTracking(ev); 157 ev.setAction(action); 158 } 159 } 160 break; 161 } 162 case MotionEvent.ACTION_MOVE: { 163 if (!mThresholdCrossed) { 164 if (squaredHypot(x - mTouchDown.x, y - mTouchDown.y) > mTouchSlopSquared) { 165 startRecentsTransition(); 166 } 167 } else { 168 float dy = Math.max(mTouchDown.y - y, 0); 169 mProgress.updateValue(dy / mDisplaySize.y); 170 } 171 break; 172 } 173 case MotionEvent.ACTION_CANCEL: 174 case MotionEvent.ACTION_UP: 175 finishTouchTracking(ev); 176 break; 177 } 178 } 179 180 /** 181 * Called when the gesture has ended. Does not correlate to the completion of the interaction as 182 * the animation can still be running. 183 */ finishTouchTracking(MotionEvent ev)184 private void finishTouchTracking(MotionEvent ev) { 185 if (mThresholdCrossed && ev.getAction() == ACTION_UP) { 186 mVelocityTracker.computeCurrentVelocity(PX_PER_MS); 187 188 float velocityY = mVelocityTracker.getYVelocity(); 189 float flingThreshold = mContext.getResources() 190 .getDimension(R.dimen.quickstep_fling_threshold_speed); 191 192 boolean dismissTask; 193 if (Math.abs(velocityY) > flingThreshold) { 194 // Is fling 195 dismissTask = velocityY < 0; 196 } else { 197 dismissTask = mProgress.value >= (1 - MIN_PROGRESS_FOR_OVERVIEW); 198 } 199 200 // Animate back to fullscreen before finishing 201 ObjectAnimator animator = mProgress.animateToValue(mProgress.value, 0); 202 animator.setDuration(100); 203 animator.setInterpolator(Interpolators.ACCEL); 204 animator.addListener(new AnimatorListenerAdapter() { 205 @Override 206 public void onAnimationEnd(Animator animation) { 207 if (dismissTask) { 208 // For now, just start the home intent so user is prompted to unlock the device. 209 mContext.startActivity(createHomeIntent()); 210 mHomeLaunched = true; 211 } 212 mStateCallback.setState(STATE_HANDLER_INVALIDATED); 213 } 214 }); 215 animator.start(); 216 } else { 217 mStateCallback.setState(STATE_HANDLER_INVALIDATED); 218 } 219 mVelocityTracker.recycle(); 220 mVelocityTracker = null; 221 } 222 startRecentsTransition()223 private void startRecentsTransition() { 224 mThresholdCrossed = true; 225 mHomeLaunched = false; 226 TestLogging.recordEvent(TestProtocol.SEQUENCE_PILFER, "pilferPointers"); 227 mInputMonitorCompat.pilferPointers(); 228 229 Intent intent = mGestureState.getHomeIntent() 230 .putExtra(INTENT_EXTRA_LOG_TRACE_ID, mGestureState.getGestureId()); 231 mTaskAnimationManager.startRecentsAnimation(mGestureState, intent, this); 232 } 233 234 @Override onRecentsAnimationStart(RecentsAnimationController controller, RecentsAnimationTargets targets)235 public void onRecentsAnimationStart(RecentsAnimationController controller, 236 RecentsAnimationTargets targets) { 237 mRecentsAnimationController = controller; 238 mTransformParams.setTargetSet(targets); 239 applyTransform(); 240 mStateCallback.setState(STATE_TARGET_RECEIVED); 241 } 242 243 @Override onRecentsAnimationCanceled(HashMap<Integer, ThumbnailData> thumbnailDatas)244 public void onRecentsAnimationCanceled(HashMap<Integer, ThumbnailData> thumbnailDatas) { 245 mRecentsAnimationController = null; 246 mTransformParams.setTargetSet(null); 247 } 248 endRemoteAnimation()249 private void endRemoteAnimation() { 250 if (mHomeLaunched) { 251 ActivityManagerWrapper.getInstance().cancelRecentsAnimation(false); 252 } else if (mRecentsAnimationController != null) { 253 mRecentsAnimationController.finishController( 254 false /* toRecents */, null /* callback */, false /* sendUserLeaveHint */); 255 } 256 } 257 applyTransform()258 private void applyTransform() { 259 mTransformParams.setProgress(mProgress.value); 260 if (mTransformParams.getTargetSet() != null) { 261 mTransformParams.applySurfaceParams(mTransformParams.createSurfaceParams(this)); 262 } 263 } 264 265 @Override onBuildTargetParams( Builder builder, RemoteAnimationTargetCompat app, TransformParams params)266 public void onBuildTargetParams( 267 Builder builder, RemoteAnimationTargetCompat app, TransformParams params) { 268 mMatrix.setTranslate(0, mProgress.value * mMaxTranslationY); 269 builder.withMatrix(mMatrix); 270 } 271 272 @Override onConsumerAboutToBeSwitched()273 public void onConsumerAboutToBeSwitched() { 274 mStateCallback.setState(STATE_HANDLER_INVALIDATED); 275 } 276 277 @Override allowInterceptByParent()278 public boolean allowInterceptByParent() { 279 return !mThresholdCrossed; 280 } 281 } 282