【SystemGestures】屏蔽鼠标悬浮唤出状态栏和手势导航
【SystemGestures】屏蔽鼠标悬浮唤出状态栏和手势导航
- 一、需求描述
- 二、解决方案
- 三、关于 SystemGesturesPointerEventListener
- 3.1 详细功能点代码解析
- 1. 屏幕边缘滑动检测 (Edge Swipe Detection)
- 2. 鼠标屏幕边缘悬浮事件 (Mouse Hover at Edges)
- 3. 快速滑动/抛掷手势 (Fling Gesture)
- 4. 按下与抬起/取消事件的回调 (Down, Up/Cancel)
- 5. 多指调试手势 (Multi-finger Debug Gesture)
- 6. 触摸点跟踪与管理
- 3.2 Android 触摸事件的逐步处理过程
- 步骤 1:硬件层和原生层
- 步骤 2:关键拦截点(窗口管理器服务)
- 步骤 3:决策——系统手势还是应用程序手势?
一、需求描述
云电脑应用界面,使用鼠标时放在屏幕边缘时会唤出状态栏和手势导航,影响正常的云电脑内部功能的使用,需要屏蔽鼠标
二、解决方案
SystemGesturesPointerEventListener.java 是 Android 系统中一个核心的输入事件监听器,它在窗口管理器(WindowManager)层面全局性地处理指针事件,主要负责识别并响应那些由系统定义、具有特殊意义的手势操作。
frameworks/base/services/core/java/com/android/server/wm/SystemGesturesPointerEventListener.java
鼠标边缘悬浮的检测逻辑如下,SystemGesturesPointerEventListener 监听 ACTION_HOVER_MOVE
/*** Listens for system-wide input gestures, firing callbacks when detected.* @hide*/
class SystemGesturesPointerEventListener implements PointerEventListener {...@Overridepublic void onPointerEvent(MotionEvent event) {if (mGestureDetector != null && event.isTouchEvent()) {mGestureDetector.onTouchEvent(event);}switch (event.getActionMasked()) {...case MotionEvent.ACTION_HOVER_MOVE:if (event.isFromSource(InputDevice.SOURCE_MOUSE)) {final float eventX = event.getX();final float eventY = event.getY();if (!mMouseHoveringAtLeft && eventX == 0) {mCallbacks.onMouseHoverAtLeft();mMouseHoveringAtLeft = true;} else if (mMouseHoveringAtLeft && eventX > 0) {mCallbacks.onMouseLeaveFromLeft();mMouseHoveringAtLeft = false;}if (!mMouseHoveringAtTop && eventY == 0) {mCallbacks.onMouseHoverAtTop();mMouseHoveringAtTop = true;} else if (mMouseHoveringAtTop && eventY > 0) {mCallbacks.onMouseLeaveFromTop();mMouseHoveringAtTop = false;}if (!mMouseHoveringAtRight && eventX >= screenWidth - 1) {mCallbacks.onMouseHoverAtRight();mMouseHoveringAtRight = true;} else if (mMouseHoveringAtRight && eventX < screenWidth - 1) {mCallbacks.onMouseLeaveFromRight();mMouseHoveringAtRight = false;}if (!mMouseHoveringAtBottom && eventY >= screenHeight - 1) {mCallbacks.onMouseHoverAtBottom();mMouseHoveringAtBottom = true;} else if (mMouseHoveringAtBottom && eventY < screenHeight - 1) {mCallbacks.onMouseLeaveFromBottom();mMouseHoveringAtBottom = false;}}break;...}}...
}
通过检测云电脑包名,在特定的包名应用中则 跳过 鼠标边缘悬浮的检测逻辑,即可防止系统级的边缘悬浮功能与云电脑应用内部的边缘操作(如调出远程桌面的菜单)产生冲突。
private boolean isIgnoringHoverMoveEvent() {// 检查当前位于前台的应用包名ActivityManager am = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE);try {ComponentName cn = am.getRunningTasks(1).get(0).topActivity;String packageName = cn.getPackageName();if (TextUtils.equals(packageName, "cm.xxx.cloudcomputerpad")) { // 云电脑的包名return true;}} catch (Exception e) {return false;}return false;
}
在 SystemGesturesPointerEventListener 中添加这个判断即可
case MotionEvent.ACTION_HOVER_MOVE:if (event.isFromSource(InputDevice.SOURCE_MOUSE)) {// 添加这个判断即可跳过if(!isIgnoringHoverMoveEvent()) {}}
三、关于 SystemGesturesPointerEventListener
SystemGesturesPointerEventListener.java 类实现了 PointerEventListener 接口,意味着它的核心是 onPointerEvent(MotionEvent event) 方法。所有来自窗口管理器(WindowManager)的全局指针事件(触摸、鼠标等)都会被送到这里进行处理。
它内部定义了一个 Callbacks 接口,这是其与系统其他部分(通常是 DisplayPolicy)通信的桥梁。当它识别出某个手势时,就会调用这个接口中的相应方法。
3.1 详细功能点代码解析
SystemGesturesPointerEventListener 是一个功能强大且职责明确的系统级手势处理器:
- 核心功能是屏幕边缘滑动检测,并为刘海屏做了适配。
- 包含对鼠标边缘悬浮的特定处理,并展示了如何通过包名判断来添加应用排除列表,这是 OEM 定制中常见的做法。
- 利用
GestureDetector来识别更复杂的手势,如 Fling,将原始的触摸事件流转化为高级的手势意图。 - 提供基础的事件生命周期回调 (
onDown,onUpOrCancel),让系统可以同步交互状态。 - 内置了一个隐藏的五指调试手势,体现了其作为系统底层服务的特性。
它完美地诠释了“在系统层面监听原始输入,并将其翻译为具有特定系统意义的动作”这一设计哲学。
1. 屏幕边缘滑动检测 (Edge Swipe Detection)
这是该类的主要职责,通过 detectSwipe 方法实现。
-
触发时机: 在
onPointerEvent的case MotionEvent.ACTION_MOVE:分支中,如果mSwipeFireable标志位为true,就会调用detectSwipe(event)。mSwipeFireable在ACTION_DOWN时被设为true,在手势成功触发或事件结束后(ACTION_UP/CANCEL)被设为false,确保一次触摸序列只触发一次滑动。 -
核心逻辑 (
detectSwipe方法):- 起始位置判断:
fromY <= mSwipeStartThreshold.top(顶部),fromY >= screenHeight - mSwipeStartThreshold.bottom(底部),fromX >= screenWidth - mSwipeStartThreshold.right(右侧),fromX <= mSwipeStartThreshold.left(左侧)。这几行代码判断手指按下的初始位置 (fromX,fromY) 是否在屏幕边缘的预设阈值 (mSwipeStartThreshold) 之内。这个阈值会在onConfigurationChanged方法中根据屏幕尺寸和刘海(DisplayCutout)区域进行动态调整,以确保在有刘海的设备上也能正常触发。 - 滑动距离判断:
y > fromY + mSwipeDistanceThreshold(从顶部下滑),y < fromY - mSwipeDistanceThreshold(从底部上滑) 等。这判断手指当前位置 (x,y) 相对于初始位置的移动距离是否超过了mSwipeDistanceThreshold。 - 超时判断:
elapsed < SWIPE_TIMEOUT_MS。这确保了滑动必须在指定的时间(默认500毫秒)内完成,防止缓慢的拖动被误判为系统手势。
- 起始位置判断:
-
回调触发: 一旦上述三个条件同时满足,
detectSwipe方法会返回相应的SWIPE_FROM_*常量,onPointerEvent中的switch语句会捕获这个返回值,并调用mCallbacks接口中对应的方法,如mCallbacks.onSwipeFromTop()。
2. 鼠标屏幕边缘悬浮事件 (Mouse Hover at Edges)
与我之前的回答不同,这段特定的代码版本确实处理了鼠标的悬浮事件,这是一个非常重要的发现。
- 触发时机: 在
onPointerEvent的case MotionEvent.ACTION_HOVER_MOVE:分支中。 - 设备来源判断:
event.isFromSource(InputDevice.SOURCE_MOUSE)确保了只有当事件来自鼠标时才处理。 - 核心逻辑:
eventX == 0(左边缘),eventY == 0(上边缘),eventX >= screenWidth - 1(右边缘),eventY >= screenHeight - 1(下边缘)。代码通过判断鼠标指针的坐标 (eventX,eventY) 是否精确地位于屏幕的四个边缘,来确定鼠标是否悬浮在边缘上。- 状态管理: 使用
mMouseHoveringAtLeft,mMouseHoveringAtTop等布尔变量来跟踪当前的悬浮状态,防止重复触发回调。当鼠标进入边缘区域时,调用onMouseHoverAt*();当鼠标离开时,调用onMouseLeaveFrom*()。
3. 快速滑动/抛掷手势 (Fling Gesture)
- 实现方式: 通过一个内部类
FlingGestureDetector,它继承自GestureDetector.SimpleOnGestureListener。SystemGesturesPointerEventListener的主实例持有一个GestureDetector对象 (mGestureDetector)。 - 事件传递: 在
onPointerEvent的开头,所有触摸事件 (event.isTouchEvent()) 都会被传递给mGestureDetector.onTouchEvent(event)。 - 核心逻辑 (
FlingGestureDetector.onFling方法):- 当
GestureDetector从事件流中识别出 Fling 手势时,会回调onFling方法。 - 该方法使用
OverScroller来计算抛掷动画的持续时间 (duration)。 mLastFlingTime用于防止过于频繁的 Fling 操作(两次 Fling 间隔必须大于MAX_FLING_TIME_MILLIS)。- 最后,调用
mCallbacks.onFling(duration),将计算出的动画时长传递出去,系统的其他部分可以利用这个时长来执行一个与手势速度匹配的动画。
- 当
4. 按下与抬起/取消事件的回调 (Down, Up/Cancel)
onDown(): 在ACTION_DOWN事件中被调用,通知系统一次新的触摸交互序列开始了。onUpOrCancel(): 在ACTION_UP或ACTION_CANCEL事件中被调用,通知系统交互结束。这对于重置状态非常重要。
5. 多指调试手势 (Multi-finger Debug Gesture)
这是一个隐藏的开发者功能。
- 触发时机: 在
ACTION_POINTER_DOWN事件(即第二个或更多手指按下)时进行判断。 - 核心逻辑:
mDebugFireable = event.getPointerCount() < 5;。当按下的手指数量达到5个时 (!mDebugFireable变为true),会立即调用mCallbacks.onDebug()。这通常用于触发一些调试用的覆盖层或系统信息显示。
6. 触摸点跟踪与管理
- 数据结构: 使用
mDownPointerId,mDownX,mDownY,mDownTime等数组来存储最多32个触摸点的初始按下信息(ID, 坐标, 时间)。 captureDown方法: 在ACTION_DOWN和ACTION_POINTER_DOWN时被调用,负责记录新按下的手指信息。findIndex方法: 一个辅助方法,用于根据pointerId快速找到该手指在跟踪数组中的索引。
3.2 Android 触摸事件的逐步处理过程
这触及了Android输入分发机制的核心。系统需要一种方法来处理自身的手势(例如下拉状态栏),同时又不干扰应用程序的手势(例如滑动浏览照片),反之亦然。关键概念是拦截、优先和消费。
以下是触摸事件的逐步处理过程,解释了系统如何决定是处理该事件还是将其传递给应用程序。想象一下,你从屏幕顶部向下滑动。
步骤 1:硬件层和原生层
- Hardware -> Kernel:你的手指触摸屏幕,触摸屏控制器检测手指的位置和压力,并将这些原始数据作为输入事件发送到 Linux 内核。
- Kernel -> InputReader:安卓系统的
InputReader该进程持续监控内核的输入设备,它读取原始数据并将其转换为结构化的 Android 数据MotionEvent。 - InputReader -> InputDispatcher:
InputDispatcher是所有输入信息的中央协调机构,它的主要任务是确定应该向哪个窗口接收这些MotionEvent信息,但在将事件发送到应用程序窗口之前,它会先咨询窗口管理器。
步骤 2:关键拦截点(窗口管理器服务)
这里系统手势指针事件监听器发挥作用。
- InputDispatcher -> WindowManagerService (WMS):
InputDispatcher发送动作事件窗口管理服务 (WMS) 负责与窗口相关的一切:窗口的位置、大小以及当前聚焦的窗口。 - WMS 会查询PointerEventListeners。 WMS维护着一份特殊的、高优先级的监听者名单,这些监听者可以“偷看”所有内容,动作事件在它进入应用程序之前,
SystemGesturesPointerEventListener是这些听众之一,已注册显示策略(一个管理系统 UI 策略的类)。
步骤 3:决策——系统手势还是应用程序手势?
这动作事件现已送达SystemGesturesPointerEventListener.onPointerEvent()您提供的代码开始执行。
场景 A:系统识别并“接收”手势
- 检测: 这检测滑动方法运行。它发现触摸操作始于fromY <= mSwipeStartThreshold.top并向下移动了足够远的距离(y > fromY + mSwipeDistanceThreshold它返回
SWIPE_FROM_TOP。 - 行动: 这onPointerEvent方法调用回调
mCallbacks.onSwipeFromTop()。 - 执行:显示策略实现此回调的函数接收到信号后,会通知系统 UI 展开通知面板。
- 消耗:这是最关键的部分。因为
SystemGesturesPointerEventListener它成功识别出系统手势,实际上就告诉输入系统:“我已经处理了这个事件。其他人不需要看到它。”InputDispatcher接收到此信号并终止事件的旅程。
结果: 这动作事件触摸操作永远不会传递给前台应用程序。屏幕上运行的游戏、浏览器或启动器完全不知道发生了触摸事件。
场景 B:系统无法识别手势
现在,想象一下你在屏幕中间滑动,远离任何边缘。
- 检测: 这
MotionEvent仍然发送到SystemGesturesPointerEventListener.onPointerEvent()。 这检测滑动方法运行,但其所有功能都无法运行。如果条件失败,因为触摸并非从边缘附近开始。该方法返回SWIPE_NONE。 - 无行动:未触发任何系统级回调。
- 途经监听器向输入系统发出信号:“我查看了这个事件,但它与我无关。你可以把它转交给其他人。”
- 发送至应用程序: 这
InputDispatcher由于事件未被处理,程序继续执行其正常任务。它会识别触摸坐标处最顶层的可见应用程序窗口。 - 交付至应用程序: 这
MotionEvent最终被发送到应用程序进程。它通常通过以下路径进入应用程序的视图层级结构:- Activity.dispatchTouchEvent():该活动优先获得处理机会。
- ViewGroup.onInterceptTouchEvent():它沿着布局树向下移动(例如,从一个布局树开始)。LinearLayout一个FrameLayout父布局可以“拦截”事件,以便在子布局(如按钮)看到滚动等手势之前对其进行处理。
- View.onTouchEvent():最后,它到达了目标视图(例如,按钮、ImageView)。这时,应用程序自身的逻辑(例如)就会执行。点击监听器或自定义手势检测)运行。
