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

一、需求描述

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

二、需求分析

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

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

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

  1. TaskbarDelegate 初始化时添加需要屏蔽上滑功能的 Activity 合集
  2. 创建一个监听器 TaskStackChangeListener,用于监听前台应用是否需要屏蔽,并注册监听
  3. 任务栈变化时候改变 SysuiFlag,如 SYSUI_STATE_OVERVIEW_DISABLEDSYSUI_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 的源码中处理,在 TouchInteractionServiceonInputEvent 方法中直接返回即可。

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

public void onInputEvent(InputEvent ev){
	if (needInterceptHome()) {
		return;
	}
	...
}
相关推荐
阿巴斯甜13 小时前
Android 报错:Zip file '/Users/lyy/develop/repoAndroidLapp/l-app-android-ble/app/bu
android
Kapaseker13 小时前
实战 Compose 中的 IntrinsicSize
android·kotlin
xq952714 小时前
Andorid Google 登录接入文档
android
黄林晴16 小时前
告别 Modifier 地狱,Compose 样式系统要变天了
android·android jetpack
冬奇Lab1 天前
Android触摸事件分发、手势识别与输入优化实战
android·源码阅读
城东米粉儿1 天前
Android MediaPlayer 笔记
android
Jony_1 天前
Android 启动优化方案
android
阿巴斯甜1 天前
Android studio 报错:Cause: error=86, Bad CPU type in executable
android
张小潇1 天前
AOSP15 Input专题InputReader源码分析
android
_小马快跑_1 天前
Kotlin | 协程调度器选择:何时用CoroutineScope配置,何时用launch指定?
android