Android清除所有noticafition的一些探究

最近在开发xposed模块中想要实现一个清除所有通知的功能,但是发现cancelAll只能清除单个应用的所有通知。

查看NotificationManager源码可知
http://androidxref.com/5.1.0_r1/xref/frameworks/base/core/java/android/app/NotificationManager.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
28    /**
229 * Cancel all previously shown notifications. See {@link #cancel} for the
230 * detailed behavior.
231 */
232 public void cancelAll()
233 {
234 INotificationManager service = getService();
//此处获取了包名,导致只能关闭该包下的消息
235 String pkg = mContext.getPackageName();
236 if (localLOGV) Log.v(TAG, pkg + ": cancelAll()");
237 try {
238 service.cancelAllNotifications(pkg, UserHandle.myUserId());
239 } catch (RemoteException e) {
240 }
241 }

再次查看UserHandle.myUserId的源码

1
2
3
4
5
6
7
8
9
10
/**
233 * Returns the user id of the current process
234 * @return user id of the current process
235 * @hide 这个方法是隐藏的
236 */
237 @SystemApi
238 public static final int myUserId() {
//获取进程对应的UID
239 return getUserId(Process.myUid());
240 }

cancelAll中关闭通知使用了uid和包名,可以看出来其实是只关闭一个应用里所有的通知

但是通知栏上面明明是有一个清除所有通知的按钮,通过源码来看一下它的实现
在源码中找到一个这样的布局
http://androidxref.com/5.1.0_r1/xref/frameworks/base/packages/SystemUI/res/layout/status_bar_notification_dismiss_all.xml
该布局里面只有一个DismissViewButton控件,通过调用它的ID,找到了这样一个类:
http://androidxref.com/5.1.0_r1/xref/frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/DismissView.java
通过里面的setOnButtonClickListener,在源码中查找调用类这个方法的类
http://androidxref.com/5.1.0_r1/xref/frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
在PhoneStatusBar中,有这样一段代码:

1
2
3
4
5
6
744        mDismissView.setOnButtonClickListener(new View.OnClickListener() {
745 @Override
746 public void onClick(View v) {
747 clearAllNotifications();
748 }
749 });

clearAllNotifications() 这个方法应该就是我们想要的那个清除所有通知的方法
我们来看一下它的具体实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
943    private void clearAllNotifications() {
944
945 // animate-swipe all dismissable notifications, then animate the shade closed
946 int numChildren = mStackScroller.getChildCount();
947
948 final ArrayList<View> viewsToHide = new ArrayList<View>(numChildren);
949 for (int i = 0; i < numChildren; i++) {
950 final View child = mStackScroller.getChildAt(i);
951 if (mStackScroller.canChildBeDismissed(child)) {
952 if (child.getVisibility() == View.VISIBLE) {
953 viewsToHide.add(child);
954 }
955 }
956 }
957 if (viewsToHide.isEmpty()) {
958 animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE);
959 return;
960 }
961
962 addPostCollapseAction(new Runnable() {
963 @Override
964 public void run() {
965 try {
966 mBarService.onClearAllNotifications(mCurrentUserId);
967 } catch (Exception ex) { }
968 }
969 });
970
971 performDismissAllAnimations(viewsToHide);
972
973 }

通过Xposed Hook后调用该方法,成功实现清除所有通知栏的内容,但是仍然有一改问题:在通知栏收起的情况下,清除通知后,状态栏仍然有图标存在,而在下拉状态栏后才清除。
这个问题应该是由于该方法中

1
2
3
952                if (child.getVisibility() == View.VISIBLE) {
953 viewsToHide.add(child);
954 }

这一段代码造成的。
解决方法:
1.使用xposed hook替换整个清除通知栏的方法,去掉这一部分。比较麻烦,而且可能会造成某些问题,但是效果好。
2.在调用hook出来的clearAllNotificatons的时候,执行一下拉通知栏和收起通知栏的方法。效果稍微差一些,但是不至于出现太大问题。