• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2017 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_MOVE;
21 import static android.view.MotionEvent.ACTION_POINTER_DOWN;
22 import static android.view.MotionEvent.ACTION_POINTER_UP;
23 import static android.view.MotionEvent.ACTION_UP;
24 import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
25 import static com.android.systemui.shared.system.NavigationBarCompat.HIT_TARGET_NONE;
26 
27 import android.annotation.TargetApi;
28 import android.app.ActivityManager.RunningTaskInfo;
29 import android.app.Service;
30 import android.content.Intent;
31 import android.graphics.PointF;
32 import android.os.Build;
33 import android.os.Handler;
34 import android.os.HandlerThread;
35 import android.os.IBinder;
36 import android.os.RemoteException;
37 import android.util.Log;
38 import android.util.SparseArray;
39 import android.view.Choreographer;
40 import android.view.MotionEvent;
41 import android.view.VelocityTracker;
42 import android.view.ViewConfiguration;
43 
44 import com.android.launcher3.BaseDraggingActivity;
45 import com.android.launcher3.MainThreadExecutor;
46 import com.android.launcher3.util.TraceHelper;
47 import com.android.launcher3.views.BaseDragLayer;
48 import com.android.quickstep.views.RecentsView;
49 import com.android.systemui.shared.recents.IOverviewProxy;
50 import com.android.systemui.shared.recents.ISystemUiProxy;
51 import com.android.systemui.shared.system.ActivityManagerWrapper;
52 import com.android.systemui.shared.system.NavigationBarCompat.HitTarget;
53 
54 /**
55  * Service connected by system-UI for handling touch interaction.
56  */
57 @TargetApi(Build.VERSION_CODES.O)
58 public class TouchInteractionService extends Service {
59 
60     private static final SparseArray<String> sMotionEventNames;
61 
62     static {
63         sMotionEventNames = new SparseArray<>(3);
sMotionEventNames.put(ACTION_DOWN, "ACTION_DOWN")64         sMotionEventNames.put(ACTION_DOWN, "ACTION_DOWN");
sMotionEventNames.put(ACTION_UP, "ACTION_UP")65         sMotionEventNames.put(ACTION_UP, "ACTION_UP");
sMotionEventNames.put(ACTION_CANCEL, "ACTION_CANCEL")66         sMotionEventNames.put(ACTION_CANCEL, "ACTION_CANCEL");
67     }
68 
69     public static final int EDGE_NAV_BAR = 1 << 8;
70 
71     private static final String TAG = "TouchInteractionService";
72 
73     /**
74      * A background thread used for handling UI for another window.
75      */
76     private static HandlerThread sRemoteUiThread;
77 
78     private final IBinder mMyBinder = new IOverviewProxy.Stub() {
79 
80         @Override
81         public void onPreMotionEvent(@HitTarget int downHitTarget) throws RemoteException {
82             TraceHelper.beginSection("SysUiBinder");
83             setupTouchConsumer(downHitTarget);
84             TraceHelper.partitionSection("SysUiBinder", "Down target " + downHitTarget);
85         }
86 
87         @Override
88         public void onMotionEvent(MotionEvent ev) {
89             mEventQueue.queue(ev);
90 
91             String name = sMotionEventNames.get(ev.getActionMasked());
92             if (name != null){
93                 TraceHelper.partitionSection("SysUiBinder", name);
94             }
95         }
96 
97         @Override
98         public void onBind(ISystemUiProxy iSystemUiProxy) {
99             mISystemUiProxy = iSystemUiProxy;
100             mRecentsModel.setSystemUiProxy(mISystemUiProxy);
101             mOverviewInteractionState.setSystemUiProxy(mISystemUiProxy);
102         }
103 
104         @Override
105         public void onQuickScrubStart() {
106             mEventQueue.onQuickScrubStart();
107             TraceHelper.partitionSection("SysUiBinder", "onQuickScrubStart");
108         }
109 
110         @Override
111         public void onQuickScrubProgress(float progress) {
112             mEventQueue.onQuickScrubProgress(progress);
113         }
114 
115         @Override
116         public void onQuickScrubEnd() {
117             mEventQueue.onQuickScrubEnd();
118             TraceHelper.endSection("SysUiBinder", "onQuickScrubEnd");
119         }
120 
121         @Override
122         public void onOverviewToggle() {
123             mOverviewCommandHelper.onOverviewToggle();
124         }
125 
126         @Override
127         public void onOverviewShown(boolean triggeredFromAltTab) {
128             if (triggeredFromAltTab) {
129                 setupTouchConsumer(HIT_TARGET_NONE);
130                 mEventQueue.onOverviewShownFromAltTab();
131             } else {
132                 mOverviewCommandHelper.onOverviewShown();
133             }
134         }
135 
136         @Override
137         public void onOverviewHidden(boolean triggeredFromAltTab, boolean triggeredFromHomeKey) {
138             if (triggeredFromAltTab && !triggeredFromHomeKey) {
139                 // onOverviewShownFromAltTab initiates quick scrub. Ending it here.
140                 mEventQueue.onQuickScrubEnd();
141             }
142         }
143 
144         @Override
145         public void onQuickStep(MotionEvent motionEvent) {
146             mEventQueue.onQuickStep(motionEvent);
147             TraceHelper.endSection("SysUiBinder", "onQuickStep");
148 
149         }
150 
151         @Override
152         public void onTip(int actionType, int viewType) {
153             mOverviewCommandHelper.onTip(actionType, viewType);
154         }
155     };
156 
157     private final TouchConsumer mNoOpTouchConsumer = (ev) -> {};
158 
159     private static boolean sConnected = false;
160 
isConnected()161     public static boolean isConnected() {
162         return sConnected;
163     }
164 
165     private ActivityManagerWrapper mAM;
166     private RecentsModel mRecentsModel;
167     private MotionEventQueue mEventQueue;
168     private MainThreadExecutor mMainThreadExecutor;
169     private ISystemUiProxy mISystemUiProxy;
170     private OverviewCommandHelper mOverviewCommandHelper;
171     private OverviewInteractionState mOverviewInteractionState;
172     private OverviewCallbacks mOverviewCallbacks;
173 
174     private Choreographer mMainThreadChoreographer;
175     private Choreographer mBackgroundThreadChoreographer;
176 
177     @Override
onCreate()178     public void onCreate() {
179         super.onCreate();
180         mAM = ActivityManagerWrapper.getInstance();
181         mRecentsModel = RecentsModel.getInstance(this);
182         mRecentsModel.setPreloadTasksInBackground(true);
183         mMainThreadExecutor = new MainThreadExecutor();
184         mOverviewCommandHelper = new OverviewCommandHelper(this);
185         mMainThreadChoreographer = Choreographer.getInstance();
186         mEventQueue = new MotionEventQueue(mMainThreadChoreographer, mNoOpTouchConsumer);
187         mOverviewInteractionState = OverviewInteractionState.getInstance(this);
188         mOverviewCallbacks = OverviewCallbacks.get(this);
189 
190         sConnected = true;
191 
192         // Temporarily disable model preload
193         // new ModelPreload().start(this);
194         initBackgroundChoreographer();
195     }
196 
197     @Override
onDestroy()198     public void onDestroy() {
199         mOverviewCommandHelper.onDestroy();
200         sConnected = false;
201         super.onDestroy();
202     }
203 
204     @Override
onBind(Intent intent)205     public IBinder onBind(Intent intent) {
206         Log.d(TAG, "Touch service connected");
207         return mMyBinder;
208     }
209 
setupTouchConsumer(@itTarget int downHitTarget)210     private void setupTouchConsumer(@HitTarget int downHitTarget) {
211         mEventQueue.reset();
212         TouchConsumer oldConsumer = mEventQueue.getConsumer();
213         if (oldConsumer.deferNextEventToMainThread()) {
214             mEventQueue = new MotionEventQueue(mMainThreadChoreographer,
215                     new DeferredTouchConsumer((v) -> getCurrentTouchConsumer(downHitTarget,
216                             oldConsumer.forceToLauncherConsumer(), v)));
217             mEventQueue.deferInit();
218         } else {
219             mEventQueue = new MotionEventQueue(
220                     mMainThreadChoreographer, getCurrentTouchConsumer(downHitTarget, false, null));
221         }
222     }
223 
getCurrentTouchConsumer( @itTarget int downHitTarget, boolean forceToLauncher, VelocityTracker tracker)224     private TouchConsumer getCurrentTouchConsumer(
225             @HitTarget int downHitTarget, boolean forceToLauncher, VelocityTracker tracker) {
226         RunningTaskInfo runningTaskInfo = mAM.getRunningTask(0);
227 
228         if (runningTaskInfo == null && !forceToLauncher) {
229             return mNoOpTouchConsumer;
230         } else if (forceToLauncher ||
231                 runningTaskInfo.topActivity.equals(mOverviewCommandHelper.overviewComponent)) {
232             return getOverviewConsumer();
233         } else {
234             if (tracker == null) {
235                 tracker = VelocityTracker.obtain();
236             }
237             return new OtherActivityTouchConsumer(this, runningTaskInfo, mRecentsModel,
238                             mOverviewCommandHelper.overviewIntent,
239                             mOverviewCommandHelper.getActivityControlHelper(), mMainThreadExecutor,
240                             mBackgroundThreadChoreographer, downHitTarget, mOverviewCallbacks,
241                             tracker);
242         }
243     }
244 
getOverviewConsumer()245     private TouchConsumer getOverviewConsumer() {
246         ActivityControlHelper activityHelper = mOverviewCommandHelper.getActivityControlHelper();
247         BaseDraggingActivity activity = activityHelper.getCreatedActivity();
248         if (activity == null) {
249             return mNoOpTouchConsumer;
250         }
251         return new OverviewTouchConsumer(activityHelper, activity);
252     }
253 
254     private static class OverviewTouchConsumer<T extends BaseDraggingActivity>
255             implements TouchConsumer {
256 
257         private final ActivityControlHelper<T> mActivityHelper;
258         private final T mActivity;
259         private final BaseDragLayer mTarget;
260         private final int[] mLocationOnScreen = new int[2];
261         private final PointF mDownPos = new PointF();
262         private final int mTouchSlop;
263         private final QuickScrubController mQuickScrubController;
264 
265         private boolean mTrackingStarted = false;
266         private boolean mInvalidated = false;
267 
268         private float mLastProgress = 0;
269         private boolean mStartPending = false;
270         private boolean mEndPending = false;
271 
OverviewTouchConsumer(ActivityControlHelper<T> activityHelper, T activity)272         OverviewTouchConsumer(ActivityControlHelper<T> activityHelper, T activity) {
273             mActivityHelper = activityHelper;
274             mActivity = activity;
275             mTarget = activity.getDragLayer();
276             mTouchSlop = ViewConfiguration.get(mTarget.getContext()).getScaledTouchSlop();
277 
278             mQuickScrubController = mActivity.<RecentsView>getOverviewPanel()
279                     .getQuickScrubController();
280         }
281 
282         @Override
accept(MotionEvent ev)283         public void accept(MotionEvent ev) {
284             if (mInvalidated) {
285                 return;
286             }
287             int action = ev.getActionMasked();
288             if (action == ACTION_DOWN) {
289                 mTrackingStarted = false;
290                 mDownPos.set(ev.getX(), ev.getY());
291             } else if (!mTrackingStarted) {
292                 switch (action) {
293                     case ACTION_POINTER_UP:
294                     case ACTION_POINTER_DOWN:
295                         if (!mTrackingStarted) {
296                             mInvalidated = true;
297                         }
298                         break;
299                     case ACTION_MOVE: {
300                         float displacement = ev.getY() - mDownPos.y;
301                         if (Math.abs(displacement) >= mTouchSlop) {
302                             mTarget.getLocationOnScreen(mLocationOnScreen);
303 
304                             // Send a down event only when mTouchSlop is crossed.
305                             MotionEvent down = MotionEvent.obtain(ev);
306                             down.setAction(ACTION_DOWN);
307                             sendEvent(down);
308                             down.recycle();
309                             mTrackingStarted = true;
310                         }
311                     }
312                 }
313             }
314 
315             if (mTrackingStarted) {
316                 sendEvent(ev);
317             }
318 
319             if (action == ACTION_UP || action == ACTION_CANCEL) {
320                 mInvalidated = true;
321             }
322         }
323 
sendEvent(MotionEvent ev)324         private void sendEvent(MotionEvent ev) {
325             int flags = ev.getEdgeFlags();
326             ev.setEdgeFlags(flags | EDGE_NAV_BAR);
327             ev.offsetLocation(-mLocationOnScreen[0], -mLocationOnScreen[1]);
328             if (!mTrackingStarted) {
329                 mTarget.onInterceptTouchEvent(ev);
330             }
331             mTarget.onTouchEvent(ev);
332             ev.offsetLocation(mLocationOnScreen[0], mLocationOnScreen[1]);
333             ev.setEdgeFlags(flags);
334         }
335 
336         @Override
onQuickStep(MotionEvent ev)337         public void onQuickStep(MotionEvent ev) {
338             if (mInvalidated) {
339                 return;
340             }
341             OverviewCallbacks.get(mActivity).closeAllWindows();
342             ActivityManagerWrapper.getInstance()
343                     .closeSystemWindows(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS);
344         }
345 
346         @Override
updateTouchTracking(int interactionType)347         public void updateTouchTracking(int interactionType) {
348             if (mInvalidated) {
349                 return;
350             }
351             if (interactionType == INTERACTION_QUICK_SCRUB) {
352                 if (!mQuickScrubController.prepareQuickScrub(TAG)) {
353                     mInvalidated = true;
354                     return;
355                 }
356                 OverviewCallbacks.get(mActivity).closeAllWindows();
357                 ActivityManagerWrapper.getInstance()
358                         .closeSystemWindows(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS);
359 
360                 mStartPending = true;
361                 Runnable action = () -> {
362                     if (!mQuickScrubController.prepareQuickScrub(TAG)) {
363                         mInvalidated = true;
364                         return;
365                     }
366                     mActivityHelper.onQuickInteractionStart(mActivity, null, true);
367                     mQuickScrubController.onQuickScrubProgress(mLastProgress);
368                     mStartPending = false;
369 
370                     if (mEndPending) {
371                         mQuickScrubController.onQuickScrubEnd();
372                         mEndPending = false;
373                     }
374                 };
375 
376                 mActivityHelper.executeOnWindowAvailable(mActivity, action);
377             }
378         }
379 
380         @Override
onQuickScrubEnd()381         public void onQuickScrubEnd() {
382             if (mInvalidated) {
383                 return;
384             }
385             if (mStartPending) {
386                 mEndPending = true;
387             } else {
388                 mQuickScrubController.onQuickScrubEnd();
389             }
390         }
391 
392         @Override
onQuickScrubProgress(float progress)393         public void onQuickScrubProgress(float progress) {
394             mLastProgress = progress;
395             if (mInvalidated || mStartPending) {
396                 return;
397             }
398             mQuickScrubController.onQuickScrubProgress(progress);
399         }
400 
401     }
402 
initBackgroundChoreographer()403     private void initBackgroundChoreographer() {
404         if (sRemoteUiThread == null) {
405             sRemoteUiThread = new HandlerThread("remote-ui");
406             sRemoteUiThread.start();
407         }
408         new Handler(sRemoteUiThread.getLooper()).post(() ->
409                 mBackgroundThreadChoreographer = Choreographer.getInstance());
410     }
411 }
412