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