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