Android清除所有noticafition的一些探究(二)

说实话我自己都没想到关于这个东西会在三个月以后写第二篇233333

写上一篇关于清除所有通知的博客已经是六月上旬的事情了
https://egguncle.github.io/2017/07/08/Android%E6%B8%85%E9%99%A4%E6%89%80%E6%9C%89noticafition%E7%9A%84%E4%B8%80%E4%BA%9B%E6%8E%A2%E7%A9%B6/#more

当时我找到了源码中调用清除所有通知的方法,并尝试调用使其清除所有通知,然后遇上了一个奇怪的问题,就是在通知栏收起的情况下,无法清除所有通知,状态栏仍然有图标存在,需要手动下拉一下通知栏再收起才可以清除。

然后我是这样说的:

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

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

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

本着再把这个功能优化一下的想法,决定使用我提到的第一种方法来实现这个功能,即使用replaceHookedMethod重写整个方法,然后按照上面提到的,去除child.getVisibility() == View.VISIBLE的判断,但是实际操作之后发现并没有出现料想之中的结果,即点击清除所有通知的快捷按钮以后,通知被清除且图标消失。
然后我又重新把这一段源码看了一遍

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
32
33
34
35
36
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)) {
//canChildBeDismissed这个方法似乎是判断一下通知栏里面veto(因该是禁止通知按钮)是不是为空和是不是可见return (veto != null && veto.getVisibility() != View.GONE);
952 if (child.getVisibility() == View.VISIBLE) {
//哇这里明明就是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 }

以上的各个方法我在重写执行时都尝试过注释他们不让他们运行,然后通知栏的行为出现了不一样的情况,比如下拉通知通知变得不可见了但是清除通知的按钮很靠下,而且无法点击,或者清除通知以后状态栏没有收回去等等。。。就不一一列举了,
但是不论怎么搞都没有实现我想要的效果,然后我注意到了mBarService这个玩意,它调用了一个onClearAllNotifications方法,看起来像是真的来清除通知栏提示的。
哇既然找到了就看一看它:
跟了一下代码,发现mBarService其实是一个AIDL接口:
http://androidxref.com/5.1.0_r1/xref/frameworks/base/core/java/com/android/internal/statusbar/IStatusBarService.aidl

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
32
33
34
35
36
37
38
39
40
41
24/** @hide */
25interface IStatusBarService
26{
27 void expandNotificationsPanel();
28 void collapsePanels();
29 void disable(int what, IBinder token, String pkg);
30 void setIcon(String slot, String iconPackage, int iconId, int iconLevel, String contentDescription);
31 void setIconVisibility(String slot, boolean visible);
32 void removeIcon(String slot);
33 void topAppWindowChanged(boolean menuVisible);
34 void setImeWindowStatus(in IBinder token, int vis, int backDisposition,
35 boolean showImeSwitcher);
36 void expandSettingsPanel();
37 void setCurrentUser(int newUserId);
38
39 // ---- Methods below are for use by the status bar policy services ----
40 // You need the STATUS_BAR_SERVICE permission
41 void registerStatusBar(IStatusBar callbacks, out StatusBarIconList iconList,
42 out int[] switches, out List<IBinder> binders);
43 void onPanelRevealed(boolean clearNotificationEffects);
44 void onPanelHidden();
45 // Mark current notifications as "seen" and stop ringing, vibrating, blinking.
46 void clearNotificationEffects();
47 void onNotificationClick(String key);
48 void onNotificationActionClick(String key, int actionIndex);
49 void onNotificationError(String pkg, String tag, int id,
50 int uid, int initialPid, String message, int userId);
51 void onClearAllNotifications(int userId);
52 void onNotificationClear(String pkg, String tag, int id, int userId);
53 void onNotificationVisibilityChanged(
54 in String[] newlyVisibleKeys, in String[] noLongerVisibleKeys);
55 void onNotificationExpansionChanged(in String key, in boolean userAction, in boolean expanded);
56 void setSystemUiVisibility(int vis, int mask, String cause);
57 void setWindowState(int window, int state);
58
59 void showRecentApps(boolean triggeredFromAltTab);
60 void hideRecentApps(boolean triggeredFromAltTab, boolean triggeredFromHomeKey);
61 void toggleRecentApps();
62 void preloadRecentApps();
63 void cancelPreloadRecentApps();
64}

再找找看onClearAllNotifications实际被调用的地方:
http://androidxref.com/5.1.0_r1/xref/frameworks/base/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
最后找到了它的实现的地方

1
2
3
4
5
6
7
8
9
10
11
12
615    @Override
616 public void onClearAllNotifications(int userId) {
617 enforceStatusBarService();
618 final int callingUid = Binder.getCallingUid();
619 final int callingPid = Binder.getCallingPid();
620 long identity = Binder.clearCallingIdentity();
621 try {
622 mNotificationDelegate.onClearAll(callingUid, callingPid, userId);
623 } finally {
624 Binder.restoreCallingIdentity(identity);
625 }
626 }

NotificationDelegate是一个接口,它实际被实现的地方在NotificationManagerService的中,来看一下这个onClearAll方法的实现

1
2
3
4
5
6
7
532        @Override
533 public void onClearAll(int callingUid, int callingPid, int userId) {
534 synchronized (mNotificationList) {
535 cancelAllLocked(callingUid, callingPid, userId, REASON_DELEGATE_CANCEL_ALL, null,
536 /*includeCurrentProfiles*/ true);
537 }
538 }

再看看cancelAllLocked的实现以及它调用的另外一个方法

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
2677    void cancelAllLocked(int callingUid, int callingPid, int userId, int reason,
2678 ManagedServiceInfo listener, boolean includeCurrentProfiles) {
2679 String listenerName = listener == null ? null : listener.component.toShortString();
2680 EventLogTags.writeNotificationCancelAll(callingUid, callingPid,
2681 null, userId, 0, 0, reason, listenerName);
2682
2683 ArrayList<NotificationRecord> canceledNotifications = null;
2684 final int N = mNotificationList.size();
2685 for (int i=N-1; i>=0; i--) {
2686 NotificationRecord r = mNotificationList.get(i);
2687 if (includeCurrentProfiles) {
2688 if (!notificationMatchesCurrentProfiles(r, userId)) {
2689 continue;
2690 }
2691 } else {
2692 if (!notificationMatchesUserId(r, userId)) {
2693 continue;
2694 }
2695 }
2696
2697 if ((r.getFlags() & (Notification.FLAG_ONGOING_EVENT
2698 | Notification.FLAG_NO_CLEAR)) == 0) {
2699 mNotificationList.remove(i);
2700 cancelNotificationLocked(r, true, reason);
2701 // Make a note so we can cancel children later.
2702 if (canceledNotifications == null) {
2703 canceledNotifications = new ArrayList<>();
2704 }
2705 canceledNotifications.add(r);
2706 }
2707 }
2708 int M = canceledNotifications != null ? canceledNotifications.size() : 0;
2709 for (int i = 0; i < M; i++) {
2710 cancelGroupChildrenLocked(canceledNotifications.get(i), callingUid, callingPid,
2711 listenerName, REASON_GROUP_SUMMARY_CANCELED);
2712 }
2713 updateLightsLocked();
2714 }
2715
2716 // Warning: The caller is responsible for invoking updateLightsLocked().
2717 private void cancelGroupChildrenLocked(NotificationRecord r, int callingUid, int callingPid,
2718 String listenerName, int reason) {
2719 Notification n = r.getNotification();
2720 if (!n.isGroupSummary()) {
2721 return;
2722 }
2723
2724 String pkg = r.sbn.getPackageName();
2725 int userId = r.getUserId();
2726
2727 if (pkg == null) {
2728 if (DBG) Log.e(TAG, "No package for group summary: " + r.getKey());
2729 return;
2730 }
2731
2732 final int N = mNotificationList.size();
2733 for (int i = N - 1; i >= 0; i--) {
2734 NotificationRecord childR = mNotificationList.get(i);
2735 StatusBarNotification childSbn = childR.sbn;
2736 if (childR.getNotification().isGroupChild() &&
2737 childR.getGroupKey().equals(r.getGroupKey())) {
2738 EventLogTags.writeNotificationCancel(callingUid, callingPid, pkg, childSbn.getId(),
2739 childSbn.getTag(), userId, 0, 0, reason, listenerName);
//具体的清除就是在这里了
2740 mNotificationList.remove(i);
2741 cancelNotificationLocked(childR, false, reason);
2742 }
2743 }
2744 }

至此终于找到了清除的实现的部分,但是暂时还是不会把这些东西做一个调用加入到xpnavbar模块中,主要是调用的步骤复杂了一些,可能会出现系统版本不同导致奇奇怪怪的问题带来不稳定的因素。

最后放两个小彩蛋
1.关于整个方法替换的实现:

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
32
33
34
35
36
37
38
39
40
41
42
Class<?> istatusbarservice = lpparam.classLoader.loadClass(I_STATUS_BAR_SERVICE);
final Method onClearAllNotifications = istatusbarservice.getMethod("onClearAllNotifications", int.class);

XposedHelpers.findAndHookMethod(phoneStatusBarClass, "clearAllNotifications", new XC_MethodReplacement() {
@Override
protected Object replaceHookedMethod(MethodHookParam param) throws Throwable {
XpLog.i("try to replace clearAllNotifications");
ViewGroup mStackScroller = (ViewGroup) XposedHelpers.getObjectField(param.thisObject, "mStackScroller");

int numChildren = mStackScroller.getChildCount();
final ArrayList<View> viewsToHide = new ArrayList<View>(numChildren);
for (int i = 0; i < numChildren; i++) {
final View child = mStackScroller.getChildAt(i);
boolean b = (boolean) XposedHelpers.callMethod(mStackScroller, "canChildBeDismissed", child);
if (b) {
if (child.getVisibility() == View.VISIBLE) {
viewsToHide.add(child);
}
}
}
if (viewsToHide.isEmpty()) {
XposedHelpers.callMethod(param.thisObject, "animateCollapsePanels", 0);
return null;
}
final Object mBarService = XposedHelpers.getObjectField(param.thisObject, "mBarService");
final int mCurrentUserId = (int) XposedHelpers.getObjectField(param.thisObject, "mCurrentUserId");
XposedHelpers.callMethod(param.thisObject, "addPostCollapseAction", new Runnable() {
@Override
public void run() {
try {
onClearAllNotifications.invoke(mBarService, mCurrentUserId);
} catch (Exception e) {
XpLog.e(e);
}
}
});

XposedHelpers.callMethod(param.thisObject, "performDismissAllAnimations", viewsToHide);
XpLog.i("--- clearAllNotifications ---");
return null;
}
});

2.关于clearAllNotifications这个方法,在5.0和6.0的实现并不一样

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
952    private void clearAllNotifications() {
953
954 // animate-swipe all dismissable notifications, then animate the shade closed
955 int numChildren = mStackScroller.getChildCount();
956
957 final ArrayList<View> viewsToHide = new ArrayList<View>(numChildren);
958 for (int i = 0; i < numChildren; i++) {
959 final View child = mStackScroller.getChildAt(i);
960 if (child instanceof ExpandableNotificationRow) {
961 if (mStackScroller.canChildBeDismissed(child)) {
962 if (child.getVisibility() == View.VISIBLE) {
963 viewsToHide.add(child);
964 }
965 }
具体不一样的部分就是这里了,因为6.0 多了一个通知栏提醒可以展开的功能吧。
------------------------------------
966 ExpandableNotificationRow row = (ExpandableNotificationRow) child;
967 List<ExpandableNotificationRow> children = row.getNotificationChildren();
968 if (row.areChildrenExpanded() && children != null) {
969 for (ExpandableNotificationRow childRow : children) {
970 if (childRow.getVisibility() == View.VISIBLE) {
971 viewsToHide.add(childRow);
972 }
973 }
974 }
------------------------------------
975 }
976 }
977 if (viewsToHide.isEmpty()) {
978 animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE);
979 return;
980 }
981
982 addPostCollapseAction(new Runnable() {
983 @Override
984 public void run() {
985 mStackScroller.setDismissAllInProgress(false);
986 try {
987 mBarService.onClearAllNotifications(mCurrentUserId);
988 } catch (Exception ex) { }
989 }
990 });
991
992 performDismissAllAnimations(viewsToHide);
993
994 }