| /* |
| * Copyright (C) 2017 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package com.android.quickstep; |
| |
| import static android.view.MotionEvent.ACTION_CANCEL; |
| import static android.view.MotionEvent.ACTION_DOWN; |
| import static android.view.MotionEvent.ACTION_MOVE; |
| import static android.view.MotionEvent.ACTION_POINTER_DOWN; |
| import static android.view.MotionEvent.ACTION_POINTER_UP; |
| import static android.view.MotionEvent.ACTION_UP; |
| import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS; |
| import static com.android.systemui.shared.system.NavigationBarCompat.HIT_TARGET_NONE; |
| |
| import android.annotation.TargetApi; |
| import android.app.ActivityManager.RunningTaskInfo; |
| import android.app.Service; |
| import android.content.Intent; |
| import android.graphics.PointF; |
| import android.os.Build; |
| import android.os.Handler; |
| import android.os.HandlerThread; |
| import android.os.IBinder; |
| import android.os.RemoteException; |
| import android.util.Log; |
| import android.util.SparseArray; |
| import android.view.Choreographer; |
| import android.view.MotionEvent; |
| import android.view.VelocityTracker; |
| import android.view.ViewConfiguration; |
| |
| import com.android.launcher3.BaseDraggingActivity; |
| import com.android.launcher3.MainThreadExecutor; |
| import com.android.launcher3.util.TraceHelper; |
| import com.android.launcher3.views.BaseDragLayer; |
| import com.android.quickstep.views.RecentsView; |
| import com.android.systemui.shared.recents.IOverviewProxy; |
| import com.android.systemui.shared.recents.ISystemUiProxy; |
| import com.android.systemui.shared.system.ActivityManagerWrapper; |
| import com.android.systemui.shared.system.ChoreographerCompat; |
| import com.android.systemui.shared.system.NavigationBarCompat.HitTarget; |
| |
| /** |
| * Service connected by system-UI for handling touch interaction. |
| */ |
| @TargetApi(Build.VERSION_CODES.O) |
| public class TouchInteractionService extends Service { |
| |
| private static final SparseArray<String> sMotionEventNames; |
| |
| static { |
| sMotionEventNames = new SparseArray<>(3); |
| sMotionEventNames.put(ACTION_DOWN, "ACTION_DOWN"); |
| sMotionEventNames.put(ACTION_UP, "ACTION_UP"); |
| sMotionEventNames.put(ACTION_CANCEL, "ACTION_CANCEL"); |
| } |
| |
| public static final int EDGE_NAV_BAR = 1 << 8; |
| |
| private static final String TAG = "TouchInteractionService"; |
| |
| /** |
| * A background thread used for handling UI for another window. |
| */ |
| private static HandlerThread sRemoteUiThread; |
| |
| private final IBinder mMyBinder = new IOverviewProxy.Stub() { |
| |
| @Override |
| public void onPreMotionEvent(@HitTarget int downHitTarget) throws RemoteException { |
| TraceHelper.beginSection("SysUiBinder"); |
| setupTouchConsumer(downHitTarget); |
| TraceHelper.partitionSection("SysUiBinder", "Down target " + downHitTarget); |
| } |
| |
| @Override |
| public void onMotionEvent(MotionEvent ev) { |
| mEventQueue.queue(ev); |
| |
| String name = sMotionEventNames.get(ev.getActionMasked()); |
| if (name != null){ |
| TraceHelper.partitionSection("SysUiBinder", name); |
| } |
| } |
| |
| @Override |
| public void onBind(ISystemUiProxy iSystemUiProxy) { |
| mISystemUiProxy = iSystemUiProxy; |
| mRecentsModel.setSystemUiProxy(mISystemUiProxy); |
| mOverviewInteractionState.setSystemUiProxy(mISystemUiProxy); |
| } |
| |
| @Override |
| public void onQuickScrubStart() { |
| mEventQueue.onQuickScrubStart(); |
| TraceHelper.partitionSection("SysUiBinder", "onQuickScrubStart"); |
| } |
| |
| @Override |
| public void onQuickScrubProgress(float progress) { |
| mEventQueue.onQuickScrubProgress(progress); |
| } |
| |
| @Override |
| public void onQuickScrubEnd() { |
| mEventQueue.onQuickScrubEnd(); |
| TraceHelper.endSection("SysUiBinder", "onQuickScrubEnd"); |
| } |
| |
| @Override |
| public void onOverviewToggle() { |
| mOverviewCommandHelper.onOverviewToggle(); |
| } |
| |
| @Override |
| public void onOverviewShown(boolean triggeredFromAltTab) { |
| if (triggeredFromAltTab) { |
| setupTouchConsumer(HIT_TARGET_NONE); |
| mEventQueue.onOverviewShownFromAltTab(); |
| } else { |
| mOverviewCommandHelper.onOverviewShown(); |
| } |
| } |
| |
| @Override |
| public void onOverviewHidden(boolean triggeredFromAltTab, boolean triggeredFromHomeKey) { |
| if (triggeredFromAltTab && !triggeredFromHomeKey) { |
| // onOverviewShownFromAltTab initiates quick scrub. Ending it here. |
| mEventQueue.onQuickScrubEnd(); |
| } |
| } |
| |
| @Override |
| public void onQuickStep(MotionEvent motionEvent) { |
| mEventQueue.onQuickStep(motionEvent); |
| TraceHelper.endSection("SysUiBinder", "onQuickStep"); |
| |
| } |
| |
| @Override |
| public void onTip(int actionType, int viewType) { |
| mOverviewCommandHelper.onTip(actionType, viewType); |
| } |
| }; |
| |
| private final TouchConsumer mNoOpTouchConsumer = (ev) -> {}; |
| |
| private static boolean sConnected = false; |
| |
| public static boolean isConnected() { |
| return sConnected; |
| } |
| |
| private ActivityManagerWrapper mAM; |
| private RecentsModel mRecentsModel; |
| private MotionEventQueue mEventQueue; |
| private MainThreadExecutor mMainThreadExecutor; |
| private ISystemUiProxy mISystemUiProxy; |
| private OverviewCommandHelper mOverviewCommandHelper; |
| private OverviewInteractionState mOverviewInteractionState; |
| private OverviewCallbacks mOverviewCallbacks; |
| |
| private Choreographer mMainThreadChoreographer; |
| private Choreographer mBackgroundThreadChoreographer; |
| |
| @Override |
| public void onCreate() { |
| super.onCreate(); |
| mAM = ActivityManagerWrapper.getInstance(); |
| mRecentsModel = RecentsModel.getInstance(this); |
| mRecentsModel.setPreloadTasksInBackground(true); |
| mMainThreadExecutor = new MainThreadExecutor(); |
| mOverviewCommandHelper = new OverviewCommandHelper(this); |
| mMainThreadChoreographer = Choreographer.getInstance(); |
| mEventQueue = new MotionEventQueue(mMainThreadChoreographer, mNoOpTouchConsumer); |
| mOverviewInteractionState = OverviewInteractionState.getInstance(this); |
| mOverviewCallbacks = OverviewCallbacks.get(this); |
| |
| sConnected = true; |
| |
| // Temporarily disable model preload |
| // new ModelPreload().start(this); |
| initBackgroundChoreographer(); |
| } |
| |
| @Override |
| public void onDestroy() { |
| mOverviewCommandHelper.onDestroy(); |
| sConnected = false; |
| super.onDestroy(); |
| } |
| |
| @Override |
| public IBinder onBind(Intent intent) { |
| Log.d(TAG, "Touch service connected"); |
| return mMyBinder; |
| } |
| |
| private void setupTouchConsumer(@HitTarget int downHitTarget) { |
| mEventQueue.reset(); |
| TouchConsumer oldConsumer = mEventQueue.getConsumer(); |
| if (oldConsumer.deferNextEventToMainThread()) { |
| mEventQueue = new MotionEventQueue(mMainThreadChoreographer, |
| new DeferredTouchConsumer((v) -> getCurrentTouchConsumer(downHitTarget, |
| oldConsumer.forceToLauncherConsumer(), v))); |
| mEventQueue.deferInit(); |
| } else { |
| mEventQueue = new MotionEventQueue( |
| mMainThreadChoreographer, getCurrentTouchConsumer(downHitTarget, false, null)); |
| } |
| } |
| |
| private TouchConsumer getCurrentTouchConsumer( |
| @HitTarget int downHitTarget, boolean forceToLauncher, VelocityTracker tracker) { |
| RunningTaskInfo runningTaskInfo = mAM.getRunningTask(0); |
| |
| if (runningTaskInfo == null && !forceToLauncher) { |
| return mNoOpTouchConsumer; |
| } else if (forceToLauncher || |
| runningTaskInfo.topActivity.equals(mOverviewCommandHelper.overviewComponent)) { |
| return getOverviewConsumer(); |
| } else { |
| if (tracker == null) { |
| tracker = VelocityTracker.obtain(); |
| } |
| return new OtherActivityTouchConsumer(this, runningTaskInfo, mRecentsModel, |
| mOverviewCommandHelper.overviewIntent, |
| mOverviewCommandHelper.getActivityControlHelper(), mMainThreadExecutor, |
| mBackgroundThreadChoreographer, downHitTarget, mOverviewCallbacks, |
| tracker); |
| } |
| } |
| |
| private TouchConsumer getOverviewConsumer() { |
| ActivityControlHelper activityHelper = mOverviewCommandHelper.getActivityControlHelper(); |
| BaseDraggingActivity activity = activityHelper.getCreatedActivity(); |
| if (activity == null) { |
| return mNoOpTouchConsumer; |
| } |
| return new OverviewTouchConsumer(activityHelper, activity); |
| } |
| |
| private static class OverviewTouchConsumer<T extends BaseDraggingActivity> |
| implements TouchConsumer { |
| |
| private final ActivityControlHelper<T> mActivityHelper; |
| private final T mActivity; |
| private final BaseDragLayer mTarget; |
| private final int[] mLocationOnScreen = new int[2]; |
| private final PointF mDownPos = new PointF(); |
| private final int mTouchSlop; |
| private final QuickScrubController mQuickScrubController; |
| |
| private boolean mTrackingStarted = false; |
| private boolean mInvalidated = false; |
| |
| private float mLastProgress = 0; |
| private boolean mStartPending = false; |
| private boolean mEndPending = false; |
| |
| OverviewTouchConsumer(ActivityControlHelper<T> activityHelper, T activity) { |
| mActivityHelper = activityHelper; |
| mActivity = activity; |
| mTarget = activity.getDragLayer(); |
| mTouchSlop = ViewConfiguration.get(mTarget.getContext()).getScaledTouchSlop(); |
| |
| mQuickScrubController = mActivity.<RecentsView>getOverviewPanel() |
| .getQuickScrubController(); |
| } |
| |
| @Override |
| public void accept(MotionEvent ev) { |
| if (mInvalidated) { |
| return; |
| } |
| int action = ev.getActionMasked(); |
| if (action == ACTION_DOWN) { |
| mTrackingStarted = false; |
| mDownPos.set(ev.getX(), ev.getY()); |
| } else if (!mTrackingStarted) { |
| switch (action) { |
| case ACTION_POINTER_UP: |
| case ACTION_POINTER_DOWN: |
| if (!mTrackingStarted) { |
| mInvalidated = true; |
| } |
| break; |
| case ACTION_MOVE: { |
| float displacement = ev.getY() - mDownPos.y; |
| if (Math.abs(displacement) >= mTouchSlop) { |
| mTarget.getLocationOnScreen(mLocationOnScreen); |
| |
| // Send a down event only when mTouchSlop is crossed. |
| MotionEvent down = MotionEvent.obtain(ev); |
| down.setAction(ACTION_DOWN); |
| sendEvent(down); |
| down.recycle(); |
| mTrackingStarted = true; |
| } |
| } |
| } |
| } |
| |
| if (mTrackingStarted) { |
| sendEvent(ev); |
| } |
| |
| if (action == ACTION_UP || action == ACTION_CANCEL) { |
| mInvalidated = true; |
| } |
| } |
| |
| private void sendEvent(MotionEvent ev) { |
| int flags = ev.getEdgeFlags(); |
| ev.setEdgeFlags(flags | EDGE_NAV_BAR); |
| ev.offsetLocation(-mLocationOnScreen[0], -mLocationOnScreen[1]); |
| if (!mTrackingStarted) { |
| mTarget.onInterceptTouchEvent(ev); |
| } |
| mTarget.onTouchEvent(ev); |
| ev.offsetLocation(mLocationOnScreen[0], mLocationOnScreen[1]); |
| ev.setEdgeFlags(flags); |
| } |
| |
| @Override |
| public void onQuickStep(MotionEvent ev) { |
| if (mInvalidated) { |
| return; |
| } |
| OverviewCallbacks.get(mActivity).closeAllWindows(); |
| ActivityManagerWrapper.getInstance() |
| .closeSystemWindows(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS); |
| } |
| |
| @Override |
| public void updateTouchTracking(int interactionType) { |
| if (mInvalidated) { |
| return; |
| } |
| if (interactionType == INTERACTION_QUICK_SCRUB) { |
| if (!mQuickScrubController.prepareQuickScrub(TAG)) { |
| mInvalidated = true; |
| return; |
| } |
| OverviewCallbacks.get(mActivity).closeAllWindows(); |
| ActivityManagerWrapper.getInstance() |
| .closeSystemWindows(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS); |
| |
| mStartPending = true; |
| Runnable action = () -> { |
| if (!mQuickScrubController.prepareQuickScrub(TAG)) { |
| mInvalidated = true; |
| return; |
| } |
| mActivityHelper.onQuickInteractionStart(mActivity, null, true); |
| mQuickScrubController.onQuickScrubProgress(mLastProgress); |
| mStartPending = false; |
| |
| if (mEndPending) { |
| mQuickScrubController.onQuickScrubEnd(); |
| mEndPending = false; |
| } |
| }; |
| |
| mActivityHelper.executeOnWindowAvailable(mActivity, action); |
| } |
| } |
| |
| @Override |
| public void onQuickScrubEnd() { |
| if (mInvalidated) { |
| return; |
| } |
| if (mStartPending) { |
| mEndPending = true; |
| } else { |
| mQuickScrubController.onQuickScrubEnd(); |
| } |
| } |
| |
| @Override |
| public void onQuickScrubProgress(float progress) { |
| mLastProgress = progress; |
| if (mInvalidated || mStartPending) { |
| return; |
| } |
| mQuickScrubController.onQuickScrubProgress(progress); |
| } |
| |
| } |
| |
| private void initBackgroundChoreographer() { |
| if (sRemoteUiThread == null) { |
| sRemoteUiThread = new HandlerThread("remote-ui"); |
| sRemoteUiThread.start(); |
| } |
| new Handler(sRemoteUiThread.getLooper()).post(() -> |
| mBackgroundThreadChoreographer = ChoreographerCompat.getSfInstance()); |
| } |
| } |