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