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