【TaskbarDelegate】屏蔽上滑返回桌面手势功能

一、需求描述

基于Android 14 平台,在应用界面中默认使用上滑手势会返回桌面,某些应用要求不能返回到桌面,需要屏蔽上滑手势。

二、需求分析

我们知道手势导航的底部导航横线的逻辑是在 SystemUI 的 NavigationBarView -> NavigationBar -> NavigationBarController -> TaskbarDelegate 等相关的类控制,在手势导航中上滑手势有 2 种功能:

  • 从屏幕底部向上滑动,即可进入主屏幕
  • 从底部向上滑动、按住再松开,可切换应用

如果想要屏蔽,可以直接返回消费掉 InputEvent 事件,就不会走后续的流程了,我尝试在NavigationBarView 的 onInterceptTouchEvent 返回 true,但是只屏蔽了返回Home的功能,最近任务还是会启动。在 TaskbarDelegate 中有设置相关的 sys ui flag 的相关逻辑,于是我们可以参考 【EdgeBackGesture】屏蔽屏幕边缘返回手势,实现类似的屏蔽效果。

  1. TaskbarDelegate 初始化时添加需要屏蔽上滑功能的 Activity 合集
  2. 创建一个监听器 TaskStackChangeListener,用于监听前台应用是否需要屏蔽,并注册监听
  3. 任务栈变化时候改变 SysuiFlag,如 SYSUI_STATE_OVERVIEW_DISABLED 和 SYSUI_STATE_HOME_DISABLED

这个方案的优点是,我们不需要去追踪复杂的输入事件流,而是直接通过官方提供的、系统级的状态标志来声明我们的意图。InputDispatcher(负责分发所有触摸事件的底层服务)会尊重这些标志,并从源头上就阻止手势的发生。

三、解决方案

SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java

1.添加需要屏蔽上滑功能的 Activity 合集

java 复制代码
private final List<ComponentName> mHomeBlockingActivities = new ArrayList<>();

public TaskbarDelegate(Context context,
        LightBarTransitionsController.Factory lightBarTransitionsControllerFactory,
        StatusBarKeyguardViewManager statusBarKeyguardViewManager) {
    mLightBarTransitionsControllerFactory = lightBarTransitionsControllerFactory;
    mContext = context;
    mDisplayManager = mContext.getSystemService(DisplayManager.class);
    mPipListener = (bounds) -> {
        mEdgeBackGestureHandler.setPipStashExclusionBounds(bounds);
    };
    mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
    mStatusBarKeyguardViewManager.setTaskbarDelegate(this);
    // 添加要屏蔽的 Activity
    String[] disabledActivities = mContext.getResources().getStringArray(
            R.array.config_blockedHomeGestureActivities);
    for (String disabledActivity : disabledActivities) {
        mHomeBlockingActivities.add(ComponentName.unflattenFromString(disabledActivity));
    }
}

在新建的 config_blockedHomeGestureActivities 数组中添加需要屏蔽类名即可

xml 复制代码
<string-array name="config_blockedHomeGestureActivities">
    <item>com.android.deskclock/.DeskClock</item>
</string-array>

2.注册并监听任务栈变化

TaskbarDelegate 原本逻辑中就包含了 TaskStackChangeListener 的监听,但是没有 onTaskStackChanged 的重写,只需要加上即可

java 复制代码
private boolean mIsHomeGestureBlocked;

private final TaskStackChangeListener mTaskStackListener = new TaskStackChangeListener() {
    @Override
    public void onLockTaskModeChanged(int mode) {
        mSysUiState.setFlag(SYSUI_STATE_SCREEN_PINNING, mode == LOCK_TASK_MODE_PINNED)
                .commitUpdate(mDisplayId);
    }
    @Override
    public void onTaskStackChanged() {
        updateHomeGestureBlockState();
    }
};

private void updateHomeGestureBlockState() {
    boolean shouldBlock = false;
    ActivityManager.RunningTaskInfo runningTask =
            ActivityManagerWrapper.getInstance().getRunningTask();
    ComponentName topActivity = runningTask == null ? null : runningTask.topActivity;
    shouldBlock = topActivity != null && mHomeBlockingActivities.contains(topActivity);
    if (mIsHomeGestureBlocked != shouldBlock) {
        mIsHomeGestureBlocked = shouldBlock;
        updateSysuiFlags();
    }
}

3.更新标志位 updateSysuiFlags

java 复制代码
private void updateSysuiFlags() {
    android.util.Log.e("maxx","updateSysuiFlags");
    int a11yFlags = mNavBarHelper.getA11yButtonState();
    boolean clickable = (a11yFlags & SYSUI_STATE_A11Y_BUTTON_CLICKABLE) != 0;
    boolean longClickable = (a11yFlags & SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE) != 0;
    //新增修改:
    boolean overviewDisabled = (mDisabledFlags & View.STATUS_BAR_DISABLE_RECENT) != 0
            || mIsHomeGestureBlocked;
    boolean homeDisabled = (mDisabledFlags & View.STATUS_BAR_DISABLE_HOME) != 0
            || mIsHomeGestureBlocked;
    //*/
    mSysUiState.setFlag(SYSUI_STATE_A11Y_BUTTON_CLICKABLE, clickable)
            .setFlag(SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE, longClickable)
            .setFlag(SYSUI_STATE_IME_SHOWING,
                    (mNavigationIconHints & NAVIGATION_HINT_BACK_ALT) != 0)
            .setFlag(SYSUI_STATE_IME_SWITCHER_SHOWING,
                    (mNavigationIconHints & NAVIGATION_HINT_IME_SWITCHER_SHOWN) != 0)
             //新增修改:
            .setFlag(SYSUI_STATE_OVERVIEW_DISABLED, overviewDisabled
                    /*(mDisabledFlags & View.STATUS_BAR_DISABLE_RECENT) != 0*/)
            .setFlag(SYSUI_STATE_HOME_DISABLED, homeDisabled
                    /*(mDisabledFlags & View.STATUS_BAR_DISABLE_HOME) != 0*/)
             //*/
            .setFlag(SYSUI_STATE_BACK_DISABLED,
                    (mDisabledFlags & View.STATUS_BAR_DISABLE_BACK) != 0)
            .setFlag(SYSUI_STATE_NAV_BAR_HIDDEN, !isWindowVisible())
            .setFlag(SYSUI_STATE_ALLOW_GESTURE_IGNORING_BAR_VISIBILITY,
                    allowSystemGestureIgnoringBarVisibility())
            .setFlag(SYSUI_STATE_IMMERSIVE_MODE, isImmersiveMode())
            .commitUpdate(mDisplayId);
}

还有一种思路是在 Launcher 做屏蔽处理,例如在 Launcher3 的源码中处理,在 TouchInteractionService 的 onInputEvent 方法中直接返回即可。

java 复制代码
Launcher3/quickstep/src/com/android/quickstep/TouchInteractionService.java

public void onInputEvent(InputEvent ev){
	if (needInterceptHome()) {
		return;
	}
	...
}
相关推荐
方白羽2 小时前
Android 双开环境底层剖析与风控检测落地
android·安全·app
weixin_563371473 小时前
Android Studio 2024 版 Device Explorer 打开方法
android·ide·android studio
AIsoft_86895 小时前
职场音频文件怎么整理?录音APP有哪些用法
android·音视频
booboosui5 小时前
第一批个人 Agent 用户正在卸载,问题不在模型,在它住在哪儿
android
洞见前行6 小时前
点击图标到内容出现,你的 App 在这几秒里做了什么:Android 冷启动全链路优化实战
android·性能优化·android studio
凯基迪科技6 小时前
芯片UID绑定修改 加密与逆向修改思路分析
android
Liben7 小时前
Weex开发踩坑记录
android·前端·ios
救救孩子把9 小时前
结构化输出为何“格式正确、答案错误”?JSON Schema 的能力边界
android·java·json·agent·agent服务
张小姐的猫9 小时前
【MySQL】事务
android·mysql·adb
你看我名字10 小时前
数眼智能 Search + Reader 实战:联网问答怎样避免“搜到了,却引用错了”
android·java·开发语言