• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2018 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.view.MotionEvent.ACTION_CANCEL;
19 import static android.view.MotionEvent.ACTION_DOWN;
20 import static android.view.MotionEvent.ACTION_UP;
21 
22 import static com.android.launcher3.Utilities.FLAG_NO_GESTURES;
23 
24 import android.view.InputEvent;
25 import android.view.KeyEvent;
26 import android.view.MotionEvent;
27 
28 import com.android.launcher3.util.Preconditions;
29 import com.android.quickstep.inputconsumers.InputConsumer;
30 import com.android.quickstep.util.SwipeAnimationTargetSet;
31 import com.android.systemui.shared.system.InputConsumerController;
32 
33 import java.util.ArrayList;
34 import java.util.function.Supplier;
35 
36 import androidx.annotation.UiThread;
37 
38 /**
39  * Wrapper around RecentsAnimationController to help with some synchronization
40  */
41 public class RecentsAnimationWrapper {
42 
43     // A list of callbacks to run when we receive the recents animation target. There are different
44     // than the state callbacks as these run on the current worker thread.
45     private final ArrayList<Runnable> mCallbacks = new ArrayList<>();
46 
47     public SwipeAnimationTargetSet targetSet;
48 
49     private boolean mWindowThresholdCrossed = false;
50 
51     private final InputConsumerController mInputConsumerController;
52     private final Supplier<InputConsumer> mInputProxySupplier;
53 
54     private InputConsumer mInputConsumer;
55     private boolean mTouchInProgress;
56 
57     private boolean mFinishPending;
58 
RecentsAnimationWrapper(InputConsumerController inputConsumerController, Supplier<InputConsumer> inputProxySupplier)59     public RecentsAnimationWrapper(InputConsumerController inputConsumerController,
60             Supplier<InputConsumer> inputProxySupplier) {
61         mInputConsumerController = inputConsumerController;
62         mInputProxySupplier = inputProxySupplier;
63     }
64 
hasTargets()65     public boolean hasTargets() {
66         return targetSet != null && targetSet.hasTargets();
67     }
68 
69     @UiThread
setController(SwipeAnimationTargetSet targetSet)70     public synchronized void setController(SwipeAnimationTargetSet targetSet) {
71         Preconditions.assertUIThread();
72         this.targetSet = targetSet;
73 
74         if (targetSet == null) {
75             return;
76         }
77         targetSet.setWindowThresholdCrossed(mWindowThresholdCrossed);
78 
79         if (!mCallbacks.isEmpty()) {
80             for (Runnable action : new ArrayList<>(mCallbacks)) {
81                 action.run();
82             }
83             mCallbacks.clear();
84         }
85     }
86 
runOnInit(Runnable action)87     public synchronized void runOnInit(Runnable action) {
88         if (targetSet == null) {
89             mCallbacks.add(action);
90         } else {
91             action.run();
92         }
93     }
94 
95     /** See {@link #finish(boolean, Runnable, boolean)} */
96     @UiThread
finish(boolean toRecents, Runnable onFinishComplete)97     public void finish(boolean toRecents, Runnable onFinishComplete) {
98         finish(toRecents, onFinishComplete, false /* sendUserLeaveHint */);
99     }
100 
101     /**
102      * @param onFinishComplete A callback that runs on the main thread after the animation
103      *                         controller has finished on the background thread.
104      * @param sendUserLeaveHint Determines whether userLeaveHint flag will be set on the pausing
105      *                          activity. If userLeaveHint is true, the activity will enter into
106      *                          picture-in-picture mode upon being paused.
107      */
108     @UiThread
finish(boolean toRecents, Runnable onFinishComplete, boolean sendUserLeaveHint)109     public void finish(boolean toRecents, Runnable onFinishComplete, boolean sendUserLeaveHint) {
110         Preconditions.assertUIThread();
111         if (!toRecents) {
112             finishAndClear(false, onFinishComplete, sendUserLeaveHint);
113         } else {
114             if (mTouchInProgress) {
115                 mFinishPending = true;
116                 // Execute the callback
117                 if (onFinishComplete != null) {
118                     onFinishComplete.run();
119                 }
120             } else {
121                 finishAndClear(true, onFinishComplete, sendUserLeaveHint);
122             }
123         }
124     }
125 
finishAndClear(boolean toRecents, Runnable onFinishComplete, boolean sendUserLeaveHint)126     private void finishAndClear(boolean toRecents, Runnable onFinishComplete,
127             boolean sendUserLeaveHint) {
128         SwipeAnimationTargetSet controller = targetSet;
129         targetSet = null;
130         if (controller != null) {
131             controller.finishController(toRecents, onFinishComplete, sendUserLeaveHint);
132         }
133     }
134 
enableInputConsumer()135     public void enableInputConsumer() {
136         if (targetSet != null) {
137             targetSet.enableInputConsumer();
138         }
139     }
140 
141     /**
142      * Indicates that the gesture has crossed the window boundary threshold and system UI can be
143      * update the represent the window behind
144      */
setWindowThresholdCrossed(boolean windowThresholdCrossed)145     public void setWindowThresholdCrossed(boolean windowThresholdCrossed) {
146         if (mWindowThresholdCrossed != windowThresholdCrossed) {
147             mWindowThresholdCrossed = windowThresholdCrossed;
148             if (targetSet != null) {
149                 targetSet.setWindowThresholdCrossed(windowThresholdCrossed);
150             }
151         }
152     }
153 
enableInputProxy()154     public void enableInputProxy() {
155         mInputConsumerController.setInputListener(this::onInputConsumerEvent);
156     }
157 
onInputConsumerEvent(InputEvent ev)158     private boolean onInputConsumerEvent(InputEvent ev) {
159         if (ev instanceof MotionEvent) {
160             onInputConsumerMotionEvent((MotionEvent) ev);
161         } else if (ev instanceof KeyEvent) {
162             if (mInputConsumer == null) {
163                 mInputConsumer = mInputProxySupplier.get();
164             }
165             mInputConsumer.onKeyEvent((KeyEvent) ev);
166             return true;
167         }
168         return false;
169     }
170 
onInputConsumerMotionEvent(MotionEvent ev)171     private boolean onInputConsumerMotionEvent(MotionEvent ev) {
172         int action = ev.getAction();
173         if (action == ACTION_DOWN) {
174             mTouchInProgress = true;
175             if (mInputConsumer == null) {
176                 mInputConsumer = mInputProxySupplier.get();
177             }
178         } else if (action == ACTION_CANCEL || action == ACTION_UP) {
179             // Finish any pending actions
180             mTouchInProgress = false;
181             if (mFinishPending) {
182                 mFinishPending = false;
183                 finishAndClear(true /* toRecents */, null, false /* sendUserLeaveHint */);
184             }
185         }
186         if (mInputConsumer != null) {
187             int flags = ev.getEdgeFlags();
188             ev.setEdgeFlags(flags | FLAG_NO_GESTURES);
189             mInputConsumer.onMotionEvent(ev);
190             ev.setEdgeFlags(flags);
191         }
192 
193         return true;
194     }
195 
setCancelWithDeferredScreenshot(boolean deferredWithScreenshot)196     public void setCancelWithDeferredScreenshot(boolean deferredWithScreenshot) {
197         if (targetSet != null) {
198             targetSet.controller.setCancelWithDeferredScreenshot(deferredWithScreenshot);
199         }
200     }
201 
getController()202     public SwipeAnimationTargetSet getController() {
203         return targetSet;
204     }
205 }
206