背景描述
客户说要屏蔽掉某个应用里的通知,但是不全部屏蔽,只屏蔽指定应用的部分通知。
很奇怪,这个东西要系统来做,应用不发通知不就好了嘛?
但是没办法,甲方爸爸的需求,唉(:
需求实现
方案1 ------ systemui中屏蔽通知
vendor/mediatek/proprietary/packages/apps/SystemUI/src/com/android/systemui/statusbar/notification/NotificationFilter.java的shouldFilterOut方法中添加过滤
/**
* "packageName/channelId"
* 表示 包名中此channelID的通知可以显示,其它全部屏蔽,判断逻辑见{@link notificationHide}
* */
private static void initNotificationFilterList(){
notificationFilterList.add("com.aaa.bbb/background_download");
notificationFilterList.add("com.aaaa.ccc/background__upgrade");
}
/**
* @return true/false 标时指定同时是否需要隐藏
* @param key 指定应用的包名
* @param channelId 显示指定的通知,其它通知全部过滤
* */
public static boolean notificationHide(String key,String channelId){
if (notificationFilterList.size() == 0){
initNotificationFilterList();
}
if (key != null && key.length() > 0 && channelId != null && channelId.length() > 0){
for (int i=0;i < notificationFilterList.size();i++){
String index = notificationFilterList.get(i);
String packageName ;
String cId ;
if (index != null && index.length() > 0){
String[] dex = index.split("/");
if (dex !=null && dex.length == 2){
packageName = dex[0];
cId = dex[1];
if (key.contains(packageName)
&& !channelId.equals(cId)) {
return true;
}
}
}
}
}
return false;
}
/**
* @return true if the provided notification should NOT be shown right now.
*/
public boolean shouldFilterOut(NotificationEntry entry) {
final StatusBarNotification sbn = entry.getSbn();
...
if (sbn != null){
if (notificationHide(sbn.getKey(),sbn.getNotification().getChannelId())){
//屏蔽应用通知
return true;
}
}
...
return false;
}
vendor/mediatek/proprietary/packages/apps/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java的onNotificationPosted中
@Override
public void onNotificationPosted(final StatusBarNotification sbn,
final RankingMap rankingMap) {
if (DEBUG) Log.d(TAG, "onNotificationPosted: " + sbn);
if (sbn != null){
//屏蔽应用通知
if (NotificationFilter.notificationHide(sbn.getKey(),sbn.getNotification().getChannelId())){
return ;
}
}
if (sbn != null && !onPluginNotificationPosted(sbn, rankingMap)) {
mMainHandler.post(() -> {
processForRemoteInput(sbn.getNotification(), mContext);
for (NotificationHandler handler : mNotificationHandlers) {
handler.onNotificationPosted(sbn, rankingMap);
}
});
}
}
上述方法可以屏蔽systemui下面的通知,实现指定应用只显示指定通知,其它通知全部隐藏的需求。
but
这个效果只是不显示通知,通知本身还是在的。
1、比如launcher的通知小圆点,长按桌面图标的通知显示等问题。
frameworks/base/core/java/android/service/notification/NotificationListenerService.java
的getActiveNotifications方法获取通知。
2、其它应用可以通过
frameworks/base/services/core/java/com/android/server/notification/NotificationManagerService.java的getNotificationRecord、getActiveNotificationsFromListener或者其他方法获取通知内容。
换个思路重新触发
我们应该从根本上删除那些要屏蔽的通知,或者从一开始就不让它add到NotificationManagerService中去。
方案2 ------ framework中屏蔽
frameworks/base/services/core/java/com/android/server/notification/NotificationManagerService.java中
通知的获取主要在
@GuardedBy("mNotificationLock")
final ArrayList<NotificationRecord> mNotificationList = new ArrayList<>();
@GuardedBy("mNotificationLock")
final ArrayMap<String, NotificationRecord> mNotificationsByKey = new ArrayMap<>();
这两个列表和map中存储。我们要实现阻断添加,实现屏蔽特定通知。
1、跟踪上面2个add和put数据的地方
2、PostNotificationRunnable的run方法中进行add和put通知数据
3、EnqueueNotificationRunnable中启动的PostNotificationRunnable
4、enqueueNotificationInternal方法中启动EnqueueNotificationRunnable,但是后续发现还有其他地方也启用了EnqueueNotificationRunnable。
所以我们在enqueueNotificationInternal和EnqueueNotificationRunnable 2个地方都加了一些屏蔽处理。
void enqueueNotificationInternal(...){
...
if (notificationHide(pkg,channelId)){//过滤通知
return;
}
if (channel == null) {
final String noChannelStr = "No Channel found for "
}
...
}
内部类EnqueueNotificationRunnable的run方法中
public void run() {
...
if (contextId != null) {
(new SnoozeNotificationRunnable(r.getSbn().getKey(),
0, contextId)).snoozeLocked(r);
return;
}
//屏蔽通知
if(r != null && r.getSbn() !=null && r.getSbn().getNotification() !=null){
String key = r.getSbn().getKey();
String channelId = r.getSbn().getNotification().getChannelId();
if (notificationHide(key,channelId)){
return;
}
}
mEnqueuedNotifications.add(r);
...
}
这样修改之后,就可以完全屏蔽,通知不会添加到列表中了。
结尾
上述流程只是notification通知流程中的一小部分
完整的通知发送----framework添加通知----systemui显示通知。等流程没有完全跟踪。
如果有理解不到位的地方,欢迎留言~
补充
通知的使用:https://blog.csdn.net/jppipai/article/details/122864465
1、应用发送通知------framework中service管理通知数据
NotificationManager.notify------NotificationManagerService.enqueueNotificationWithTag------NotificationManagerService.enqueueNotificationInternal
这里就和上面service屏蔽通知的地方呼应上了。
//manager作为给上层的接口,最终还是会掉到service管理通知数据
frameworks/base/core/java/android/app/NotificationManager.java
public void notify(int id, Notification notification)
{
notify(null, id, notification);
}
public void notify(String tag, int id, Notification notification)
{
notifyAsUser(tag, id, notification, mContext.getUser());
}
public void notifyAsUser(String tag, int id, Notification notification, UserHandle user)
{
INotificationManager service = getService();
String pkg = mContext.getPackageName();
try {
if (localLOGV) Log.v(TAG, pkg + ": notify(" + id + ", " + notification + ")");
service.enqueueNotificationWithTag(pkg, mContext.getOpPackageName(), tag, id,
fixNotification(notification), user.getIdentifier());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
//service管理通知数据
frameworks/base/services/core/java/com/android/server/notification/NotificationManagerService.java
@Override
public void enqueueNotificationWithTag(String pkg, String opPkg, String tag, int id,
Notification notification, int userId) throws RemoteException {
enqueueNotificationInternal(pkg, opPkg, Binder.getCallingUid(),
Binder.getCallingPid(), tag, id, notification, userId);
}