MTK-Android12-底部导航失效-问题详解
文章目录
- [前言 - 问题](#前言 - 问题)
- 一、参考资料
- 二、相关文件
- 三、实现方案
- [四、基于MTK-Android12 手势基本架构](#四、基于MTK-Android12 手势基本架构)
-
- 手势基本架构
- 手势相关配置
- [手势底部导航framework 层](#手势底部导航framework 层)
- 底部手势导航SystemUI模块
- [底部手势-Launcher3 Quickstep 层 TouchInteractionService.java(手势逻辑处理核心)](#底部手势-Launcher3 Quickstep 层 TouchInteractionService.java(手势逻辑处理核心))
-
- [模块 1:跨进程 Binder 接收 SystemUI 热区(TISBinder)](#模块 1:跨进程 Binder 接收 SystemUI 热区(TISBinder))
- [模块 2:双刷新逻辑](#模块 2:双刷新逻辑)
- [模块 3:输入监控 InputMonitorCompat](#模块 3:输入监控 InputMonitorCompat)
- [模块 4:触摸判定核心 onInputEvent ()](#模块 4:触摸判定核心 onInputEvent ())
- [模块 5:手势执行 Consumer 分发](#模块 5:手势执行 Consumer 分发)
- 四、思考反思-闭坑
- 总结
前言 - 问题
以前做过相关的手势底部导航失效,然后针对性的解决方案,可是实际发现这个手势导航失效居然偶现,经过几天的不断查阅资料和调试最终修复了底部手势导航失效的问题。
收获:
- 捋清楚手势导航思路-源码级别一步一步分析、手势框架
- 解决了实际问题
一、参考资料
资料参考-关联资料
之前自己有总结过手势相关的问题,都可以参考,对后续开发都有借鉴性的作用,如果合适拿来主义直接用。
MTK Android12 SystemUI 手势导航 隐藏导航栏底部布局
Android12-手势导航-三按键导航切换-三按键底部导航5秒消失功能实现
MTK - Android12 默认唯一Launcher作为HOME程序-手势导航左右滑动无效问题
Android12-手势导航失效-左右手势-上拉手势失效-解决方案-经验分享
其中,如下两个相关的资料,针对性手势失效的之前的解决方案。
MTK - Android12 默认唯一Launcher作为HOME程序-手势导航左右滑动无效问题
Android12-手势导航失效-左右手势-上拉手势失效-解决方案-经验分享
为什么又要单独来详解-分析-问题核心原因讨论
本身在如上针对性手势相关的资料中,已经针对性的解决了左右滑动手势和底部导航栏手势失效原因做了分析,后面才发现 只是在具体的场景下恰好那样解决只是解决了局限性问题,恰好在验证时候没有复现问题而已,属于偶发性问题 概率又高。 所以没有从本质上去解决实际问题。
所以总结内容:
- 闭坑指南、经验分享
- 底部导航框架
- 总结经验
二、相关文件
涉及到的文件
java
framework 层:
frameworks/base/services/core/java/com/android/server/wm/DisplayPolicy.java
frameworks/base/core/res/res/values/config.xml
SystemUI层:
vendor/mediatek/proprietary/packages/apps/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java
vendor/mediatek/proprietary/packages/apps/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
vendor/mediatek/proprietary/packages/apps/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
vendor/mediatek/proprietary/packages/apps/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
vendor/mediatek/proprietary/packages/apps/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl
Launcher3 层:
vendor/mediatek/proprietary/packages/apps/Launcher3/quickstep/src/com/android/quickstep/TouchInteractionService.java
vendor/mediatek/proprietary/packages/apps/Launcher3/quickstep/src/com/android/quickstep/OverviewComponentObserver.java
修改相关文件
java
/vendor/mediatek/proprietary/packages/apps/Launcher3/quickstep/src/com/android/quickstep/TouchInteractionService.java
/vendor/mediatek/proprietary/packages/apps/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
这里基于我的代码修改只需要修改这两个文件就可以了,实际上 frameworks/base/core/res/res/values/config.xml 这个文件修改也蛮重要,这里涉及到的是配置。
三、实现方案
手势失效问题本质说明
在最早的文章分析中:
MTK - Android12 默认唯一Launcher作为HOME程序-手势导航左右滑动无效问题
Android12-手势导航失效-左右手势-上拉手势失效-解决方案-经验分享
恰好解决了当时零时问题,实际上还是有相当概率性问题导致,问题的本质是:
- 为了适配MTK 芯片方案
USB摄像头正常成像,用了竖屏开机,开机后根据GSensor自动旋转正常的横屏显示。旋转过程中涉及到整个页面的翻转,底部手势导航的翻转,手势区域、底部导航区域多次重构 - 系统开机到正常显示整个过程,涉及到翻转、涉及到各种锁(
systemUnlocked && !mDeviceState.isUserUnlocked() && !mIsUnlockedTaskPending) 导致各种时序问题,造成概率性底部手势导航失效。 - 客户自己搞了两个Launcher同时存在,不修改。需要做成
Launcher程序又有如下配置:android:directBootAware="true"
如下又有:android:priority="1" 配置
java
<activity
android:name="com.komect.ajlauncher.SplashActivity"
android:exported="true"
android:screenOrientation="0"
android:configChanges="0x400027e4"
android:windowSoftInputMode="0x2">
<intent-filter
android:priority="1">
<action
android:name="android.intent.action.MAIN" />
<action
android:name="android.intent.action.VIEW" />
<category
android:name="android.intent.category.LAUNCHER" />
<category
android:name="android.intent.category.HOME" />
<category
android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
特别是 android:directBootAware="true" 配置,直接破换了整个开机流程,造成时序异常导致系统底部导航手势失效。
实现底部通栏全屏手势热区
修改文件:/vendor/mediatek/proprietary/packages/apps/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
java
/**
* Notifies the overview service of the active touch regions.
*/
public void notifyActiveTouchRegions() {
//mOverviewProxyService.onActiveNavBarRegionChanges(
// getButtonLocations(true /* includeFloatingButtons */, true /* inScreen */,
// true /* useNearestRegion */));
Rect fullNavBounds = new Rect();
this.getGlobalVisibleRect(fullNavBounds);
int gestureHeight =72;// getResources().getDimensionPixelSize(R.dimen.config_nav_bar_gesture_height);
fullNavBounds.top = fullNavBounds.bottom - gestureHeight;
Region fullGestureRegion = new Region(fullNavBounds);
Log.d("TouchInteractionService", "gesture left=" + fullNavBounds.left
+ " top=" + fullNavBounds.top
+ " right=" + fullNavBounds.right
+ " bottom=" + fullNavBounds.bottom);
mOverviewProxyService.onActiveNavBarRegionChanges(fullGestureRegion);
}
思路:就整一个全屏的底部手势热区导航,放置系统通过getButtonLocations坐标来进行判断不准导致底部手势导航失效
接收Region-刷新Region-各种锁机制-防规避机制实现
修改文件:vendor/mediatek/proprietary/packages/apps/Launcher3/quickstep/src/com/android/quickstep/TouchInteractionService.java
我这里直接给出核心修改处,MTK Android12 可以参考: 核心思想,适配热区、各种锁机制规避异常流程

方法:onActiveNavBarRegionChanges
java
@BinderThread
public void onActiveNavBarRegionChanges(Region region) {
- MAIN_EXECUTOR.execute(() -> mDeviceState.setDeferredGestureRegion(region));
+
+ Rect rect = new Rect();
+ region.getBounds(rect);
+ Log.d(TAG,"onActiveNavBarRegionChanges wfc NavBar region bounds: left="+rect.left+" top="+rect.top+" right="+rect.right+" bottom="+rect.bottom);
+
+ boolean isEmptyRegion = (rect.left == rect.right) || (rect.top == rect.bottom);
+ if(isEmptyRegion){
+ Log.d(TAG,"====onActiveNavBarRegionChanges=======wfc skip empty nav gesture region, discard");
+ mLastNavRegion.setEmpty();
+ return;
+ }
+
+ Log.d(TAG,"===onActiveNavBarRegionChanges=======wfc========");
+
+ Region temp = new Region(region);
+ if (temp.equals(mLastNavRegion)) {
+ Log.d(TAG, "nav region no change, skip refresh");
+ return;
+ }
+ mLastNavRegion.set(region);
+
+ UserManager userManager = (UserManager) getApplicationContext().getSystemService(Context.USER_SERVICE);
+ boolean systemUnlocked = userManager.isUserUnlocked();
+
+
+
+ if (!systemUnlocked) {
+ Log.d(TAG,"==wfc lock screen refresh gesture region==");
+ MAIN_EXECUTOR.execute(() -> refreshGestureRegion(region));
+ } else {
+ Log.d(TAG,"==wfc user unlocked refresh gesture region from sysui nav==");
+ if(!mDeviceState.isUserUnlocked()){
+ //MAIN_EXECUTOR.postDelayed(() -> refreshGestureRegion(region), 200);
+
+ mMainHandler.postDelayed(() -> {
+ refreshGestureRegion(region);
+ }, 200);
+
+ }else{
+ MAIN_EXECUTOR.execute(() -> refreshGestureRegion(region));
+ }
+ }
+
+
+
+
+ if (systemUnlocked && !mDeviceState.isUserUnlocked() && !mIsUnlockedTaskPending) {
+ mIsUnlockedTaskPending = true;
+ MAIN_EXECUTOR.execute(() -> {
+ try {
+ onUserUnlocked();
+ } catch (Exception e) {
+ Log.e(TAG,"=====wfc=====onUserUnlocked crash catch", e);
+ } finally {
+ mIsUnlockedTaskPending = false;
+ }
+ });
}
+
+ }
方法onCreate :
java
@Override
public void onCreate() {
super.onCreate();
+ Log.d(TAG,"=====onCreate===========wfc======");
+
+
+
+ mMainHandler = new Handler(Looper.getMainLooper());
+ mHasRunUserUnlockedOnce = false;
+ mDisplayManager = (DisplayManager) getSystemService(Context.DISPLAY_SERVICE);
+ mCachedDisplay = mDisplayManager.getDisplay(Display.DEFAULT_DISPLAY);
+
+
+ if(mCachedDisplay != null) {
+ mDisplayUiContext = createDisplayContext(mCachedDisplay);
+ }
// Initialize anything here that is needed in direct boot mode.
// Everything else should be initialized in onUserUnlocked() below.
mMainChoreographer = Choreographer.getInstance();
mAM = ActivityManagerWrapper.getInstance();
- mDeviceState = new RecentsAnimationDeviceState(this, true);
+ mDeviceState = new RecentsAnimationDeviceState(mDisplayUiContext, true);
mDisplayManager = getSystemService(DisplayManager.class);
mTaskbarManager = new TaskbarManager(this);
@@ -339,6 +439,7 @@ public class TouchInteractionService extends Service implements PluginListener<O
mDeviceState.runOnUserUnlocked(mTaskbarManager::onUserUnlocked);
ProtoTracer.INSTANCE.get(this).add(this);
sConnected = true;
+
}
新增方法:refreshGestureRegion
java
+ // modify by fang chen start
+ @UiThread
+ private void refreshGestureRegion(@Nullable Region sysUiNavRegion) {
+ if (mCachedDisplay == null || mRotationTouchHelper == null) {
+ return;
+ }
+ synchronized (mGestureRegionLock) {
+ if (mIsRefreshingGestureRegion) {
+ Log.d(TAG, "skip duplicate gesture region refresh");
+ return;
+ }
+ mIsRefreshingGestureRegion = true;
+ }
+ try {
+ Region targetGestureRegion = null;
+ if (sysUiNavRegion != null && !sysUiNavRegion.isEmpty()) {
+ targetGestureRegion = new Region(sysUiNavRegion);
+ } else {
+
+
+ if (mDeviceState.isUserUnlocked()) {
+ Log.w(TAG, "User unlocked, skip local fallback gesture region");
+ return;
+ }
+
+ Display display = mCachedDisplay;
+ DisplayMetrics dm = new DisplayMetrics();
+ display.getRealMetrics(dm);
+ int rotation = display.getRotation();
+ int physW = dm.widthPixels;
+ int physH = dm.heightPixels;
+ int gestureH = 72;
+ Rect targetGestureRect = new Rect();
+ switch (rotation) {
+ case Surface.ROTATION_0:
+ targetGestureRect.set(0, physH - gestureH, physW, physH);
+ break;
+ case Surface.ROTATION_90:
+ targetGestureRect.set(physW - gestureH, 0, physW, physH);
+ break;
+ case Surface.ROTATION_180:
+ targetGestureRect.set(0, 0, physW, gestureH);
+ break;
+ case Surface.ROTATION_270:
+ targetGestureRect.set(0, 0, gestureH, physH);
+ break;
+ default:
+ targetGestureRect.set(0, physH - gestureH, physW, physH);
+ break;
+ }
+ targetGestureRegion = new Region(targetGestureRect);
+ Log.w(TAG, "local fallback gesture region used, not from sysui");
+ }
+ mDeviceState.setDeferredGestureRegion(targetGestureRegion);
+ Rect logRect = new Rect();
+ targetGestureRegion.getBounds(logRect);
+ Log.d(TAG,"==wfc unified refresh gesture region rect="+logRect);
+ mRotationTouchHelper.updateGestureTouchRegions();
+ } finally {
+ synchronized (mGestureRegionLock) {
+ mIsRefreshingGestureRegion = false;
+ }
+ }
+ }
+
+
+
+
+
+
+
+
+ @UiThread
+ private void refreshGestureRegion() {
+ refreshGestureRegion(null);
+ }
+
+ // modify by fang chen end
+
+
方法:onUserUnlocked 修改
java
@UiThread
public void onUserUnlocked() {
- mTaskAnimationManager = new TaskAnimationManager(this);
- mOverviewComponentObserver = new OverviewComponentObserver(this, mDeviceState);
+ Log.d(TAG,"==onUserUnlocked======wfc=============");
+ mHasRunUserUnlockedOnce = true;
+ boolean needInitResource = (mTaskAnimationManager == null);
+ if(needInitResource){
+ mTaskAnimationManager = new TaskAnimationManager(mDisplayUiContext);
+ mOverviewComponentObserver = new OverviewComponentObserver(mDisplayUiContext, mDeviceState);
mOverviewCommandHelper = new OverviewCommandHelper(this,
mOverviewComponentObserver, mTaskAnimationManager);
mResetGestureInputConsumer = new ResetGestureInputConsumer(mTaskAnimationManager);
@@ -404,7 +614,21 @@ public class TouchInteractionService extends Service implements PluginListener<O
mOverviewComponentObserver.setOverviewChangeListener(this::onOverviewTargetChange);
onOverviewTargetChange(mOverviewComponentObserver.isHomeAndOverviewSame());
- }
+ }
+
+
+ // Log.d(TAG,"==wfc onUserUnlocked force update gesture touch regions==");
+
+ // Log.d(TAG,"==wfc onUserUnlocked immediate refresh gesture==");
+ // refreshGestureRegion();
+ // initInputMonitor();
+
+ // Log.d(TAG,"==wfc onUserUnlocked force update gesture touch regions==");
+
+
+
+
+ }
触摸:onInputEvent 修改
java
private void onInputEvent(InputEvent ev) {
+
+
if (!(ev instanceof MotionEvent)) {
Log.e(TAG, "Unknown event " + ev);
return;
}
MotionEvent event = (MotionEvent) ev;
+ Log.d(TAG,"=================================ENABLE_PER_WINDOW_INPUT_ROTATION:"+ENABLE_PER_WINDOW_INPUT_ROTATION);
if (ENABLE_PER_WINDOW_INPUT_ROTATION) {
final Display display = mDisplayManager.getDisplay(mDeviceState.getDisplayId());
int rotation = display.getRotation();
@@ -524,21 +753,83 @@ public class TouchInteractionService extends Service implements PluginListener<O
TestLogging.recordMotionEvent(
TestProtocol.SEQUENCE_TIS, "TouchInteractionService.onInputEvent", event);
- if (!mDeviceState.isUserUnlocked()) {
- return;
- }
+ // modify by fangchen start
+ UserManager userManager = (UserManager) getApplicationContext().getSystemService(Context.USER_SERVICE);
+ boolean systemUnlocked = userManager.isUserUnlocked();
+ if (!systemUnlocked) {
+ Log.d(TAG,"=onInputEvent==system locked return===wfc======");
+ return;
+ }
+
+ if (!mDeviceState.isUserUnlocked()&& !mIsUnlockedTaskPending) {
+ Log.d(TAG,"=onInputEvent sync unlock state trigger onUserUnlocked==");
+ mIsUnlockedTaskPending = true;
+
+ MAIN_EXECUTOR.execute(() -> {
+ try {
+ onUserUnlocked();
+ } catch (Exception e) {
+ Log.e(TAG,"onInputEvent trigger onUserUnlocked crash",e);
+ }finally {
+ mIsUnlockedTaskPending = false;
+ }
+ });
+ }
+ // modify by fangchen end
+
+
Object traceToken = TraceHelper.INSTANCE.beginFlagsOverride(
TraceHelper.FLAG_ALLOW_BINDER_TRACKING);
final int action = event.getAction();
if (action == ACTION_DOWN) {
- mRotationTouchHelper.setOrientationTransformIfNeeded(event);
-
- if (!mDeviceState.isOneHandedModeActive()
- && mRotationTouchHelper.isInSwipeUpTouchRegion(event)) {
- // Clone the previous gesture state since onConsumerAboutToBeSwitched might trigger
- // onConsumerInactive and wipe the previous gesture state
+ Log.d(TAG,"====wfc onInputEvent ACTION_DOWN=================");
+
+ mRotationTouchHelper.setOrientationTransformIfNeeded(event);
+
+ boolean isInGestureZone = false;
+ if (!mDeviceState.isOneHandedModeActive()) {
+ Log.d(TAG,"====wfc onInputEvent !mDeviceState.isOneHandedModeActive()================:"+!mDeviceState.isOneHandedModeActive());
+
+ Display display = mCachedDisplay;
+ if (display != null) {
+ DisplayMetrics dm = new DisplayMetrics();
+ display.getRealMetrics(dm);
+ int screenH = dm.heightPixels;
+ int gestureH = 72;
+ int bottomThreshold = screenH - gestureH;
+ float touchY = event.getY();
+
+ Log.d(TAG, "screenH=" + screenH + " gestureH=" + gestureH);
+ Log.d(TAG, "touchY=" + touchY + " bottomThreshold=" + bottomThreshold);
+ if (touchY >= bottomThreshold) {
+ isInGestureZone = true;
+ Log.d(TAG, "============qiangshi panduan dibu bottom shengxiao ================");
+ }else{
+ Log.d(TAG, "=========== now ===============isInGestureZone:"+isInGestureZone);
+
+ }
+ }
+ }
+
+ Log.d(TAG,"onInputEvent !mDeviceState.isOneHandedModeActive():"+!mDeviceState.isOneHandedModeActive());
+ Log.d(TAG,"onInputEvent mRotationTouchHelper.isInSwipeUpTouchRegion(event):"+mRotationTouchHelper.isInSwipeUpTouchRegion(event));
+ Log.d(TAG,"onInputEvent x:"+event.getX()+" y:"+event.getY());
+ Log.d(TAG,"onInputEvent isInGestureZone:"+isInGestureZone);
+
+
+
+
+
+
+
+
+ if (isInGestureZone) {
+ Log.d(TAG,"==wfc=====isInGestureZone========");
+
四、基于MTK-Android12 手势基本架构
手势基本架构



手势相关配置
位置:frameworks/base/core/res/res/values/config.xml
如下几个参数可参考:
java
<!-- If set, this will force all windows to draw the status bar background, including the apps
that have not requested doing so (via the WindowManager.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS
flag). -->
<bool name="config_forceWindowDrawsStatusBarBackground">true</bool>
<!-- Controls the opacity of the navigation bar depending on the visibility of the
various workspace stacks.
0 - Nav bar is always opaque when either the freeform stack or docked stack is visible.
1 - Nav bar is always translucent when the freeform stack is visible, otherwise always
opaque.
2 - Nav bar is never forced opaque.
-->
<integer name="config_navBarOpacityMode">0</integer>
<!-- Controls the navigation bar interaction mode:
0: 3 button mode (back, home, overview buttons)
1: 2 button mode (back, home buttons + swipe up for overview)
2: gestures only for back, home and overview -->
<integer name="config_navBarInteractionMode">2</integer>
<!-- Controls whether the nav bar can move from the bottom to the side in landscape.
Only applies if the device display is not square. -->
<bool name="config_navBarCanMove">false</bool>
<!-- Controls whether the navigation bar lets through taps. -->
<bool name="config_navBarTapThrough">false</bool>
<!-- Controls whether the side edge gestures can always trigger the transient nav bar to
show. -->
<bool name="config_navBarAlwaysShowOnSideEdgeGesture">true</bool>
<!-- Controls the size of the back gesture inset. -->
<dimen name="config_backGestureInset">10dp</dimen>
<!-- Array of values used in Gesture Navigation settings page to reduce/increase the back
gesture's inset size. These values will be multiplied into the default width, read from the
gesture navigation overlay package, in order to create 3 different sizes which are selectable
via a slider component. -->
<array name="config_backGestureInsetScales">
<item>0.60</item>
<item>1.00</item>
<item>1.33</item>
</array>
手势底部导航framework 层
WindowManager 窗口策略中枢,系统底层总控,如果分析手势底部导航失效,首先定位分析到的就是这个位置。
文件路径:frameworks/base/services/core/java/com/android/server/wm/DisplayPolicy.java
这里几个方法,手势底部触摸回调、热区判断:
java
@Override
public void onSwipeFromBottom() {
synchronized (mLock) {
if (mNavigationBar != null
&& mNavigationBarPosition == NAV_BAR_BOTTOM) {
requestTransientBars(mNavigationBar);
}
checkAltBarSwipeForTransientBars(ALT_BAR_BOTTOM);
}
// sendCreateNavigationbar();
//Log.d("huanghb","onSwipeFromBottom");
}
```java
private void requestTransientBars(WindowState swipeTarget) {
if (!mService.mPolicy.isUserSetupComplete()) {
// Swipe-up for navigation bar is disabled during setup
return;
}
final InsetsSourceProvider provider = swipeTarget.getControllableInsetProvider();
final InsetsControlTarget controlTarget = provider != null
? provider.getControlTarget() : null;
if (controlTarget == null || controlTarget == getNotificationShade()) {
// No transient mode on lockscreen (in notification shade window).
return;
}
final @InsetsType int restorePositionTypes =
(controlTarget.getRequestedVisibility(ITYPE_NAVIGATION_BAR)
? Type.navigationBars() : 0)
| (controlTarget.getRequestedVisibility(ITYPE_STATUS_BAR)
? Type.statusBars() : 0)
| (mExtraNavBarAlt != null && controlTarget.getRequestedVisibility(
ITYPE_EXTRA_NAVIGATION_BAR)
? Type.navigationBars() : 0)
| (mClimateBarAlt != null && controlTarget.getRequestedVisibility(
ITYPE_CLIMATE_BAR)
? Type.statusBars() : 0);
if (swipeTarget == mNavigationBar
&& (restorePositionTypes & Type.navigationBars()) != 0) {
// Don't show status bar when swiping on already visible navigation bar.
// But restore the position of navigation bar if it has been moved by the control
// target.
controlTarget.showInsets(Type.navigationBars(), false);
return;
}
if (controlTarget.canShowTransient()) {
// Show transient bars if they are hidden; restore position if they are visible.
mDisplayContent.getInsetsPolicy().showTransient(SHOW_TYPES_FOR_SWIPE);
controlTarget.showInsets(restorePositionTypes, false);
} else {
// Restore visibilities and positions of system bars.
controlTarget.showInsets(Type.statusBars() | Type.navigationBars(), false);
// To further allow the pull-down-from-the-top gesture to pull down the notification
// shade as a consistent motion, we reroute the touch events here from the currently
// touched window to the status bar after making it visible.
if (swipeTarget == mStatusBar) {
final boolean transferred = mStatusBar.transferTouch();
if (!transferred) {
Slog.i(TAG, "Could not transfer touch to the status bar");
}
}
}
mImmersiveModeConfirmation.confirmCurrentPrompt();
}
底部手势导航SystemUI模块
路径:vendor/mediatek/proprietary/packages/apps/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java 还有 StatusBar ,就是底部导航栏都涉及的。
定位:手势可视化载体 + 热区计算上报节点(你修改过这个文件)
完整职责
- 绘制底部导航栏视图,手势模式下只显示一条细条;
- 计算全局屏幕手势热区(核心修改点)
- 原生逻辑:遍历返回、主页按钮,收集分散触摸区域;
- 修改后逻辑:取
NavBar全局可视矩形,裁剪底部 矩形作为手势Region; - 跨进程
Binder上报热区给Launcher TIS - 通过
mOverviewProxyService.onActiveNavBarRegionChanges(Region)发送手势区域; - 对应
TIS里TISBinder.onActiveNavBarRegionChanges()接收回调; - 监听屏幕旋转、锁屏 / 解锁、分屏、输入法弹出,实时重新计算并刷新热区;
- 处理侧边返回手势左右侧 Region 上报(你当前只处理底部上滑)。
底部手势-Launcher3 Quickstep 层 TouchInteractionService.java(手势逻辑处理核心)
系统手势导航的大脑,所有底部 / 侧边触摸最终都会分流到此服务处理。
五大核心模块 & 流程
模块 1:跨进程 Binder 接收 SystemUI 热区(TISBinder)
java
onActiveNavBarRegionChanges(Region region)
- 接收
SystemUI传过来的底部手势矩形; - 做防抖:和上一次缓存
mLastNavRegion对比,无变化跳过刷新; - 区分锁屏 / 解锁状态,投递主线程执行
refreshGestureRegion(region); - 把标准
SysUI区域存入mDeviceState.setDeferredGestureRegion()。
模块 2:双刷新逻辑
- 两套刷新手势区域入口,存在竞态覆盖:
java
带参刷新(正确来源):refreshGestureRegion(region),使用 SystemUI 标准底部全屏 Region;
无参兜底刷新(错误来源):refreshGestureRegion(),不传 region 则本地读取屏幕尺寸计算;
- 锁屏:计算底部全屏矩形;
- 解锁 + 竖屏异常计算出右侧竖条
Rect (1848,0,1920,1080); - 时序冲突:解锁后
onUserUnlocked()、initInputMonitor()调用无参刷新,后执行覆盖正确底部区域 → 触摸判定 false。
模块 3:输入监控 InputMonitorCompat
initInputMonitor() 创建全局触摸监听器 mInputEventReceiver,接收 WMS 分流到底部的所有触摸事件;
所有屏幕底部 ACTION_DOWN/ACTION_MOVE/ACTION_UP 都会进入 onInputEvent()
模块 4:触摸判定核心 onInputEvent ()
完整判定链路:
- 过滤非触摸事件、锁屏状态直接 return;
- 开启
ENABLE_PER_WINDOW_INPUT_ROTATION对event做窗口旋转变换(坐标错乱根源); ACTION_DOWN时执行手势区域判定:
原生逻辑:mRotationTouchHelper.isInSwipeUpTouchRegion(event) 缺陷:用变换后的窗口逻辑坐标对比SysUI 上报的屏幕物理 Region,两套坐标系不匹配,永远返回 false; 你的旧兜底:依赖event.getRawX()/getRawY(),但 transform 会污染 raw 坐标,数值失真(日志 rawY=1079)isInGestureZone=true才会创建手势InputConsumer,处理上滑返回 / 回到桌面 / 多任务;
false则直接丢弃触摸,手势完全失效。
模块 5:手势执行 Consumer 分发
判定区域有效后,创建对应消费器处理手势:
OtherActivityInputConsumer:第三方APP内上滑;OverviewInputConsumer:桌面、多任务界面;AssistantInputConsumer/OneHandedModeInputConsumer:助手、单手模式拦截。
四、思考反思-闭坑
以下几个注意点、思考点可借鉴:
- 当找不出来原因时候分析代码、流程 整个链路走一遍必不可少的
- 不要一股脑子思考配置问题、手势中间层屏幕触摸窗口策略
DisplayPolicy上面找问题,最后发现怎么都无法解决问题 - 把整个手势链路理顺之后发现还是无法解决问题时候就想一想办法了,如上其实就是自己不去解决了 直接搞个手势热区区域出来
- 分析、调试问题遇到问题很正常不断去解决问题才能理解问题,发现真实失效原因。
总结
- 这里先要搞清楚问题本质原因:
App原因 导致系统只能适配来解决随机概率性手势失效问题 - 既然
App原因,那么最优解就是App修改,但是很多时候App客户就是不改,导致系统必须适配,针对性适配 - 如遇遇到类似问题,可以多分析实际情况,然后针对性来解决,这里的解决方案其实并不适合所有,但是有一个概念和架构性的东西是一样的。
- 这里仅仅针对MTK Android12 平台版本,其它版本和方案源码稍微区别,也可参考思路。