Android O之前的虚拟按键,基本的控制方法都是在SystemUI中做处理的,在Android 10上为了在手势导航操作时其动画更加流畅,与Launcher互动效果更好,google的设计师就把手势导航相关的操作放到了Launcher3中,而且为了与SystemUI进行信息同步,利用两个aidl的文件利用binder做Launcher3与Systemui之前的进程通信。
在分析之前我们来看下AndroidManifest的一个开机启动属性
android:directBootAware="true"
这个属性对于手势导航的启动是有决定性的作用,有了这属性,其无论是应用还是服务都能第一时间启动,它保证了手势导航功能在系统准备好之前做好初始化。
SystemUI是在application定义了这个属性
而Launcher3是只对TouchInteractionService进行了设置,而且TouchInteractionService设置了一个action“android.intent.action.QUICKSTEP_SERVICE”,这为SystemUI绑定TouchInteractionService提供了向导。
我也在真机中测试了下各个应用的启动时间,整个过程如下,供大家参考:
开机动画 启动时间:15:53:52
12-05 15:53:52.606939 824 871 D BootAnimation: initialize opengl and egl
SystemUI 启动时间:15:54:09
<6>[ 64.260830] .(3)[877:ActivityManager][name:bootprof&][name:bootprof&]BOOTPROF: 64260.824614:AP_Init:[added application]:[com.android.systemui]:[com.android.systemui]:pid:1412:(PersistAP)
12-05 15:54:09.582747 652 877 I am_proc_start: [0,1265,10119,com.android.systemui,service,{com.android.systemui/com.android.systemui.ImageWallpaper}]
Launcher3(TouchInteractionService) 启动时间:15:54:15
<6>[ 68.148559] .(3)[877:ActivityManager][name:bootprof&][name:bootprof&]BOOTPROF: 68148.554315:AP_Init:[service]:[com.android.launcher3]:[{com.android.launcher3/com.android.quickstep.TouchInteractionService}]:pid:1644
12-05 15:54:15.729360 652 877 I am_proc_start: [0,1644,10114,com.android.launcher3,service,{com.android.launcher3/com.android.quickstep.TouchInteractionService}]
12-05 15:54:15.731171 652 877 I ActivityManager: Start proc 1644:com.android.launcher3/u0a114 for service {com.android.launcher3/com.android.quickstep.TouchInteractionService}
(一)先看下Launcher3与Systemui之间做对接的服务
这两个服务为 SystemUI (OverviewProxyService)和Launcher3(TouchInteractionService) 。
OverviewProxyService:
Class to send information from overview to launcher with a binder
是随着SystemUI应用启动而生成的,作为一个单例类存在于SystemUI这种系统应用中,而非一个正常的”Service“
OverviewProxyService中实现了ISystemUiProxy.aidl,如下所示:
SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
private ISystemUiProxy mSysUiProxy = new ISystemUiProxy.Stub() {
@Override
public void startScreenPinning(int taskId) {
if (!verifyCaller("startScreenPinning")) {
return;
}
long token = Binder.clearCallingIdentity();
try {
mHandler.post(() -> {
StatusBar statusBar = SysUiServiceProvider.getComponent(mContext,
StatusBar.class);
if (statusBar != null) {
statusBar.showScreenPinningRequest(taskId, false /* allowCancel */);
}
});
} finally {
Binder.restoreCallingIdentity(token);
}
}
@Override
public void stopScreenPinning() {
if (!verifyCaller("stopScreenPinning")) {
return;
}
long token = Binder.clearCallingIdentity();
try {
mHandler.post(() -> {
try {
ActivityTaskManager.getService().stopSystemLockTaskMode();
} catch (RemoteException e) {
Log.e(TAG_OPS, "Failed to stop screen pinning");
}
});
} finally {
Binder.restoreCallingIdentity(token);
}
}
@Override
public void onStatusBarMotionEvent(MotionEvent event) {
if (!verifyCaller("onStatusBarMotionEvent")) {
return;
}
long token = Binder.clearCallingIdentity();
try {
// TODO move this logic to message queue
mHandler.post(()->{
StatusBar bar = SysUiServiceProvider.getComponent(mContext, StatusBar.class);
if (bar != null) {
bar.dispatchNotificationsPanelTouchEvent(event);
int action = event.getActionMasked();
if (action == ACTION_DOWN) {
mStatusBarGestureDownEvent = MotionEvent.obtain(event);
}
if (action == ACTION_UP || action == ACTION_CANCEL) {
mStatusBarGestureDownEvent.recycle();
mStatusBarGestureDownEvent = null;
}
event.recycle();
}
});
} finally {
Binder.restoreCallingIdentity(token);
}
}
@Override
public void onSplitScreenInvoked() {
if (!verifyCaller("onSplitScreenInvoked")) {
return;
}
long token = Binder.clearCallingIdentity();
try {
Divider divider = SysUiServiceProvider.getComponent(mContext, Divider.class);
if (divider != null) {
divider.onDockedFirstAnimationFrame();
}
} finally {
Binder.restoreCallingIdentity(token);
}
}
@Override
public void onOverviewShown(boolean fromHome) {
if (!verifyCaller("onOverviewShown")) {
return;
}
long token = Binder.clearCallingIdentity();
try {
mHandler.post(() -> {
for (int i = mConnectionCallbacks.size() - 1; i >= 0; --i) {
mConnectionCallbacks.get(i).onOverviewShown(fromHome);
}
});
} finally {
Binder.restoreCallingIdentity(token);
}
}
/***还有很多其他功能,在ISystemUiProxy.aidl文件中都有功能相关的描述
SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl***/
};
mSysUiProxy是作为binder服务端存在的,在SystemUI等待着类似屏幕固定,分屏,最近任务展示等方法的回调。
而mSysUiProxy与Launcher3与进行通信的开端便是“ServiceConnection”服务的链接绑定,如下所示:
SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
/*service对应的action,用于绑定服务*/
private static final String ACTION_QUICKSTEP = "android.intent.action.QUICKSTEP_SERVICE";
private void internalConnectToCurrentUser() {
/*断开之间的所有链接*/
disconnectFromLauncherService();
// If user has not setup yet or already connected, do not try to connect
if (!mDeviceProvisionedController.isCurrentUserSetup() || !isEnabled()) {
Log.v(TAG_OPS, "Cannot attempt connection, is setup "
+ mDeviceProvisionedController.isCurrentUserSetup() + ", is enabled "
+ isEnabled());
return;
}
mHandler.removeCallbacks(mConnectionRunnable);
/*Intent填入指定的action*/
Intent launcherServiceIntent = new Intent(ACTION_QUICKSTEP)
.setPackage(mRecentsComponentName.getPackageName());
try {
/*绑定服务*/
mBound = mContext.bindServiceAsUser(launcherServiceIntent,
mOverviewServiceConnection,
Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE,
UserHandle.of(mDeviceProvisionedController.getCurrentUser()));
} catch (SecurityException e) {
Log.e(TAG_OPS, "Unable to bind because of security error", e);
}
if (mBound) {
// Ensure that connection has been established even if it thinks it is bound
mHandler.postDelayed(mDeferredConnectionCallback, DEFERRED_CALLBACK_MILLIS);
} else {
// Retry after exponential backoff timeout
retryConnectionWithBackoff();
}
}
private final ServiceConnection mOverviewServiceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
mConnectionBackoffAttempts = 0;
mHandler.removeCallbacks(mDeferredConnectionCallback);
try {
service.linkToDeath(mOverviewServiceDeathRcpt, 0);
} catch (RemoteException e) {
// Failed to link to death (process may have died between binding and connecting),
// just unbind the service for now and retry again
Log.e(TAG_OPS, "Lost connection to launcher service", e);
disconnectFromLauncherService();
retryConnectionWithBackoff();
return;
}
mCurrentBoundedUserId = mDeviceProvisionedController.getCurrentUser();
/*获取IOverviewProxy的代理*/
mOverviewProxy = IOverviewProxy.Stub.asInterface(service);
Bundle params = new Bundle();
params.putBinder(KEY_EXTRA_SYSUI_PROXY, mSysUiProxy.asBinder());
params.putFloat(KEY_EXTRA_WINDOW_CORNER_RADIUS, mWindowCornerRadius);
params.putBoolean(KEY_EXTRA_SUPPORTS_WINDOW_CORNERS, mSupportsRoundedCornersOnWindows);
try {
/*把ISystemUiProxy推荐给Launcher3*/
mOverviewProxy.onInitialize(params);
} catch (RemoteException e) {
mCurrentBoundedUserId = -1;
Log.e(TAG_OPS, "Failed to call onInitialize()", e);
}
dispatchNavButtonBounds();
// Update the systemui state flags
updateSystemUiStateFlags();
notifyConnectionChanged();
}
@Override
public void onNullBinding(ComponentName name) {
Log.w(TAG_OPS, "Null binding of '" + name + "', try reconnecting");
mCurrentBoundedUserId = -1;
retryConnectionWithBackoff();
}
@Override
public void onBindingDied(ComponentName name) {
Log.w(TAG_OPS, "Binding died of '" + name + "', try reconnecting");
mCurrentBoundedUserId = -1;
retryConnectionWithBackoff();
}
@Override
public void onServiceDisconnected(ComponentName name) {
// Do nothing
mCurrentBoundedUserId = -1;
}
};
在mOverviewServiceConnection的onServiceConnected方法中,又看到了另外一个aidl的实现"IOverviewProxy"。这个对象是在服务绑定后通过binder传递过来的,那么"IOverviewProxy"的服务端就应该在Launcher3中了。当"IOverviewProxy"出现在SystemUI中的第一时间,mSysUiProxy就迫不及待的把自己打包好放到了Bundle(mSysUiProxy这个服务者在很尽责的推销自己嘛),然后又由mOverviewProxy反向传递给了Launcher3。
TouchInteractionService
Service connected by system-UI for handling touch interaction.
TouchInteractionService 是Launcher3中的Service,注册于AndroidManifest中,服务有directBootAware属性,为开机即启动的服务
下面就要看下IOverviewProxy在TouchInteractionService中的实现了:
Launcher3/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
private final IBinder mMyBinder = new IOverviewProxy.Stub() {
public void onActiveNavBarRegionChanges(Region region) {
mActiveNavBarRegion = region;
}
/*从SystemUI而来,携带着在SystemUI中实现的ISystemUiProxy*/
public void onInitialize(Bundle bundle) {
mISystemUiProxy = ISystemUiProxy.Stub
.asInterface(bundle.getBinder(KEY_EXTRA_SYSUI_PROXY));
MAIN_THREAD_EXECUTOR.execute(TouchInteractionService.this::initInputMonitor);
MAIN_THREAD_EXECUTOR.execute(TouchInteractionService.this::onSystemUiProxySet);
}
@Override
public void onOverviewToggle() {
mOverviewCommandHelper.onOverviewToggle();
}
@Override
public void onOverviewShown(boolean triggeredFromAltTab) {
mOverviewCommandHelper.onOverviewShown(triggeredFromAltTab);
}
@Override
public void onOverviewHidden(boolean triggeredFromAltTab, boolean triggeredFromHomeKey) {
if (triggeredFromAltTab && !triggeredFromHomeKey) {
// onOverviewShownFromAltTab hides the overview and ends at the target app
mOverviewCommandHelper.onOverviewHidden();
}
}
/***一些最近任务界面,Back按键等事件的互通,具体
SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl***/
};
@Override
public IBinder onBind(Intent intent) {
Log.d(TAG, "Touch service connected");
/*当绑定成功后,反馈给SystemUI的IOverviewProxy*/
return mMyBinder;
}
当上面的IOverviewProxy函数onInitialize执行完成之后,SystemUI也就和Launcher3胜利会师了,从而进入了你中有我,我中有你的状态。整个流程如下图所示:
当Launcher3成功与SystemUI能够正常通信之后Launcher3就开始注册屏幕Touch事件的监听了。
Launcher3/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
private final IBinder mMyBinder = new IOverviewProxy.Stub() {
public void onInitialize(Bundle bundle) {
/*获取binder代理*/
mISystemUiProxy = ISystemUiProxy.Stub
.asInterface(bundle.getBinder(KEY_EXTRA_SYSUI_PROXY));
/*监听屏幕触摸事件,此处深追很有意思,感兴趣的话可以看看,或者谁能推给我一个blog,我也想看看*/
MAIN_THREAD_EXECUTOR.execute(TouchInteractionService.this::initInputMonitor);
/*把mISystemUiProxy传给会用到的模块*/
MAIN_THREAD_EXECUTOR.execute(TouchInteractionService.this::onSystemUiProxySet);
}
}
private void initInputMonitor() {
if (!mMode.hasGestures || mISystemUiProxy == null) {
return;
}
disposeEventHandlers();
try {
/*这个地方就是注册监听触摸事件的地方了*/
mInputMonitorCompat = InputMonitorCompat.fromBundle(mISystemUiProxy
.monitorGestureInput("swipe-up", mDefaultDisplayId), KEY_EXTRA_INPUT_MONITOR);
mInputEventReceiver = mInputMonitorCompat.getInputReceiver(Looper.getMainLooper(),
mMainChoreographer, this::onInputEvent);
} catch (RemoteException e) {
Log.e(TAG, "Unable to create input monitor", e);
}
initTouchBounds();
}
从上面看到,Launcher3处理触摸事件的方法是onInputEvent,其会根据系统处于不同的状态而生成不同的事件处理器,刚开始看的时候到这里是一头雾水的,完全没有办法理清整个事件的处理过程,这个时候乜办法就只能硬着头皮看了,同时我也从Android的开源库去查询googel工程师的提交记录。经过一段时间的研究,我是被google开发者的设计思路惊艳到了,将近10种的触摸处理机制被很好的整合到了一起。以下是具体的代码实现:
quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
private void onInputEvent(InputEvent ev) {
if (!(ev instanceof MotionEvent)) {
Log.e(TAG, "Unknown event " + ev);
return;
}
MotionEvent event = (MotionEvent) ev;
TOUCH_INTERACTION_LOG.addLog("onMotionEvent", event.getActionMasked());
if (event.getAction() == ACTION_DOWN) {
/*根据手指Down时的系统状态,以及实时更新的mSwipeTouchRegion所包含的手势导航的有效位置,去获取不同的处理器*/
if (mSwipeTouchRegion.contains(event.getX(), event.getY())) {
boolean useSharedState = mConsumer.useSharedSwipeState();
mConsumer.onConsumerAboutToBeSwitched();
mConsumer = newConsumer(useSharedState, event);
TOUCH_INTERACTION_LOG.addLog("setInputConsumer", mConsumer.getType());
mUncheckedConsumer = mConsumer;
} else if (mIsUserUnlocked && mMode == Mode.NO_BUTTON
&& canTriggerAssistantAction(event)) {
// Do not change mConsumer as if there is an ongoing QuickSwitch gesture, we should
// not interrupt it. QuickSwitch assumes that interruption can only happen if the
// next gesture is also quick switch.
mUncheckedConsumer =
new AssistantTouchConsumer(this, mISystemUiProxy,
mOverviewComponentObserver.getActivityControlHelper(),
InputConsumer.NO_OP, mInputMonitorCompat);
} else {
mUncheckedConsumer = InputConsumer.NO_OP;
}
}
mUncheckedConsumer.onMotionEvent(event);
}
private InputConsumer newConsumer(boolean useSharedState, MotionEvent event) {
boolean isInValidSystemUiState = validSystemUiFlags();
if (!mIsUserUnlocked) {
if (isInValidSystemUiState) {
// This handles apps launched in direct boot mode (e.g. dialer) as well as apps
// launched while device is locked even after exiting direct boot mode (e.g. camera).
return createDeviceLockedInputConsumer(mAM.getRunningTask(0));
} else {
return mResetGestureInputConsumer;
}
}
// When using sharedState, bypass systemState check as this is a followup gesture and the
// first gesture started in a valid system state.
InputConsumer base = isInValidSystemUiState || useSharedState
? newBaseConsumer(useSharedState, event) : mResetGestureInputConsumer;
if (mMode == Mode.NO_BUTTON) {
final ActivityControlHelper activityControl =
mOverviewComponentObserver.getActivityControlHelper();
if (canTriggerAssistantAction(event)) {
base = new AssistantTouchConsumer(this, mISystemUiProxy, activityControl, base,
mInputMonitorCompat);
}
if ((mSystemUiStateFlags & SYSUI_STATE_SCREEN_PINNING) != 0) {
// Note: we only allow accessibility to wrap this, and it replaces the previous
// base input consumer (which should be NO_OP anyway since topTaskLocked == true).
base = new ScreenPinnedInputConsumer(this, mISystemUiProxy, activityControl);
}
if ((mSystemUiStateFlags & SYSUI_STATE_A11Y_BUTTON_CLICKABLE) != 0) {
base = new AccessibilityInputConsumer(this, mISystemUiProxy,
(mSystemUiStateFlags & SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE) != 0, base,
mInputMonitorCompat, mSwipeTouchRegion);
}
} else {
if ((mSystemUiStateFlags & SYSUI_STATE_SCREEN_PINNING) != 0) {
base = mResetGestureInputConsumer;
}
}
return base;
}
private InputConsumer newBaseConsumer(boolean useSharedState, MotionEvent event) {
final RunningTaskInfo runningTaskInfo = mAM.getRunningTask(0);
if (!useSharedState) {
sSwipeSharedState.clearAllState(false /* finishAnimation */);
}
if ((mSystemUiStateFlags & SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED) != 0) {
// This handles apps showing over the lockscreen (e.g. camera)
return createDeviceLockedInputConsumer(runningTaskInfo);
}
final ActivityControlHelper activityControl =
mOverviewComponentObserver.getActivityControlHelper();
if (runningTaskInfo == null && !sSwipeSharedState.goingToLauncher
&& !sSwipeSharedState.recentsAnimationFinishInterrupted) {
return mResetGestureInputConsumer;
} else if (sSwipeSharedState.recentsAnimationFinishInterrupted) {
// If the finish animation was interrupted, then continue using the other activity input
// consumer but with the next task as the running task
RunningTaskInfo info = new ActivityManager.RunningTaskInfo();
info.id = sSwipeSharedState.nextRunningTaskId;
return createOtherActivityInputConsumer(event, info);
} else if (sSwipeSharedState.goingToLauncher || activityControl.isResumed()) {
return createOverviewInputConsumer(event);
} else if (ENABLE_QUICKSTEP_LIVE_TILE.get() && activityControl.isInLiveTileMode()) {
return createOverviewInputConsumer(event);
} else if (mGestureBlockingActivity != null && runningTaskInfo != null
&& mGestureBlockingActivity.equals(runningTaskInfo.topActivity)) {
return mResetGestureInputConsumer;
} else if (mMode == Mode.NO_BUTTON && !mOverviewComponentObserver.isHomeAndOverviewSame()) {
return new FallbackNoButtonInputConsumer(this, activityControl,
mInputMonitorCompat, sSwipeSharedState, mSwipeTouchRegion,
mOverviewComponentObserver, disableHorizontalSwipe(event), runningTaskInfo);
} else {
return createOtherActivityInputConsumer(event, runningTaskInfo);
}
}
设计者为了在不同系统状态下区分处理触摸事件,分别设计了可以处理多种输入事件的消费者。例如OverviewInputConsumer就是在桌面或者最近任务界面处理事件的消费者,而OtherActivityInputConsumer是非桌面情况下出现的事件消费者,以下是我在源码中以及对应google commiter提交的说明而总结出来的消费者的对应信息供大家参考:
AssistantTouchConsumer:
Touch consumer for handling events to launch assistant from launcher
AccessibilityInputConsumer:
Touch consumer for two finger swipe actions for accessibility actions
DeviceLockedInputConsumer:
A dummy input consumer used when the device is still locked, e.g. from secure camera.
FallbackNoButtonInputConsumer:
In case of 3P launcher, swipe-up will go to Launcher and there will be no way to reach overview
OtherActivityInputConsumer:
Input consumer for handling events originating from an activity other than Launcher
OverviewInputConsumer:
Input consumer for handling touch on the recents/Launcher activity.
OverviewWithoutFocusInputConsumer:
Using a separate InputConsumer when Launcher is resumed but not focused. When Launcher is not focused and we try to dispatch events to Launcher, it can lead to a inconsistent state. For eg, NavBarTouchController was trying to take launcher from NORMAL to NORMAL state causing the endCallback to be called immediately, which in turn didn't clear Swipedetetor state.
ResetGestureInputConsumer:
A NO_OP input consumer which also resets any pending gesture
Bypass systemstate check when using shared state
System state can change using the interaction, for eg: an app can enter immersive mode in the middle of quickswitch. Ignore such changes to prevent system gestures getting blocked by an app
Fixing nullpointer in device locked consumer construction when user is not locked yet Creating a fallback resetGesture input consumer, which cancels any pending transition in case we missed to cancel it
ScreenPinnedInputConsumer:
An input consumer that detects swipe up and hold to exit screen pinning mode.
以下常见的两种处理过程,我贴出来下,对第一种略作分析,以用来抛砖引玉。
(1)在桌面/桌面最近任务时手势导航处的触摸事件处理过程:
Launcher3/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverviewInputConsumer.java
private final BaseDragLayer mTarget;
/*Predicate是个断言式接口其参数是,也就是给一个参数T,返回boolean类型的结果.Predicate的具体实现也是根据传入的lambda表达式来决定的
boolean test(T t);
*/
private final Predicate mEventReceiver;
public OverviewInputConsumer(T activity, @Nullable InputMonitorCompat inputMonitor,
boolean startingInActivityBounds) {
/*获取最上层图层*/
mTarget = activity.getDragLayer();
if (startingInActivityBounds) {
mEventReceiver = mTarget::dispatchTouchEvent;
mProxyTouch = true;
} else {
// Only proxy touches to controllers if we are starting touch from nav bar.
mEventReceiver = mTarget::proxyTouchEvent;
mTarget.getLocationOnScreen(mLocationOnScreen);
mProxyTouch = mTarget.prepareProxyEventStarting();
}
}
@Override
public void onMotionEvent(MotionEvent ev) {
if (!mProxyTouch) {
return;
}
int flags = ev.getEdgeFlags();
if (!mStartingInActivityBounds) {
ev.setEdgeFlags(flags | Utilities.EDGE_NAV_BAR);
}
/*重新设置MotionEvent,以便控制桌面最上层的Layer*/
ev.offsetLocation(-mLocationOnScreen[0], -mLocationOnScreen[1]);
boolean handled = mEventReceiver.test(ev);
/*恢复MotionEvent*/
ev.offsetLocation(mLocationOnScreen[0], mLocationOnScreen[1]);
ev.setEdgeFlags(flags);
if (!mTargetHandledTouch && handled) {
mTargetHandledTouch = true;
if (!mStartingInActivityBounds) {
OverviewCallbacks.get(mActivity).closeAllWindows();
ActivityManagerWrapper.getInstance()
.closeSystemWindows(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS);
TOUCH_INTERACTION_LOG.addLog("startQuickstep");
}
//获取当前正在发送到此监视器的所有当前指针事件流,并为通常会获取它们的窗口生成适当的取消
if (mInputMonitor != null) {
mInputMonitor.pilferPointers();
}
}
}
从上面看到真正的事件消费者就是在桌面的最上层“图层”了,其实现如下所示:
Launcher3/src/com/android/launcher3/views/BaseDragLayer.java
public boolean proxyTouchEvent(MotionEvent ev) {
boolean handled;、
/*获取控制器*/
if (mProxyTouchController != null) {
handled = mProxyTouchController.onControllerTouchEvent(ev);
} else {
mProxyTouchController = findControllerToHandleTouch(ev);
handled = mProxyTouchController != null;
}
int action = ev.getAction();
/*触摸事件取消时,重置控制器*/
if (action == ACTION_UP || action == ACTION_CANCEL) {
mProxyTouchController = null;
mTouchDispatchState &= ~TOUCH_DISPATCHING_PROXY;
}
return handled;
}
private TouchController findControllerToHandleTouch(MotionEvent ev) {
if (shouldDisableGestures(ev)) return null;
/*当桌面有类似有pop窗时,直接把事件传给这个View*/
AbstractFloatingView topView = AbstractFloatingView.getTopOpenView(mActivity);
if (topView != null && topView.onControllerInterceptTouchEvent(ev)) {
return topView;
}
/*当桌面没有pop窗时,把事件传给每一个TouchController,当被消费时返回*/
for (TouchController controller : mControllers) {
if (controller.onControllerInterceptTouchEvent(ev)) {
return controller;
}
}
return null;
}
以上就是在桌面时从获取触摸事件到传递到指点消费者的过程,有popView时优先popView,无popView时遍历所有的控制器,以做处理。其设计的控制器也有多个,我列一下,各位有兴趣就自己看吧:
FlingAndHoldTouchController
Touch controller which handles swipe and hold to go to Overview
NavBarToHomeTouchController
Handles swiping up on the nav bar to go home from launcher, e.g. overview or all apps.
OverviewToAllAppsTouchController
Touch controller from going from OVERVIEW to ALL_APPS.This is used in landscape mode. It is also used in portrait mode for the fallback recents.
QuickSwitchTouchController
Handles quick switching to a recent task from the home screen.
TaskViewTouchController
Touch controller for handling task view card swipes
(2)在非桌面的事件处理过程,简单贴一下,其代码应该会涉及到window的实时动画,所以这个估计是我下个要啃的地方:
Launcher3/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
@Override
public void onMotionEvent(MotionEvent ev) {
if (mVelocityTracker == null) {
return;
}
// Proxy events to recents view
if (mPaddedWindowMoveSlop && mInteractionHandler != null
&& !mRecentsViewDispatcher.hasConsumer()) {
mRecentsViewDispatcher.setConsumer(mInteractionHandler.getRecentsViewDispatcher(
mNavBarPosition.getRotationMode()));
}
int edgeFlags = ev.getEdgeFlags();
ev.setEdgeFlags(edgeFlags | EDGE_NAV_BAR);
mRecentsViewDispatcher.dispatchEvent(ev);
ev.setEdgeFlags(edgeFlags);
mVelocityTracker.addMovement(ev);
if (ev.getActionMasked() == ACTION_POINTER_UP) {
mVelocityTracker.clear();
mMotionPauseDetector.clear();
}
switch (ev.getActionMasked()) {
case ACTION_DOWN: {
RaceConditionTracker.onEvent(DOWN_EVT, ENTER);
TraceHelper.beginSection("TouchInt");
mActivePointerId = ev.getPointerId(0);
mDownPos.set(ev.getX(), ev.getY());
mLastPos.set(mDownPos);
// Start the window animation on down to give more time for launcher to draw if the
// user didn't start the gesture over the back button
if (!mIsDeferredDownTarget) {
startTouchTrackingForWindowAnimation(ev.getEventTime());
}
RaceConditionTracker.onEvent(DOWN_EVT, EXIT);
break;
}
case ACTION_POINTER_DOWN: {
if (!mPassedPilferInputSlop) {
// Cancel interaction in case of multi-touch interaction
int ptrIdx = ev.getActionIndex();
if (!mSwipeTouchRegion.contains(ev.getX(ptrIdx), ev.getY(ptrIdx))) {
forceCancelGesture(ev);
}
}
break;
}
case ACTION_POINTER_UP: {
int ptrIdx = ev.getActionIndex();
int ptrId = ev.getPointerId(ptrIdx);
if (ptrId == mActivePointerId) {
final int newPointerIdx = ptrIdx == 0 ? 1 : 0;
mDownPos.set(
ev.getX(newPointerIdx) - (mLastPos.x - mDownPos.x),
ev.getY(newPointerIdx) - (mLastPos.y - mDownPos.y));
mLastPos.set(ev.getX(newPointerIdx), ev.getY(newPointerIdx));
mActivePointerId = ev.getPointerId(newPointerIdx);
}
break;
}
case ACTION_MOVE: {
int pointerIndex = ev.findPointerIndex(mActivePointerId);
if (pointerIndex == INVALID_POINTER_ID) {
break;
}
mLastPos.set(ev.getX(pointerIndex), ev.getY(pointerIndex));
float displacement = getDisplacement(ev);
float displacementX = mLastPos.x - mDownPos.x;
if (!mPaddedWindowMoveSlop) {
if (!mIsDeferredDownTarget) {
// Normal gesture, ensure we pass the drag slop before we start tracking
// the gesture
if (Math.abs(displacement) > mTouchSlop) {
mPaddedWindowMoveSlop = true;
mStartDisplacement = Math.min(displacement, -mTouchSlop);
}
}
}
if (!mPassedPilferInputSlop) {
float displacementY = mLastPos.y - mDownPos.y;
if (squaredHypot(displacementX, displacementY) >= mSquaredTouchSlop) {
if (mDisableHorizontalSwipe
&& Math.abs(displacementX) > Math.abs(displacementY)) {
// Horizontal gesture is not allowed in this region
forceCancelGesture(ev);
break;
}
mPassedPilferInputSlop = true;
if (mIsDeferredDownTarget) {
// Deferred gesture, start the animation and gesture tracking once
// we pass the actual touch slop
startTouchTrackingForWindowAnimation(ev.getEventTime());
}
if (!mPaddedWindowMoveSlop) {
mPaddedWindowMoveSlop = true;
mStartDisplacement = Math.min(displacement, -mTouchSlop);
}
notifyGestureStarted();
}
}
if (mInteractionHandler != null) {
if (mPaddedWindowMoveSlop) {
// Move
mInteractionHandler.updateDisplacement(displacement - mStartDisplacement);
}
if (mMode == Mode.NO_BUTTON) {
float horizontalDist = Math.abs(displacementX);
float upDist = -displacement;
boolean isLikelyToStartNewTask = horizontalDist > upDist;
mMotionPauseDetector.setDisallowPause(upDist < mMotionPauseMinDisplacement
|| isLikelyToStartNewTask);
mMotionPauseDetector.addPosition(displacement, ev.getEventTime());
mInteractionHandler.setIsLikelyToStartNewTask(isLikelyToStartNewTask);
}
}
break;
}
case ACTION_CANCEL:
case ACTION_UP: {
finishTouchTracking(ev);
break;
}
}
}