• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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