Android10 SystemUI系列 需求定制(二)隐藏状态栏通知图标,锁屏通知,可定制包名,渠道等

一、前言

SystemUI 所包含的界面和模块比较多,这一节主要分享一下状态栏通知图标和通知栏的定制需求:隐藏状态栏通知图标,锁屏通知,可定制包名,渠道等
来熟悉一下Systemui。

二、准备工作

按照惯例先找到核心类。这里提前说一下,这个需求的修改方法更多,笔者这里也只是提供一个思路。不过由于笔者最看是是做SystemUI 的自认为修改需求和解决问题要找到本质。
下面说一下设计到的核心类frameworks\base\services\core\java\com\android\server\notification\NotificationManagerService.java
之所以选择这个类,是因为需求中所涉及到通知相关的都在这个通知管理服务中处理。比如系统收到了通知,需要显示。NotificationManagerService 会经过一系列处理之后在发送给Systemui 用作显示,所以在NotificationManagerService 这里处理,是从源头上做了处理

三、具体修改

frameworks\base\services\core\java\com\android\server\notification\NotificationManagerService.java

 void enqueueNotificationInter
<think> 我们被要求实现:在Android 15的SystemUI中,对指定应用隐藏状态栏且不可滑动显示。 根据之前的引用,我们知道SystemUI的相关定制通常涉及修改核心类,如ExpandableNotificationRow、NotificationMenuRow等。 但是,这里的需求是“对指定应用隐藏状态栏且禁止滑动显示”,这涉及到状态栏的显示和手势控制。 分析需求: 1. 隐藏状态栏:在指定应用界面,状态栏不可见。 2. 禁止滑动显示:即使在隐藏状态下,用户也无法通过滑动呼出状态栏。 在Android中,状态栏隐藏通常可以通过全模式实现,比如使用`View.SYSTEM_UI_FLAG_FULLSCREEN`。但是,这个需求是针对SystemUI的,意味着我们需要在系统层面为指定应用实现这个功能,并且要禁止滑动显示。 在SystemUI中,状态栏的显示和隐藏由StatusBar类(或相关类)控制,而手势检测则是由NotificationPanelViewController(或类似类)处理。 步骤: 1. 我们需要在系统层面为每个应用记录一个设置:是否隐藏状态栏并禁止滑动。 2. 在应用切换时(或应用启动时),检查当前应用是否需要隐藏状态栏并禁止滑动。 3. 如果需要,则隐藏状态栏,并禁用下拉手势。 具体实现思路: a. 添加一个设置:可以配置哪些应用需要隐藏状态栏(例如,通过一个列表)。 b. 监听当前前台应用,当切换到指定应用时,隐藏状态栏并禁用下拉手势;当切换到其他应用时,恢复状态栏和手势。 核心类: - StatusBar: 控制状态栏的显示和隐藏。 - NotificationPanelViewController: 控制通知面板(含手势处理)。 - 可能需要修改CentralSurfacesImpl(在Android 12之后,StatusBar的功能被转移到CentralSurfacesImpl)? 实现步骤: 1. 在SystemUI中创建一个管理类,用于维护需要隐藏状态栏的应用列表。 2. 在StatusBar(或CentralSurfacesImpl)中监听前台应用的变化(通过ActivityManager或StatusBarStateController)。 3. 当应用切换时,检查当前应用是否在列表中。如果是,则执行: - 隐藏状态栏:调用`setBarState`或直接设置状态栏隐藏。 - 禁用下拉手势:在NotificationPanelViewController中,有一个方法`setPanelEnabled(boolean)`,我们可以将其设置为false。 4. 当离开该应用时,恢复状态栏的显示和手势。 注意:在Android 15上,类和具体方法可能会有变化,但思路类似。 具体代码修改(示例): 步骤1:创建管理类(例如,StatusBarAppConfig) 我们可以创建一个单例类,用于存储需要隐藏状态栏的应用列表。 步骤2:在StatusBar(或CentralSurfacesImpl)中监听前台应用 我们可以使用`ActivityManager`来监听前台应用,但更常见的是使用`StatusBarStateController`或`KeyguardStateController`来获取当前状态,或者使用`ActivityManager`来获取当前运行的应用。 在StatusBar(或CentralSurfaces)中,我们可以注册一个回调来监听前台应用的变化: 例如,在Android 13中,StatusBar中有一个`mActivityManager`,我们可以使用`ActivityTaskManager`来监听当前应用。 在Android 15中,我们可以使用`ActivityManager`的`getRunningAppProcesses()`或通过`ActivityTaskManager`的`getAppTasks()`来获取,但更推荐使用`ActivityManager.RunningTaskInfo`(注意:在Android 11及以上,使用`ActivityTaskManager`)。 或者,我们可以利用已有的`ConfigurationController`来监听配置变化,因为应用切换会导致配置变化(但并非所有情况)。 另一种方法是使用`StatusBarStateController`的状态(如、桌面、应用等)结合当前应用。 步骤3:修改状态栏的显示和手势 - 隐藏状态栏:在StatusBar中,我们可以调用`setBarState(int state)`,其中状态可能是`StatusBarState.SHADE`(展开)或`StatusBarState.KEYGUARD`()等。但是隐藏状态栏通常是通过全标志实现的,系统UI可见性控制。 SystemUI提供了`StatusBarWindowController`,我们可以通过它来设置系统UI的可见性。 然而,在SystemUI内部,更直接的方法是设置状态栏的高度为0,或者设置View为GONE。但这样可能会影响其他组件。 另一种方法是,在状态栏的根布局上设置高度为0,并禁用触摸事件。 但是,更合理的方式是使用系统提供的全模式。我们可以通过WindowManager.LayoutParams.FLAG_FULLSCREEN来实现,但这是在应用端设置的。我们需要在系统端强制设置。 在SystemUI中,我们可以通过修改状态栏的WindowManager.LayoutParams来设置高度为0,并设置FLAG_NOT_TOUCHABLE。 但是,这可能会影响整个状态栏区域,括其他应用。 因此,我们需要针对当前应用进行动态调整。 另一种思路:在状态栏的视图层,我们可以根据当前应用动态设置高度和触摸事件。 在状态栏的布局中,通常有一个根视图(如PhoneStatusBarView)。我们可以在其中根据当前应用设置可见性和触摸事件。 具体实现: 1. 在StatusBar(或CentralSurfacesImpl)中,添加一个方法: private void updateStatusBarForApp(String packageName) { boolean shouldHide = mAppConfig.shouldHideStatusBar(packageName); if (shouldHide) { // 隐藏状态栏:设置状态栏高度为0,并禁止触摸 mStatusBarWindowController.setBarHeight(0); mStatusBarWindowController.setTouchable(false); // 同时,可能需要隐藏状态栏图标等 mStatusBarView.setVisibility(View.GONE); } else { // 恢复 mStatusBarWindowController.setBarHeight(originalHeight); mStatusBarWindowController.setTouchable(true); mStatusBarView.setVisibility(View.VISIBLE); } } 但是,请注意,状态栏窗口的高度和触摸控制可能涉及到多个方面。 2. 在SystemUI中,状态栏窗口的高度由`StatusBarWindowController`控制,我们可以通过它来设置高度。另外,我们还可以通过`StatusBarWindowController`设置`disableTouch`。 然而,查看`StatusBarWindowController`的源码,它并没有直接提供设置高度为0的方法,但我们可以通过修改布局参数来实现。 实际上,在`StatusBarWindowController`中,我们可以通过`setBarHeight(int height)`来设置状态栏的高度,但是这个方法可能只在初始化时调用。我们可以修改布局参数: private void updateWindowForApp(boolean shouldHide) { WindowManager.LayoutParams lp = mWindow.getWindowLayoutParams(); if (shouldHide) { lp.height = 0; lp.flags |= WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE; } else { // 恢复原来的高度和标志 lp.height = mBarHeight; lp.flags &= ~WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE; } mWindow.setWindowLayoutParams(lp); } 但是,这种方法需要保存原来的高度。 3. 另外,我们还需要在状态栏的视图层设置GONE,因为即使高度为0,视图可能还在绘制。所以,同时设置状态栏根视图为GONE。 步骤4:在适当的地方调用更新方法,比如当应用切换时。 在StatusBar中,我们可以通过`ConfigurationController`监听应用变化: mConfigurationController.addCallback(new ConfigurationListener() { @Override public void onConfigChanged(Configuration newConfig) { // 应用切换可能会引起配置变化,但不是所有变化都是应用切换 } }); 更准确的方法是使用`ActivityManager`监听当前运行的应用: ActivityManager am = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE); am.addOnAppChangedListener( ... ); // 但是这个方法在SDK中不存在 或者,我们可以使用`ActivityTaskManager`来监听: ActivityTaskManager am = (ActivityTaskManager) mContext.getSystemService(Context.ACTIVITY_TASK_SERVICE); am.registerTaskStackListener(new TaskStackListener() { @Override public void onTaskStackChanged() { // 当任务栈变化时,可能是应用切换 updateCurrentApp(); } }); 在`updateCurrentApp`中,我们可以获取当前前台应用: private void updateCurrentApp() { ActivityManager am = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE); List<ActivityManager.RunningAppProcessInfo> processes = am.getRunningAppProcesses(); if (processes != null) { for (ActivityManager.RunningAppProcessInfo process : processes) { if (process.importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND) { String packageName = process.pkgList[0]; // 注意:可能有多个,但通常取第一个 updateStatusBarForApp(packageName); break; } } } } 但是,这种方法效率较低,并且可能不准确。更推荐使用`UsageStatsManager`或`ActivityTaskManager`的`getRunningTasks`(注意:需要权限,且在高版本中已废弃)。 在Android 5.0以上,我们可以使用`ActivityManager`的`getRunningAppProcesses`,但注意它可能不返回。 在SystemUI中,通常使用`StatusBarStateController`来监听状态,结合`ShadeViewController`等。 但是,在Android 10之后,Google推荐使用`ProcessObserver`来监听前台应用变化。 在SystemUI中,有一个`CurrentUserContextTracker`,但它主要用于跟踪当前用户。 在SystemUI中,实际上有一个`ForegroundServiceController`,但它用于前台服务通知。 我们发现在SystemUI中有一个类`ForegroundServiceNotificationListener`,它监听前台服务通知,但这不是我们需要的。 实际上,SystemUI中有一个`KeyguardUpdateMonitor`,它有一个回调可以监听用户切换和启动。 但是,我们可以使用`ActivityManager`的`getRunningTasks`(虽然已废弃,但在系统应用仍可使用)来获取当前前台应用: ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); List<ActivityManager.RunningTaskInfo> tasks = activityManager.getRunningTasks(1); if (tasks != null && !tasks.isEmpty()) { ActivityManager.RunningTaskInfo task = tasks.get(0); ComponentName componentName = task.topActivity; String packageName = componentName.getPackageName(); // 然后处理 } 注意:需要权限`android.permission.GET_TASKS`,在系统应用中可以声明。 步骤5:在AndroidManifest.xml中添加权限: <uses-permission android:name="android.permission.GET_TASKS" /> 由于SystemUI是系统应用,所以可以声明这个权限。 但是,从Android 11开始,`getRunningTasks`被废弃,并且可能无法获取其他应用的任务。在系统应用中使用`getRunningTasks`仍然可以获取,但需要系统权限。 在SystemUI中,我们可以使用`ActivityTaskManager`的`getTasks`方法(需要系统权限)来获取任务栈。 在Android 15中,我们可以使用: ActivityTaskManager activityTaskManager = (ActivityTaskManager) context.getSystemService(Context.ACTIVITY_TASK_SERVICE); List<ActivityManager.RunningTaskInfo> tasks = activityTaskManager.getTasks(1); if (tasks != null && !tasks.isEmpty()) { String packageName = tasks.get(0).topActivity.getPackageName(); // ... } 步骤6:将上述逻辑整合到StatusBar中。 由于修改涉及多个类,并且需要维护一个应用列表,我们可以创建一个新的类来管理。 但是,注意性能:应用切换频繁,不要做耗时操作。 总结实现步骤: 1. 创建一个管理类`StatusBarAppConfig`,用于存储需要隐藏状态栏的应用列表(可以从设置中读取,这里我们先硬编码或通过系统属性等方式配置)。 2. 在StatusBar的初始化中,注册一个任务栈监听器(或使用其他方式监听前台应用变化)。 3. 在监听回调中,获取当前前台应用的,然后判断是否在列表中,然后更新状态栏的显示和手势。 4. 更新状态栏的显示:通过修改状态栏窗口的高度和触摸标志,同时隐藏状态栏视图。 注意:在状态下,我们不应该隐藏状态栏。因此,我们需要监听当前状态(、桌面、应用等)。 在StatusBar中,我们可以通过`mState`(状态栏状态)来判断当前状态。例如,如果当前是状态,则即使指定应用也不隐藏。 因此,我们需要在更新时检查当前状态: if (mState == StatusBarState.KEYGUARD) { // ,不隐藏 return; } 另外,在桌面(Launcher)时,我们可能也不想隐藏。 所以,我们需要在`updateStatusBarForApp`中加入条件: if (shouldHide && !isKeyguard && !isHomeApp(packageName)) { // 隐藏 } else { // 恢复 } 其中,`isHomeApp`可以判断当前应用是否是桌面应用。 获取桌面应用: Intent intent = new Intent(Intent.ACTION_MAIN); intent.addCategory(Intent.CATEGORY_HOME); ResolveInfo resolveInfo = getPackageManager().resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY); if (resolveInfo != null) { homePackage = resolveInfo.activityInfo.packageName; } 然后判断当前是否等于homePackage。 由于这些修改涉及系统核心功能,需要谨慎。 代码实现示例(基于Android 15,类和具体方法可能有所不同): 在StatusBar的初始化中(如`start`方法): // 初始化任务栈监听 mActivityTaskManager = ActivityTaskManager.getInstance(); mTaskStackListener = new TaskStackListener() { @Override public void onTaskStackChanged() { updateCurrentApp(); } }; mActivityTaskManager.registerTaskStackListener(mTaskStackListener); 同时,在StatusBar中实现`updateCurrentApp`: private void updateCurrentApp() { if (mActivityTaskManager == null) { return; } List<ActivityManager.RunningTaskInfo> tasks = null; try { tasks = mActivityTaskManager.getTasks(1); } catch (Exception e) { Log.e(TAG, "getTasks error", e); } if (tasks != null && !tasks.isEmpty()) { ActivityManager.RunningTaskInfo task = tasks.get(0); ComponentName componentName = task.topActivity; if (componentName != null) { String packageName = componentName.getPackageName(); boolean shouldHide = mStatusBarAppConfig.shouldHideStatusBar(packageName); boolean isKeyguard = mStatusBarStateController.getState() == StatusBarState.KEYGUARD; String homePackage = getHomePackage(); boolean isHome = packageName.equals(homePackage); if (shouldHide && !isKeyguard && !isHome) { hideStatusBar(); } else { showStatusBar(); } } } } 然后实现`hideStatusBar`和`showStatusBar`: private void hideStatusBar() { // 修改窗口参数 WindowManager.LayoutParams lp = mStatusBarWindow.getWindowLayoutParams(); lp.height = 0; lp.flags |= WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE; mStatusBarWindow.setWindowLayoutParams(lp); // 隐藏视图 mStatusBarView.setVisibility(View.GONE); } private void showStatusBar() { WindowManager.LayoutParams lp = mStatusBarWindow.getWindowLayoutParams(); lp.height = mBarHeight; // 需要保存原始高度 lp.flags &= ~WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE; mStatusBarWindow.setWindowLayoutParams(lp); mStatusBarView.setVisibility(View.VISIBLE); } 注意:mBarHeight可以在初始化时获取: mBarHeight = mContext.getResources().getDimensionPixelSize(com.android.internal.R.dimen.status_bar_height); 同时,我们需要保存原始的高度和标志,以便恢复。 另外,在状态变化时也要更新: mStatusBarStateController.addCallback(new StateListener() { @Override public void onStateChanged(int state) { updateCurrentApp(); } }); 这样,当状态变化时,也会更新状态栏的显示。 注意事项: - 这个修改会影响系统UI,需要充分测试。 - 在隐藏状态栏时,要确保其他组件(如导航栏)不受影响。 - 需要处理横竖切换,因为状态栏高度可能会变。 由于这是系统级修改,需要重新编译SystemUI并刷机测试。 以上是一个大致的实现思路。具体实现可能需要根据Android 15的源码进行调整。 由于用户要求对指定应用隐藏状态栏且禁止滑动显示,我们的方法是在系统层面监听应用切换,动态调整状态栏窗口和视图。 最后,提供一个配置接口(例如,通过系统属性、设置数据库或配置文件)来设置哪些应用需要隐藏状态栏,这部分可以根据产品需求实现。 相关问题: §§相关问题§§ 1. 如何动态配置需要隐藏状态栏的应用列表? 2. 在隐藏状态栏后,如何确保应用能够全显示? 3. 除了状态栏,如何同时隐藏导航栏? 4. 在Android 15中,如何监听前台应用变化的其他方法? 5. 如何优化状态栏隐藏和显示的性能? </think>### Android 15 SystemUI 对指定应用隐藏状态栏且禁止滑动显示的实现方法 在 Android 15 中实现**对特定应用隐藏状态栏且禁止滑动显示**的功能,需要修改 SystemUI 的核心逻辑。以下是具体实现步骤: --- ### 一、核心实现原理 1. **动态状态栏控制** 在 `StatusBar` 相关类中监听应用切换事件,当检测到指定应用进入前台时: - 强制隐藏状态栏 - 禁用状态栏下拉手势 - 拦截触摸事件 2. **禁用滑动逻辑** 在 `NotificationPanelViewController` 中覆写触摸事件处理方法,当目标应用运行时直接拦截手势。 --- ### 、核心修改类 1. `frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java` - 监听应用切换 - 控制状态栏显隐 2. `frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java` - 禁用下拉手势 - 拦截触摸事件 3. 自定义配置文件(如 `HiddenAppsConfig.java`) - 管理需要隐藏状态栏的应用列表 --- ### 三、具体实现步骤 #### 步骤 1:创建应用配置管理类 ```java // HiddenAppsConfig.java public class HiddenAppsConfig { private static final Set<String> HIDDEN_APPS = new ArraySet<>(); static { HIDDEN_APPS.add("com.target.app.package"); HIDDEN_APPS.add("com.another.app.package"); } public static boolean shouldHideStatusBar(String packageName) { return HIDDEN_APPS.contains(packageName); } } ``` #### 步骤 2:在 StatusBar 中添加动态控制逻辑 ```java // StatusBar.java public class StatusBar { private String mCurrentPackage; private boolean mForceHideStatusBar; // 监听应用切换 private final ActivityManager.OnTaskStackChangedListener mTaskListener = () -> { ActivityManager am = getSystemService(ActivityManager.class); List<ActivityManager.RunningTaskInfo> tasks = am.getRunningTasks(1); if (!tasks.isEmpty()) { String packageName = tasks.get(0).topActivity.getPackageName(); updateStatusBarVisibility(packageName); } }; private void updateStatusBarVisibility(String packageName) { mCurrentPackage = packageName; boolean shouldHide = HiddenAppsConfig.shouldHideStatusBar(packageName); if (shouldHide != mForceHideStatusBar) { mForceHideStatusBar = shouldHide; // 触发状态栏刷新 mStatusBarWindowController.setForceHide(shouldHide); mNotificationPanelViewController.setGesturesDisabled(shouldHide); } } } ``` #### 步骤 3:修改状态栏窗口控制器 ```java // StatusBarWindowController.java public void setForceHide(boolean hide) { if (hide) { // 完全隐藏状态栏 mCurrentState.setSystemUiVisibility(View.STATUS_BAR_HIDDEN); } else { // 恢复默认状态 mCurrentState.setSystemUiVisibility(View.STATUS_BAR_VISIBLE); } apply(); } ``` #### 步骤 4:禁用通知面板手势 ```java // NotificationPanelViewController.java public void setGesturesDisabled(boolean disabled) { mGesturesDisabled = disabled; } @Override public boolean onTouchEvent(MotionEvent event) { if (mGesturesDisabled) { // 完全拦截所有触摸事件 return true; } return super.onTouchEvent(event); } @Override public boolean onInterceptTouchEvent(MotionEvent event) { return mGesturesDisabled || super.onInterceptTouchEvent(event); } ``` --- ### 四、补充说明 1. **隐藏状态栏的实现方式**: 使用 `View.STATUS_BAR_HIDDEN` 替代旧版 `View.SYSTEM_UI_FLAG_FULLSCREEN`,确保兼容 Android 15 的新沉浸模式 API。 2. **手势拦截优先级**: 在 `NotificationPanelViewController` 中直接拦截事件,比全局修改触摸分发更高效[^1]。 3. **状态保存**: 在 `onTaskStackChanged` 中实时检测应用切换,确保离开目标应用后自动恢复状态栏[^2]。 --- ### 注意事项 1. **系统签权限**: 修改 SystemUI 需使用平台签编译 APK。 2. **兼容性处理**: 在 `StatusBar` 初始化时添加权限检查: ```java if (!Settings.Secure.getInt(mContentResolver, "force_hide_statusbar", 0) == 1) { // 安全模式下禁用功能 } ``` 3. **边缘情况**: 处理多任务分场景,需在 `onMultiWindowModeChanged()` 中同步更新状态栏状态。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Erorrs

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值