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