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;
17 
18 import static android.app.ActivityTaskManager.INVALID_TASK_ID;
19 
20 import static com.android.launcher3.MotionEventsUtils.isTrackpadFourFingerSwipe;
21 import static com.android.launcher3.MotionEventsUtils.isTrackpadThreeFingerSwipe;
22 import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_ALLAPPS;
23 import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_BACKGROUND;
24 import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_HOME;
25 import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_OVERVIEW;
26 import static com.android.quickstep.MultiStateCallback.DEBUG_STATES;
27 import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.SET_END_TARGET;
28 import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.SET_END_TARGET_ALL_APPS;
29 import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.SET_END_TARGET_HOME;
30 import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.SET_END_TARGET_NEW_TASK;
31 
32 import android.content.Intent;
33 import android.os.SystemClock;
34 import android.view.MotionEvent;
35 import android.view.RemoteAnimationTarget;
36 
37 import androidx.annotation.NonNull;
38 import androidx.annotation.Nullable;
39 
40 import com.android.launcher3.statemanager.BaseState;
41 import com.android.quickstep.TopTaskTracker.CachedTaskInfo;
42 import com.android.quickstep.util.ActiveGestureErrorDetector;
43 import com.android.quickstep.util.ActiveGestureLog;
44 import com.android.quickstep.views.RecentsViewContainer;
45 import com.android.systemui.shared.recents.model.ThumbnailData;
46 
47 import java.io.PrintWriter;
48 import java.util.ArrayList;
49 import java.util.Arrays;
50 import java.util.HashMap;
51 import java.util.HashSet;
52 import java.util.List;
53 import java.util.Set;
54 import java.util.function.Predicate;
55 
56 /**
57  * Manages the state for an active system gesture, listens for events from the system and Launcher,
58  * and fires events when the states change.
59  */
60 public class GestureState implements RecentsAnimationCallbacks.RecentsAnimationListener {
61 
62     final Predicate<RemoteAnimationTarget> mLastStartedTaskIdPredicate = new Predicate<>() {
63         @Override
64         public boolean test(RemoteAnimationTarget targetCompat) {
65             for (int taskId : mLastStartedTaskId) {
66                 if (targetCompat.taskId == taskId) {
67                     return true;
68                 }
69             }
70             return false;
71         }
72     };
73 
74     /**
75      * Defines the end targets of a gesture and the associated state.
76      */
77     public enum GestureEndTarget {
78         HOME(true, LAUNCHER_STATE_HOME, false),
79 
80         RECENTS(true, LAUNCHER_STATE_OVERVIEW, true),
81 
82         NEW_TASK(false, LAUNCHER_STATE_BACKGROUND, true),
83 
84         LAST_TASK(false, LAUNCHER_STATE_BACKGROUND, true),
85 
86         ALL_APPS(true, LAUNCHER_STATE_ALLAPPS, false);
87 
GestureEndTarget(boolean isLauncher, int containerType, boolean recentsAttachedToAppWindow)88         GestureEndTarget(boolean isLauncher, int containerType,
89                 boolean recentsAttachedToAppWindow) {
90             this.isLauncher = isLauncher;
91             this.containerType = containerType;
92             this.recentsAttachedToAppWindow = recentsAttachedToAppWindow;
93         }
94 
95         /** Whether the target is in the launcher activity. Implicitly, if the end target is going
96          to Launcher, then we can not interrupt the animation to start another gesture. */
97         public final boolean isLauncher;
98         /** Used to log where the user ended up after the gesture ends */
99         public final int containerType;
100         /** Whether RecentsView should be attached to the window as we animate to this target */
101         public final boolean recentsAttachedToAppWindow;
102     }
103 
104     private static final String TAG = "GestureState";
105 
106     private static final List<String> STATE_NAMES = new ArrayList<>();
107     public static final GestureState DEFAULT_STATE = new GestureState();
108 
109     private static int FLAG_COUNT = 0;
getNextStateFlag(String name)110     private static int getNextStateFlag(String name) {
111         if (DEBUG_STATES) {
112             STATE_NAMES.add(name);
113         }
114         int index = 1 << FLAG_COUNT;
115         FLAG_COUNT++;
116         return index;
117     }
118 
119     // Called when the end target as been set
120     public static final int STATE_END_TARGET_SET =
121             getNextStateFlag("STATE_END_TARGET_SET");
122 
123     // Called when the end target animation has finished
124     public static final int STATE_END_TARGET_ANIMATION_FINISHED =
125             getNextStateFlag("STATE_END_TARGET_ANIMATION_FINISHED");
126 
127     // Called when the recents animation has been requested to start
128     public static final int STATE_RECENTS_ANIMATION_INITIALIZED =
129             getNextStateFlag("STATE_RECENTS_ANIMATION_INITIALIZED");
130 
131     // Called when the recents animation is started and the TaskAnimationManager has been updated
132     // with the controller and targets
133     public static final int STATE_RECENTS_ANIMATION_STARTED =
134             getNextStateFlag("STATE_RECENTS_ANIMATION_STARTED");
135 
136     // Called when the recents animation is canceled
137     public static final int STATE_RECENTS_ANIMATION_CANCELED =
138             getNextStateFlag("STATE_RECENTS_ANIMATION_CANCELED");
139 
140     // Called when the recents animation finishes
141     public static final int STATE_RECENTS_ANIMATION_FINISHED =
142             getNextStateFlag("STATE_RECENTS_ANIMATION_FINISHED");
143 
144     // Always called when the recents animation ends (regardless of cancel or finish)
145     public static final int STATE_RECENTS_ANIMATION_ENDED =
146             getNextStateFlag("STATE_RECENTS_ANIMATION_ENDED");
147 
148     // Called when RecentsView stops scrolling and settles on a TaskView.
149     public static final int STATE_RECENTS_SCROLLING_FINISHED =
150             getNextStateFlag("STATE_RECENTS_SCROLLING_FINISHED");
151 
152     // Needed to interact with the current activity
153     private final Intent mHomeIntent;
154     private final Intent mOverviewIntent;
155     private final BaseContainerInterface mContainerInterface;
156     private final MultiStateCallback mStateCallback;
157     private final int mGestureId;
158 
159     public enum TrackpadGestureType {
160         NONE,
161         THREE_FINGER,
162         FOUR_FINGER;
163 
getTrackpadGestureType(MotionEvent event)164         public static TrackpadGestureType getTrackpadGestureType(MotionEvent event) {
165             if (isTrackpadThreeFingerSwipe(event)) {
166                 return TrackpadGestureType.THREE_FINGER;
167             }
168             if (isTrackpadFourFingerSwipe(event)) {
169                 return TrackpadGestureType.FOUR_FINGER;
170             }
171 
172             return TrackpadGestureType.NONE;
173         }
174     }
175 
176     private TrackpadGestureType mTrackpadGestureType = TrackpadGestureType.NONE;
177     private CachedTaskInfo mRunningTask;
178     private GestureEndTarget mEndTarget;
179     private RemoteAnimationTarget[] mLastAppearedTaskTargets;
180     private Set<Integer> mPreviouslyAppearedTaskIds = new HashSet<>();
181     private int[] mLastStartedTaskId = new int[]{INVALID_TASK_ID, INVALID_TASK_ID};
182     private RecentsAnimationController mRecentsAnimationController;
183     private HashMap<Integer, ThumbnailData> mRecentsAnimationCanceledSnapshots;
184 
185     /** The time when the swipe up gesture is triggered. */
186     private final long mSwipeUpStartTimeMs = SystemClock.uptimeMillis();
187 
188     private boolean mHandlingAtomicEvent;
189     private boolean mIsInExtendedSlopRegion;
190 
GestureState(OverviewComponentObserver componentObserver, int gestureId)191     public GestureState(OverviewComponentObserver componentObserver, int gestureId) {
192         mHomeIntent = componentObserver.getHomeIntent();
193         mOverviewIntent = componentObserver.getOverviewIntent();
194         mContainerInterface = componentObserver.getActivityInterface();
195         mStateCallback = new MultiStateCallback(
196                 STATE_NAMES.toArray(new String[0]), GestureState::getTrackedEventForState);
197         mGestureId = gestureId;
198     }
199 
GestureState(GestureState other)200     public GestureState(GestureState other) {
201         mHomeIntent = other.mHomeIntent;
202         mOverviewIntent = other.mOverviewIntent;
203         mContainerInterface = other.mContainerInterface;
204         mStateCallback = other.mStateCallback;
205         mGestureId = other.mGestureId;
206         mRunningTask = other.mRunningTask;
207         mEndTarget = other.mEndTarget;
208         mLastAppearedTaskTargets = other.mLastAppearedTaskTargets;
209         mPreviouslyAppearedTaskIds = other.mPreviouslyAppearedTaskIds;
210         mLastStartedTaskId = other.mLastStartedTaskId;
211     }
212 
GestureState()213     public GestureState() {
214         // Do nothing, only used for initializing the gesture state prior to user unlock
215         mHomeIntent = new Intent();
216         mOverviewIntent = new Intent();
217         mContainerInterface = null;
218         mStateCallback = new MultiStateCallback(
219                 STATE_NAMES.toArray(new String[0]), GestureState::getTrackedEventForState);
220         mGestureId = -1;
221     }
222 
223     @Nullable
getTrackedEventForState(int stateFlag)224     private static ActiveGestureErrorDetector.GestureEvent getTrackedEventForState(int stateFlag) {
225         if (stateFlag == STATE_END_TARGET_ANIMATION_FINISHED) {
226             return ActiveGestureErrorDetector.GestureEvent.STATE_END_TARGET_ANIMATION_FINISHED;
227         } else if (stateFlag == STATE_RECENTS_SCROLLING_FINISHED) {
228             return ActiveGestureErrorDetector.GestureEvent.STATE_RECENTS_SCROLLING_FINISHED;
229         } else if (stateFlag == STATE_RECENTS_ANIMATION_CANCELED) {
230             return ActiveGestureErrorDetector.GestureEvent.STATE_RECENTS_ANIMATION_CANCELED;
231         }
232         return null;
233     }
234 
235     /**
236      * @return whether the gesture state has the provided {@param stateMask} flags set.
237      */
hasState(int stateMask)238     public boolean hasState(int stateMask) {
239         return mStateCallback.hasStates(stateMask);
240     }
241 
242     /**
243      * Sets the given {@param stateFlag}s.
244      */
setState(int stateFlag)245     public void setState(int stateFlag) {
246         mStateCallback.setState(stateFlag);
247     }
248 
249     /**
250      * Adds a callback for when the states matching the given {@param stateMask} is set.
251      */
runOnceAtState(int stateMask, Runnable callback)252     public void runOnceAtState(int stateMask, Runnable callback) {
253         mStateCallback.runOnceAtState(stateMask, callback);
254     }
255 
256     /**
257      * @return the intent for the Home component.
258      */
getHomeIntent()259     public Intent getHomeIntent() {
260         return mHomeIntent;
261     }
262 
263     /**
264      * @return the intent for the Overview component.
265      */
getOverviewIntent()266     public Intent getOverviewIntent() {
267         return mOverviewIntent;
268     }
269 
270     /**
271      * @return the interface to the activity handing the UI updates for this gesture.
272      */
273     public <S extends BaseState<S>, T extends RecentsViewContainer>
getContainerInterface()274             BaseContainerInterface<S, T> getContainerInterface() {
275         return mContainerInterface;
276     }
277 
278     /**
279      * @return the id for this particular gesture.
280      */
getGestureId()281     public int getGestureId() {
282         return mGestureId;
283     }
284 
285     /**
286      * Sets if the gesture is is from the trackpad, if so, whether 3-finger, or 4-finger
287      */
setTrackpadGestureType(TrackpadGestureType trackpadGestureType)288     public void setTrackpadGestureType(TrackpadGestureType trackpadGestureType) {
289         mTrackpadGestureType = trackpadGestureType;
290     }
291 
isTrackpadGesture()292     public boolean isTrackpadGesture() {
293         return mTrackpadGestureType != TrackpadGestureType.NONE;
294     }
295 
isThreeFingerTrackpadGesture()296     public boolean isThreeFingerTrackpadGesture() {
297         return mTrackpadGestureType == TrackpadGestureType.THREE_FINGER;
298     }
299 
isFourFingerTrackpadGesture()300     public boolean isFourFingerTrackpadGesture() {
301         return mTrackpadGestureType == TrackpadGestureType.FOUR_FINGER;
302     }
303 
304     /**
305      * @return the running task for this gesture.
306      */
307     @Nullable
getRunningTask()308     public CachedTaskInfo getRunningTask() {
309         return mRunningTask;
310     }
311 
312     /**
313      * @param getMultipleTasks Whether multiple tasks or not are to be returned (for split)
314      * @return the running task ids for this gesture.
315      */
getRunningTaskIds(boolean getMultipleTasks)316     public int[] getRunningTaskIds(boolean getMultipleTasks) {
317         if (mRunningTask == null) {
318             return new int[]{INVALID_TASK_ID, INVALID_TASK_ID};
319         } else {
320             int cachedTasksSize = mRunningTask.mAllCachedTasks.size();
321             int count = Math.min(cachedTasksSize, getMultipleTasks ? 2 : 1);
322             int[] runningTaskIds = new int[count];
323             for (int i = 0; i < count; i++) {
324                 runningTaskIds[i] = mRunningTask.mAllCachedTasks.get(i).taskId;
325             }
326             return runningTaskIds;
327         }
328     }
329 
330     /**
331      * @see #getRunningTaskIds(boolean)
332      * @return the single top-most running taskId for this gesture
333      */
getTopRunningTaskId()334     public int getTopRunningTaskId() {
335         return getRunningTaskIds(false /*getMultipleTasks*/)[0];
336     }
337 
338     /**
339      * Updates the running task for the gesture to be the given {@param runningTask}.
340      */
updateRunningTask(@onNull CachedTaskInfo runningTask)341     public void updateRunningTask(@NonNull CachedTaskInfo runningTask) {
342         mRunningTask = runningTask;
343     }
344 
345     /**
346      * Updates the last task that appeared during this gesture.
347      */
updateLastAppearedTaskTargets(RemoteAnimationTarget[] lastAppearedTaskTargets)348     public void updateLastAppearedTaskTargets(RemoteAnimationTarget[] lastAppearedTaskTargets) {
349         mLastAppearedTaskTargets = lastAppearedTaskTargets;
350         for (RemoteAnimationTarget target : lastAppearedTaskTargets) {
351             if (target == null) {
352                 continue;
353             }
354             mPreviouslyAppearedTaskIds.add(target.taskId);
355         }
356     }
357 
358     /**
359      * @return The id of the task that appeared during this gesture.
360      */
getLastAppearedTaskIds()361     public int[] getLastAppearedTaskIds() {
362         if (mLastAppearedTaskTargets == null) {
363             return new int[]{INVALID_TASK_ID, INVALID_TASK_ID};
364         } else {
365             return Arrays.stream(mLastAppearedTaskTargets)
366                     .mapToInt(target -> target != null ? target.taskId : INVALID_TASK_ID).toArray();
367         }
368     }
369 
updatePreviouslyAppearedTaskIds(Set<Integer> previouslyAppearedTaskIds)370     public void updatePreviouslyAppearedTaskIds(Set<Integer> previouslyAppearedTaskIds) {
371         mPreviouslyAppearedTaskIds = previouslyAppearedTaskIds;
372     }
373 
getPreviouslyAppearedTaskIds()374     public Set<Integer> getPreviouslyAppearedTaskIds() {
375         return mPreviouslyAppearedTaskIds;
376     }
377 
378     /**
379      * Updates the last task that we started via startActivityFromRecents() during this gesture.
380      */
updateLastStartedTaskIds(int[] lastStartedTaskId)381     public void updateLastStartedTaskIds(int[] lastStartedTaskId) {
382         mLastStartedTaskId = lastStartedTaskId;
383     }
384 
385     /**
386      * @return The id of the task that was most recently started during this gesture, or -1 if
387      * no task has been started yet (i.e. we haven't settled on a new task).
388      */
getLastStartedTaskIds()389     public int[] getLastStartedTaskIds() {
390         return mLastStartedTaskId;
391     }
392 
393     /**
394      * @return the end target for this gesture (if known).
395      */
getEndTarget()396     public GestureEndTarget getEndTarget() {
397         return mEndTarget;
398     }
399 
400     /**
401      * Sets the end target of this gesture and immediately notifies the state changes.
402      */
setEndTarget(GestureEndTarget target)403     public void setEndTarget(GestureEndTarget target) {
404         setEndTarget(target, true /* isAtomic */);
405     }
406 
407     /**
408      * Sets the end target of this gesture, but if {@param isAtomic} is {@code false}, then the
409      * caller must explicitly set {@link #STATE_END_TARGET_ANIMATION_FINISHED} themselves.
410      */
setEndTarget(GestureEndTarget target, boolean isAtomic)411     public void setEndTarget(GestureEndTarget target, boolean isAtomic) {
412         mEndTarget = target;
413         mStateCallback.setState(STATE_END_TARGET_SET);
414         ActiveGestureLog.INSTANCE.addLog(
415                 new ActiveGestureLog.CompoundString("setEndTarget ")
416                         .append(mEndTarget.name()),
417                 /* gestureEvent= */ SET_END_TARGET);
418         switch (mEndTarget) {
419             case HOME:
420                 ActiveGestureLog.INSTANCE.trackEvent(SET_END_TARGET_HOME);
421                 break;
422             case NEW_TASK:
423                 ActiveGestureLog.INSTANCE.trackEvent(SET_END_TARGET_NEW_TASK);
424                 break;
425             case ALL_APPS:
426                 ActiveGestureLog.INSTANCE.trackEvent(SET_END_TARGET_ALL_APPS);
427                 break;
428             case LAST_TASK:
429             case RECENTS:
430             default:
431                 // No-Op
432         }
433         if (isAtomic) {
434             mStateCallback.setState(STATE_END_TARGET_ANIMATION_FINISHED);
435         }
436     }
437 
438     /**
439      * Indicates if the gesture is handling an atomic event like a click and not a
440      * user controlled gesture.
441      */
setHandlingAtomicEvent(boolean handlingAtomicEvent)442     public void setHandlingAtomicEvent(boolean handlingAtomicEvent) {
443         mHandlingAtomicEvent = handlingAtomicEvent;
444     }
445 
446     /**
447      * Returns true if the gesture is handling an atomic event like a click and not a
448      * user controlled gesture.
449      */
isHandlingAtomicEvent()450     public boolean isHandlingAtomicEvent() {
451         return mHandlingAtomicEvent;
452     }
453 
454     /**
455      * @return whether the current gesture is still running a recents animation to a state in the
456      *         Launcher or Recents activity.
457      */
isRunningAnimationToLauncher()458     public boolean isRunningAnimationToLauncher() {
459         return isRecentsAnimationRunning() && mEndTarget != null && mEndTarget.isLauncher;
460     }
461 
462     /**
463      * @return whether the recents animation is started but not yet ended
464      */
isRecentsAnimationRunning()465     public boolean isRecentsAnimationRunning() {
466         return mStateCallback.hasStates(STATE_RECENTS_ANIMATION_STARTED)
467                 && !mStateCallback.hasStates(STATE_RECENTS_ANIMATION_ENDED);
468     }
469 
470     @Override
onRecentsAnimationStart(RecentsAnimationController controller, RecentsAnimationTargets targets)471     public void onRecentsAnimationStart(RecentsAnimationController controller,
472             RecentsAnimationTargets targets) {
473         mRecentsAnimationController = controller;
474         mStateCallback.setState(STATE_RECENTS_ANIMATION_STARTED);
475     }
476 
477     @Override
onRecentsAnimationCanceled(HashMap<Integer, ThumbnailData> thumbnailDatas)478     public void onRecentsAnimationCanceled(HashMap<Integer, ThumbnailData> thumbnailDatas) {
479         mRecentsAnimationCanceledSnapshots = thumbnailDatas;
480         mStateCallback.setState(STATE_RECENTS_ANIMATION_CANCELED);
481         mStateCallback.setState(STATE_RECENTS_ANIMATION_ENDED);
482         if (mRecentsAnimationCanceledSnapshots != null) {
483             // Clean up the screenshot to finalize the recents animation cancel
484             if (mRecentsAnimationController != null) {
485                 mRecentsAnimationController.cleanupScreenshot();
486             }
487             mRecentsAnimationCanceledSnapshots = null;
488         }
489     }
490 
491     @Override
onRecentsAnimationFinished(RecentsAnimationController controller)492     public void onRecentsAnimationFinished(RecentsAnimationController controller) {
493         mStateCallback.setState(STATE_RECENTS_ANIMATION_FINISHED);
494         mStateCallback.setState(STATE_RECENTS_ANIMATION_ENDED);
495     }
496 
497     /**
498      * Set whether it's in long press nav handle (LPNH)'s extended touch slop region, e.g., second
499      * stage region in order to continue respect LPNH and ignore other touch slop logic.
500      * This will only be set to true when flag ENABLE_LPNH_TWO_STAGES is turned on.
501      */
setIsInExtendedSlopRegion(boolean isInExtendedSlopRegion)502     public void setIsInExtendedSlopRegion(boolean isInExtendedSlopRegion) {
503         if (DeviceConfigWrapper.get().getEnableLpnhTwoStages()) {
504             mIsInExtendedSlopRegion = isInExtendedSlopRegion;
505         }
506     }
507 
508     /**
509      * Returns whether it's in LPNH's extended touch slop region. This is only valid when flag
510      * ENABLE_LPNH_TWO_STAGES is turned on.
511      */
isInExtendedSlopRegion()512     public boolean isInExtendedSlopRegion() {
513         return mIsInExtendedSlopRegion;
514     }
515 
516     /**
517      * Returns and clears the canceled animation thumbnail data. This call only returns a value
518      * while STATE_RECENTS_ANIMATION_CANCELED state is being set, and the caller is responsible for
519      * calling {@link RecentsAnimationController#cleanupScreenshot()}.
520      */
521     @Nullable
consumeRecentsAnimationCanceledSnapshot()522     HashMap<Integer, ThumbnailData> consumeRecentsAnimationCanceledSnapshot() {
523         if (mRecentsAnimationCanceledSnapshots != null) {
524             HashMap<Integer, ThumbnailData> data =
525                     new HashMap<Integer, ThumbnailData>(mRecentsAnimationCanceledSnapshots);
526             mRecentsAnimationCanceledSnapshots = null;
527             return data;
528         }
529         return null;
530     }
531 
getSwipeUpStartTimeMs()532     long getSwipeUpStartTimeMs() {
533         return mSwipeUpStartTimeMs;
534     }
535 
dump(String prefix, PrintWriter pw)536     public void dump(String prefix, PrintWriter pw) {
537         pw.println(prefix + "GestureState:");
538         pw.println(prefix + "\tgestureID=" + mGestureId);
539         pw.println(prefix + "\trunningTask=" + mRunningTask);
540         pw.println(prefix + "\tendTarget=" + mEndTarget);
541         pw.println(prefix + "\tlastAppearedTaskTargetId="
542                 + Arrays.toString(mLastAppearedTaskTargets));
543         pw.println(prefix + "\tlastStartedTaskId=" + Arrays.toString(mLastStartedTaskId));
544         pw.println(prefix + "\tisRecentsAnimationRunning=" + isRecentsAnimationRunning());
545     }
546 }
547