SystemUI之notification排序

1.在syatemUI显示相关通知之前,做了很多的准备工作,这边我们来说说notification的排序。

排序工作主要是在NotificationManagerService.java里面实现的。

路径:frameworks/base/services/core/java/com/android/server/notification/NotificationManagerService.java

我们来看下enqueueNotificationWithTag方法,这里调用了enqueueNotificationInternal方法,这里就是通知处理的核心了。

    void enqueueNotificationInternal(final String pkg, final String opPkg, final int callingUid,  
            final int callingPid, final String tag, final int id, final Notification notification,  
            int[] idOut, int incomingUserId) {  
        if (DBG) {  
            Slog.v(TAG, “enqueueNotificationInternal: pkg=” + pkg + ” id=” + id  
                    + ” notification=” + notification);  
        }  
        checkCallerIsSystemOrSameApp(pkg);  
        // 校验UID  
        final boolean isSystemNotification = isUidSystem(callingUid) || (“android”.equals(pkg));  
        final boolean isNotificationFromListener = mListeners.isListenerPackage(pkg);  
      
        final int userId = ActivityManager.handleIncomingUser(callingPid,  
                callingUid, incomingUserId, true, false, “enqueueNotification”, pkg);  
        final UserHandle user = new UserHandle(userId);  
      
        // Fix the notification as best we can.  
        try {  
            final ApplicationInfo ai = getContext().getPackageManager().getApplicationInfoAsUser(  
                    pkg, PackageManager.MATCH_DEBUG_TRIAGED_MISSING,  
                    (userId == UserHandle.USER_ALL) ? UserHandle.USER_SYSTEM : userId);  
            Notification.addFieldsFromContext(ai, userId, notification);  
        } catch (NameNotFoundException e) {  
            Slog.e(TAG, “Cannot create a context for sending app”, e);  
            return;  
        }  
      
        mUsageStats.registerEnqueuedByApp(pkg);  
      
        // Limit the number of notifications that any given package except the android  
        // package or a registered listener can enqueue.  Prevents DOS attacks and deals with leaks.  
        // 这里会做一个限制,除了系统级别的应用之外,其他应用的notification数量会做限制,用来防止DOS攻击导致的泄露  
        if (!isSystemNotification && !isNotificationFromListener) {  
            synchronized (mNotificationList) {  
                final float appEnqueueRate = mUsageStats.getAppEnqueueRate(pkg);  
                if (appEnqueueRate > mMaxPackageEnqueueRate) {  
                    mUsageStats.registerOverRateQuota(pkg);  
                    final long now = SystemClock.elapsedRealtime();  
                    if ((now – mLastOverRateLogTime) > MIN_PACKAGE_OVERRATE_LOG_INTERVAL) {  
                        Slog.e(TAG, “Package enqueue rate is ” + appEnqueueRate  
                                + “. Shedding events. package=” + pkg);  
                        mLastOverRateLogTime = now;  
                    }  
                    return;  
                }  
      
                int count = 0;  
                final int N = mNotificationList.size();  
                for (int i=0; i<N; i++) {  
                    final NotificationRecord r = mNotificationList.get(i);  
                    if (r.sbn.getPackageName().equals(pkg) && r.sbn.getUserId() == userId) {  
                        if (r.sbn.getId() == id && TextUtils.equals(r.sbn.getTag(), tag)) {  
                            break;  // Allow updating existing notification  
                        }  
                        count++;  
                        if (count >= MAX_PACKAGE_NOTIFICATIONS) {// 同一个应用发送notification数量不能超过50  
                            mUsageStats.registerOverCountQuota(pkg);  
                            Slog.e(TAG, “Package has already posted ” + count  
                                    + ” notifications.  Not showing more.  package=” + pkg);  
                            return;  
                        }  
                    }  
                }  
            }  
        }  
      
        if (pkg == null || notification == null) {//通知不能为空  
            throw new IllegalArgumentException(“null not allowed: pkg=” + pkg  
                    + ” id=” + id + ” notification=” + notification);  
        }  
      
        // Whitelist pending intents.  
        if (notification.allPendingIntents != null) {  
            final int intentCount = notification.allPendingIntents.size();  
            if (intentCount > 0) {  
                final ActivityManagerInternal am = LocalServices  
                        .getService(ActivityManagerInternal.class);  
                final long duration = LocalServices.getService(  
                        DeviceIdleController.LocalService.class).getNotificationWhitelistDuration();  
                for (int i = 0; i < intentCount; i++) {  
                    PendingIntent pendingIntent = notification.allPendingIntents.valueAt(i);  
                    if (pendingIntent != null) {  
                        am.setPendingIntentWhitelistDuration(pendingIntent.getTarget(), duration);  
                    }  
                }  
            }  
        }  
      
        // Sanitize inputs  
        notification.priority = clamp(notification.priority, Notification.PRIORITY_MIN,  
                Notification.PRIORITY_MAX);  
      
        // setup local book-keeping  
        // 验证完条件后,将前面传递进来的Notification封装成一个StatusBarNotification对象,  
        final StatusBarNotification n = new StatusBarNotification(  
                pkg, opPkg, id, tag, callingUid, callingPid, 0, notification,  
                user);  
        // 封装NotificationRecord对象  
        final NotificationRecord r = new NotificationRecord(getContext(), n);  
        mHandler.post(new EnqueueNotificationRunnable(userId, r));  
      
        idOut[0] = id;  
    }  

开启线程,异步处理。

    private class EnqueueNotificationRunnable implements Runnable {  
        private final NotificationRecord r;  
        private final int userId;  
      
        EnqueueNotificationRunnable(int userId, NotificationRecord r) {  
            this.userId = userId;  
            this.r = r;  
        };  
      
        @Override  
        public void run() {  
      
            synchronized (mNotificationList) {  
                final StatusBarNotification n = r.sbn;  
                if (DBG) Slog.d(TAG, “EnqueueNotificationRunnable.run for: ” + n.getKey());  
                NotificationRecord old = mNotificationsByKey.get(n.getKey());  
                if (old != null) {  
                    // Retain ranking information from previous record  
                    r.copyRankingInformation(old);  
                }  
      
                final int callingUid = n.getUid();  
                final int callingPid = n.getInitialPid();  
                final Notification notification = n.getNotification();  
                final String pkg = n.getPackageName();  
                final int id = n.getId();  
                final String tag = n.getTag();  
                final boolean isSystemNotification = isUidSystem(callingUid) ||  
                        (“android”.equals(pkg));  
      
                // Handle grouped notifications and bail out early if we  
                // can to avoid extracting signals.  
                handleGroupedNotificationLocked(r, old, callingUid, callingPid);  
      
                // This conditional is a dirty hack to limit the logging done on  
                //     behalf of the download manager without affecting other apps.  
                if (!pkg.equals(“com.android.providers.downloads”)  
                        || Log.isLoggable(“DownloadManager”, Log.VERBOSE)) {  
                    int enqueueStatus = EVENTLOG_ENQUEUE_STATUS_NEW;  
                    if (old != null) {  
                        enqueueStatus = EVENTLOG_ENQUEUE_STATUS_UPDATE;  
                    }  
                    EventLogTags.writeNotificationEnqueue(callingUid, callingPid,  
                            pkg, id, tag, userId, notification.toString(),  
                            enqueueStatus);  
                }  
      
                mRankingHelper.extractSignals(r);  
      
                final boolean isPackageSuspended = isPackageSuspendedForUser(pkg, callingUid);  
      
                // blocked apps 判断pkg是否可以显示通知  
                if (r.getImportance() == NotificationListenerService.Ranking.IMPORTANCE_NONE  
                        || !noteNotificationOp(pkg, callingUid) || isPackageSuspended) {  
                    if (!isSystemNotification) {//不拦截系统通知  
                        if (isPackageSuspended) {  
                            Slog.e(TAG, “Suppressing notification from package due to package ”  
                                    + “suspended by administrator.”);  
                            mUsageStats.registerSuspendedByAdmin(r);  
                        } else {  
                            Slog.e(TAG, “Suppressing notification from package by user request.”);  
                            mUsageStats.registerBlocked(r);  
                        }  
                        return;  
                    }  
                }  
      
                // tell the ranker service about the notification  
                if (mRankerServices.isEnabled()) {  
                    mRankerServices.onNotificationEnqueued(r);  
                    // TODO delay the code below here for 100ms or until there is an answer  
                }  
      
                // 获取是否已经发送过此notification  
                int index = indexOfNotificationLocked(n.getKey());  
                if (index < 0) {  
                    // 如果是新发送的notification,就走新增流程.  
                    mNotificationList.add(r);  
                    mUsageStats.registerPostedByApp(r);  
                } else {  
                    //如果有发送过,就获取oldNtificationRecord,后面走更新流程 mStatusBar.updateNotification(r.statusBarKey, n)  
                    old = mNotificationList.get(index);  
                    mNotificationList.set(index, r);  
                    mUsageStats.registerUpdatedByApp(r, old);  
                    // Make sure we don’t lose the foreground service state.  
                    notification.flags |=  
                            old.getNotification().flags & Notification.FLAG_FOREGROUND_SERVICE;  
                    r.isUpdate = true;  
                }  
      
                Slog.d(TAG, “NotificationRecord, r = “+r);  
                mNotificationsByKey.put(n.getKey(), r);  
      
                // Ensure if this is a foreground service that the proper additional  
                // flags are set.  
                if ((notification.flags & Notification.FLAG_FOREGROUND_SERVICE) != 0) {  
                    notification.flags |= Notification.FLAG_ONGOING_EVENT  
                            | Notification.FLAG_NO_CLEAR;  
                }  
      
                applyZenModeLocked(r);  
                mRankingHelper.sort(mNotificationList);//将mNotificationList排序  
      
                // 如果notification设置了smallIcon,调用所有NotificationListeners的notifyPostedLocked方法,  
                // 通知有新的notification,传入的参数为上面封装成的StatusBarNotification对象.  
                if (notification.getSmallIcon() != null) {  
                    StatusBarNotification oldSbn = (old != null) ? old.sbn : null;  
                    mListeners.notifyPostedLocked(n, oldSbn);  
                } else {  
                    Slog.e(TAG, “Not posting notification without small icon: ” + notification);  
                    if (old != null && !old.isCanceled) {  
                        mListeners.notifyRemovedLocked(n);  
                    }  
                    // ATTENTION: in a future release we will bail out here  
                    // so that we do not play sounds, show lights, etc. for invalid  
                    // notifications  
                    Slog.e(TAG, “WARNING: In a future release this will crash the app: ”  
                            + n.getPackageName());  
                }  
      
                // buzzBeepBlinkLocked方法负责对消息进行处理。  
                // 通知status bar显示该notification,确认是否需要声音,震动和闪光,如果需要,那么就发出声音,震动和闪光  
                buzzBeepBlinkLocked(r);  
            }  
        }  
    }  

排序工作在mRankingHelper.sort(mNotificationList)中完成,跟进去看下

路径:frameworks/base/services/core/java/com/android/server/notification/RankingHelper.java
–》查看sort()方法 public void sort(ArrayList<NotificationRecord> notificationList) { … // rank each record individually Collections.sort(notificationList, mPreliminaryComparator);

synchronized (mProxyByGroupTmp) { // record individual ranking result and nominate proxies for each group for (int i = N – 1; i >= 0; i–) { final NotificationRecord record = notificationList.get(i); record.setAuthoritativeRank(i); final String groupKey = record.getGroupKey(); boolean isGroupSummary = record.getNotification().isGroupSummary(); if (isGroupSummary || !mProxyByGroupTmp.containsKey(groupKey)) { mProxyByGroupTmp.put(groupKey, record); } } // assign global sort key: // is_recently_intrusive:group_rank:is_group_summary:group_sort_key:rank for (int i = 0; i < N; i++) { final NotificationRecord record = notificationList.get(i); NotificationRecord groupProxy = mProxyByGroupTmp.get(record.getGroupKey()); String groupSortKey = record.getNotification().getSortKey();

// We need to make sure the developer provided group sort key (gsk) is handled // correctly: // gsk=”” < gsk=non-null-string < gsk=null // // We enforce this by using different prefixes for these three cases. String groupSortKeyPortion; if (groupSortKey == null) { groupSortKeyPortion = “nsk”; } else if (groupSortKey.equals(“”)) { groupSortKeyPortion = “esk”; } else { groupSortKeyPortion = “gsk=” + groupSortKey; }

boolean isGroupSummary = record.getNotification().isGroupSummary(); record.setGlobalSortKey( String.format(“intrsv=%c:grnk=0x%04x:gsmry=%c:%s:rnk=0x%04x”, record.isRecentlyIntrusive() ? ‘0’ : ‘1’, groupProxy.getAuthoritativeRank(), isGroupSummary ? ‘0’ : ‘1’, groupSortKeyPortion, record.getAuthoritativeRank())); } mProxyByGroupTmp.clear(); }

// Do a second ranking pass, using group proxies Collections.sort(notificationList, mFinalComparator); }

–》查看Collections.sort(notificationList, mPreliminaryComparator); 重点在于比较器mPreliminaryComparator,我们来看下代码 路径:frameworks/base/services/core/java/com/android/server/notification/NotificationComparator.java 进到里面的compare()方法来查看,以下就是具体的排序规则 public int compare(NotificationRecord left, NotificationRecord right) { final int leftImportance = left.getImportance(); final int rightImportance = right.getImportance(); if (leftImportance != rightImportance) { // by importance, high to low return -1 * Integer.compare(leftImportance, rightImportance); }

// Whether or not the notification can bypass DND. final int leftPackagePriority = left.getPackagePriority(); final int rightPackagePriority = right.getPackagePriority(); if (leftPackagePriority != rightPackagePriority) { // by priority, high to low return -1 * Integer.compare(leftPackagePriority, rightPackagePriority); }

final int leftPriority = left.sbn.getNotification().priority; final int rightPriority = right.sbn.getNotification().priority; if (leftPriority != rightPriority) { // by priority, high to low return -1 * Integer.compare(leftPriority, rightPriority); }

final float leftPeople = left.getContactAffinity(); final float rightPeople = right.getContactAffinity(); if (leftPeople != rightPeople) { // by contact proximity, close to far return -1 * Float.compare(leftPeople, rightPeople); }

// then break ties by time, most recent first return -1 * Long.compare(left.getRankingTimeMs(), right.getRankingTimeMs()); } 可以看到,比较器根据leftImportance ,leftPackagePriority ,leftPriority ,leftPeople 这四个参数做出排序,工作基本上算完成了。


    原文作者:Scott_go_on_Linux
    原文地址: https://blog.csdn.net/scott_go_on_linux/article/details/55505914
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞