Android 12.0 通知发送过程源码分析-Framework

发布于:2024-07-02 ⋅ 阅读:(15) ⋅ 点赞:(0)

以下NotificationManagerService简称 NMS

1. 通知的发送: NotificationManager.notify(int id, Notification notification) 开始.

源码路径: /frameworks/base/core/java/android/app/NotificationManager.java
 /**
     *发布通知以显示在状态栏中。 如果通知带有
     * 相同的 ID 已被您的应用程序发布且尚未被取消,它将被更新信息取代。
     *
     * @param id 此通知的标识符在您的系统中是唯一的应用。
     * @param notification  描述向用户显示的内容。 一定不为空。
     *        
     */
    public void notify(int id, Notification notification)
    {
        notify(null, id, notification);
    }

这里继续调用 notify(), 其中 tag = null;

源码路径: /frameworks/base/core/java/android/app/NotificationManager.java 
public void notify(String tag, int id, Notification notification)
    {
        notifyAsUser(tag, id, notification, mContext.getUser());
    }


 /**
     * @hide
     */
    @UnsupportedAppUsage
    public void notifyAsUser(String tag, int id, Notification notification, UserHandle user)
    {
        INotificationManager service = getService();//获取binder对象,实现跨进程通信
        String pkg = mContext.getPackageName(); //获取发送通知应用的包名

        try {
            //跨进程调用,即调用NMS中的enqueueNotificationWithTag(),请看分析4
            //在这之前会先调用fixNotification()方法,提前做一些优化,请看分析2
            service.enqueueNotificationWithTag(pkg, mContext.getOpPackageName(),tag, id,fixNotification(notification), user.getIdentifier())

        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

2.  优化通知 , fixNotification(notification) 代码如下:

源码路径: /frameworks/base/core/java/android/app/NotificationManager.java
 private Notification fixNotification(Notification notification) {
        String pkg = mContext.getPackageName();
        //这里把ApplicationInfo保存到Notificaiton.extras参数中, 请看2.(1)
        Notification.addFieldsFromContext(mContext, notification);

        //如果设置了通知铃声,这里获取铃声的uri
        if (notification.sound != null) {
            notification.sound = notification.sound.getCanonicalUri();
            if (StrictMode.vmFileUriExposureEnabled()) {
                notification.sound.checkFileUriExposed("Notification.sound");
            }

        }
        fixLegacySmallIcon(notification, pkg);//smallIcon版本兼容处理,请看2.(2)
        //Android 5.1 后要求必须设置setSmallIcon(),否则抛出异常
        if (mContext.getApplicationInfo().targetSdkVersion > Build.VERSION_CODES.LOLLIPOP_MR1) {
            if (notification.getSmallIcon() == null) {
                throw new IllegalArgumentException("Invalid notification (no valid small icon): "
                        + notification);
            }
        }

        notification.reduceImageSizes(mContext);//按比例压缩图片,请看2.(3)
        return Builder.maybeCloneStrippedForDelivery(notification);
    }

(1) 保存ApplicationInfo 对象到通知中,addFieldsFromContext() ,源码如下:

源码路径: /frameworks/base/core/java/android/app/Notification.java 
/**
     * @hide
     */
    public static void addFieldsFromContext(Context context, Notification notification) {
        addFieldsFromContext(context.getApplicationInfo(), notification);
    }

    /**
     * @hide
     */
    public static void addFieldsFromContext(ApplicationInfo ai, Notification notification) {
        //保存ApplicationInfo对象到通知中,属性名为EXTRA_BUILDER_APPLICATION_INFO
        notification.extras.putParcelable(EXTRA_BUILDER_APPLICATION_INFO, ai);
    }

(2) smallIcon版本兼容处理 

老版本中定义的通知smallIcon为资源int型,新版本中换成Icon 类型,为了兼容旧版本,这里做了转换,即把 int 型转化成Icon型,并设置到通知中,fixLegacySmallIcon(notification, pkg)源码如下:

源码路径: /frameworks/base/core/java/android/app/NotificationManager.java
private void fixLegacySmallIcon(Notification n, String pkg) {
        if (n.getSmallIcon() == null && n.icon != 0) {

           //n.setSmallIcon(Icon icon), 而 n.icon 为 int 型,这里调用了createWithResource()转换
            n.setSmallIcon(Icon.createWithResource(pkg, n.icon));
        }
    }



源码路径: frameworks/base/graphics/java/android/graphics/drawable/Icon.java

 /**
     * 创建Icon对象
     * @param resPackage 包名
     * @param resId 资源ID
     */
    public static Icon createWithResource(String resPackage, @DrawableRes int resId) {
        if (resPackage == null) {
            throw new IllegalArgumentException("Resource package name must not be null.");
        }
        final Icon rep = new Icon(TYPE_RESOURCE);
        rep.mInt1 = resId;
        rep.mString1 = resPackage;
        return rep;
    }

(3) 压缩图片 reduceImageSizes(mContext)

源码如下:

源码路径: /frameworks/base/core/java/android/app/Notification.java
/**
     * 把图片缩小成给定的尺寸
     * @hide
     */
    void reduceImageSizes(Context context) {
        if (extras.getBoolean(EXTRA_REDUCED_IMAGES)) {
            return;
        }
        boolean isLowRam = ActivityManager.isLowRamDeviceStatic();//判断设备是否为低内存
        if (mLargeIcon != null || largeIcon != null) {
            Resources resources = context.getResources();
            Class<? extends Style> style = getNotificationStyle();
            //不管是否为低内存,maxSize=48dp,源码定义在:/frameworks/base/core/res/res/values/dimens.xml
            int maxSize = resources.getDimensionPixelSize(isLowRam
                    ? R.dimen.notification_right_icon_size_low_ram
                    : R.dimen.notification_right_icon_size);
            if (mLargeIcon != null) {
                //压缩图片
                mLargeIcon.scaleDownIfNecessary(maxSize, maxSize);
            }
            if (largeIcon != null) {
               //压缩图片
                largeIcon = Icon.scaleDownIfNecessary(largeIcon, maxSize, maxSize);
            }
        }
        //对RemotView中的图片按规定的尺寸进行压缩
        reduceImageSizesForRemoteView(contentView, context, isLowRam);
        reduceImageSizesForRemoteView(headsUpContentView, context, isLowRam);
        reduceImageSizesForRemoteView(bigContentView, context, isLowRam);
        extras.putBoolean(EXTRA_REDUCED_IMAGES, true);
    }


/**
 *对RemotView中的图片按规定的尺寸进行压缩
*/
 private void reduceImageSizesForRemoteView(RemoteViews remoteView, Context context,
            boolean isLowRam) {
        if (remoteView != null) {
            Resources resources = context.getResources();
            int maxWidth = resources.getDimensionPixelSize(isLowRam
                    ? R.dimen.notification_custom_view_max_image_width_low_ram //294dp
                    : R.dimen.notification_custom_view_max_image_width);//450dp
            int maxHeight = resources.getDimensionPixelSize(isLowRam
                    ? R.dimen.notification_custom_view_max_image_height_low_ram //208dp
                    : R.dimen.notification_custom_view_max_image_height); //284dp
            remoteView.reduceImageSizes(maxWidth, maxHeight);
        }
    }



源码路径: frameworks/base/graphics/java/android/graphics/drawable/Icon.java

/**
     * 将位图缩小到给定的最大宽度和最大高度。 缩放将以统一的方式完成
     * @param bitmap 要缩小的位图
     * @param maxWidth 允许的最大宽度
     * @param maxHeight 允许的最大高度
     *
     * @如果需要则返回缩放后的位图,如果不需要缩放则返回原始位图
     * @hide
     */
    public static Bitmap scaleDownIfNecessary(Bitmap bitmap, int maxWidth, int maxHeight) {
        int bitmapWidth = bitmap.getWidth();
        int bitmapHeight = bitmap.getHeight();
        if (bitmapWidth > maxWidth || bitmapHeight > maxHeight) {
            float scale = Math.min((float) maxWidth / bitmapWidth,
                    (float) maxHeight / bitmapHeight);
            bitmap = Bitmap.createScaledBitmap(bitmap,
                    Math.max(1, (int) (scale * bitmapWidth)),
                    Math.max(1, (int) (scale * bitmapHeight)),
                    true /* filter */);
        }
        return bitmap;
    }

以上只是列举了压缩largeIcon 的例子,Notificaiton.java中,针对通知中的各种图片都做个指定尺寸的压缩.通知前期的优化完毕,继续看通知在NMS中的处理.

3. NMS 中保存通知的一些数据结构说明

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

    // 服务端维护的 已排序 的通知
    final ArrayList<NotificationRecord> mNotificationList = new ArrayList<>();
    
    // 服务端维护的 未排序 的通知
    final ArrayMap<String, NotificationRecord> mNotificationsByKey = new ArrayMap<>();
    
    // 入队通知: 保存所有入队的通知,当通知成功发送后则移除,即该列表记录的是所有入队成功且没有被发送出去的通知
    final ArrayList<NotificationRecord> mEnqueuedNotifications = new ArrayList<>();
   
    // 维护系统自动成组后的父通知
    final ArrayMap<Integer, ArrayMap<String, String>> mAutobundledSummaries = new ArrayMap<>();
 
   // 服务端根据groupKey,维护着所有用户主动成组的父通知 
   final ArrayMap<String, NotificationRecord> mSummaryByGroupKey = new ArrayMap<>();

4. 通知到达enqueueNotificationWithTag()@NMS

enqueueNotificationWithTag()里调用了 enqueueNotificationInternal(),所以直接从enqueueNotificationInternal()开始学习,源码如下:

(1) enqueueNotificationInternal()

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

void enqueueNotificationInternal(final String pkg, final String opPkg, final int callingUid,
            final int callingPid, final String tag, final int id, final Notification notification,
            int incomingUserId, boolean postSilently) {
                   
                      ......

        checkRestrictedCategories(notification);//检查通知是否属于仅限系统使用的类别类型,

        // 优化通知,请看4.(2)
        try {
            fixNotification(notification, pkg, tag, id, userId);
        } catch (Exception e) {
            if (notification.isForegroundService()) {
                throw new SecurityException("Invalid FGS notification", e);
            }
            Slog.e(TAG, "Cannot fix notification", e);
            return;
        }

        // 检查setForegroundService()是否有FLAG_FOREGROUND_SERVICE权限
        
        final ServiceNotificationPolicy policy = mAmi.applyForegroundServiceNotification(
                notification, tag, id, pkg, userId);
        if (policy == ServiceNotificationPolicy.UPDATE_ONLY) {
            if (!isNotificationShownInternal(pkg, tag, id, userId)) {
                reportForegroundServiceUpdate(false, notification, id, pkg, userId);
                return;
            }
        }

        mUsageStats.registerEnqueuedByApp(pkg);

        //把通知封装成StatusBarNotification对象,即一条通知对应一个StatusBarNotification对象,主要面对App端
        final StatusBarNotification n = new StatusBarNotification(
                pkg, opPkg, id, tag, notificationUid, callingPid, notification,
                user, null, System.currentTimeMillis());

        // 创建channelId,
        String channelId = notification.getChannelId();
        if (mIsTelevision && (new Notification.TvExtender(notification)).getChannelId() != null) {
            channelId = (new Notification.TvExtender(notification)).getChannelId();
        }
        String shortcutId = n.getShortcutId();

       //Android8.0之后就需要为通知设置Channel,这里做了判断,否则无法发送通知

        final NotificationChannel channel = mPreferencesHelper.getConversationNotificationChannel(
                pkg, notificationUid, channelId, shortcutId,
                true /* parent ok */, false /* includeDeleted */);
        if (channel == null) {
            final String noChannelStr = "No Channel found for "
                    + "pkg=" + pkg
                    + ", channelId=" + channelId
                    + ", id=" + id
                    + ", tag=" + tag
                    + ", opPkg=" + opPkg
                    + ", callingUid=" + callingUid
                    + ", userId=" + userId
                    + ", incomingUserId=" + incomingUserId
                    + ", notificationUid=" + notificationUid
                    + ", notification=" + notification;
            Slog.e(TAG, noChannelStr);

            //获取通知的重要性
            boolean appNotificationsOff = mPreferencesHelper.getImportance(pkg, notificationUid)
                    == NotificationManager.IMPORTANCE_NONE;

            if (!appNotificationsOff) {
                doChannelWarningToast("Developer warning for package \"" + pkg + "\"\n" +
                        "Failed to post notification on channel \"" + channelId + "\"\n" +
                        "See log for more details");
            }
            return;
        }
      
        //把通知封装成NotificationRecord对象,即一条通知就是一个NotificationRecord对象,主要面对Service端
        final NotificationRecord r = new NotificationRecord(getContext(), n, channel);
        r.setIsAppImportanceLocked(mPreferencesHelper.getIsAppImportanceLocked(pkg, callingUid));
        r.setPostSilently(postSilently);
        r.setFlagBubbleRemoved(false);
        r.setPkgAllowedAsConvo(mMsgPkgsAllowedAsConvos.contains(pkg));

        if ((notification.flags & Notification.FLAG_FOREGROUND_SERVICE) != 0) {
            final boolean fgServiceShown = channel.isFgServiceShown();
            if (((channel.getUserLockedFields() & NotificationChannel.USER_LOCKED_IMPORTANCE) == 0
                        || !fgServiceShown)
                    && (r.getImportance() == IMPORTANCE_MIN
                            || r.getImportance() == IMPORTANCE_NONE)) {
                //提高通知的重要性
                if (TextUtils.isEmpty(channelId)
                        || NotificationChannel.DEFAULT_CHANNEL_ID.equals(channelId)) {
                    r.setSystemImportance(IMPORTANCE_LOW);
                } else {
                    channel.setImportance(IMPORTANCE_LOW);
                    r.setSystemImportance(IMPORTANCE_LOW);
                    if (!fgServiceShown) {
                        channel.unlockFields(NotificationChannel.USER_LOCKED_IMPORTANCE);
                        channel.setFgServiceShown(true);
                    }
                    mPreferencesHelper.updateNotificationChannel(
                            pkg, notificationUid, channel, false);
                    r.updateNotificationChannel(channel);
                }
            } else if (!fgServiceShown && !TextUtils.isEmpty(channelId)
                    && !NotificationChannel.DEFAULT_CHANNEL_ID.equals(channelId)) {
                channel.setFgServiceShown(true);
                r.updateNotificationChannel(channel);
            }
        }

        ShortcutInfo info = mShortcutHelper != null
                ? mShortcutHelper.getValidShortcutInfo(notification.getShortcutId(), pkg, user)
                : null;
        if (notification.getShortcutId() != null && info == null) {
            Slog.w(TAG, "notification " + r.getKey() + " added an invalid shortcut");
        }
        r.setShortcutInfo(info);
        r.setHasSentValidMsg(mPreferencesHelper.hasSentValidMsg(pkg, notificationUid));
        r.userDemotedAppFromConvoSpace(
                mPreferencesHelper.hasUserDemotedInvalidMsgApp(pkg, notificationUid));

       //进一步过滤不符合规定的通知,限制通知速率和通知数量,请看4.(3)
        if (!checkDisqualifyingFeatures(userId, notificationUid, id, tag, r,
                r.getSbn().getOverrideGroupKey() != null)) {
            return;
        }

        if (info != null) {
            // 缓存快捷方式
            mShortcutHelper.cacheShortcut(info, user);
        }

        // 暂时允许应用程序在启动待处理意图时执行额外的工作,
        if (notification.allPendingIntents != null) {
            final int intentCount = notification.allPendingIntents.size();
            if (intentCount > 0) {
                final long duration = LocalServices.getService(
                        DeviceIdleInternal.class).getNotificationAllowlistDuration();
                for (int i = 0; i < intentCount; i++) {
                    PendingIntent pendingIntent = notification.allPendingIntents.valueAt(i);
                    if (pendingIntent != null) {
                        mAmi.setPendingIntentAllowlistDuration(pendingIntent.getTarget(),
                                ALLOWLIST_TOKEN, duration,
                                TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED,
                                REASON_NOTIFICATION_SERVICE,
                                "NotificationManagerService");
                        mAmi.setPendingIntentAllowBgActivityStarts(pendingIntent.getTarget(),
                                ALLOWLIST_TOKEN, (FLAG_ACTIVITY_SENDER | FLAG_BROADCAST_SENDER
                                        | FLAG_SERVICE_SENDER));
                    }
                }
            }
        }

        // 需要升级权限才能获得包重要性
        final long token = Binder.clearCallingIdentity();
        boolean isAppForeground;
        try {
            isAppForeground = mActivityManager.getPackageImportance(pkg) == IMPORTANCE_FOREGROUND;
        } finally {
            Binder.restoreCallingIdentity(token);
        }
        //经过上面的一步一步过滤后,现在通知post到线程里,请看5分析
        mHandler.post(new EnqueueNotificationRunnable(userId, r, isAppForeground));
    }

  

(2) 第二次优化通知, fixNotification(notification, pkg, tag, id, userId)

 源码路径: frameworks/base/services/core/java/com/android/server/notification/NotificationManagerService.java
 
protected void fixNotification(Notification notification, String pkg, String tag, int id,
            int userId) throws NameNotFoundException {
        final ApplicationInfo ai = mPackageManagerClient.getApplicationInfoAsUser(
                pkg, PackageManager.MATCH_DEBUG_TRIAGED_MISSING,
                (userId == UserHandle.USER_ALL) ? USER_SYSTEM : userId);
       //保存ApplicationInfo对象,请看分析2.(2)
        Notification.addFieldsFromContext(ai, notification);

        //检查权限,通知是否能着色,即通知中的 setColorized(boolean)
        int canColorize = mPackageManagerClient.checkPermission(
                android.Manifest.permission.USE_COLORIZED_NOTIFICATIONS, pkg);
        if (canColorize == PERMISSION_GRANTED) {
            notification.flags |= Notification.FLAG_CAN_COLORIZE;
        } else {
            notification.flags &= ~Notification.FLAG_CAN_COLORIZE;
        }

        //检查全屏通知的权限,如果在Android Q(29)及以上给通知设置了fullScreenIntent,同时还
        //需要设置android.Manifest.permission.USE_FULL_SCREEN_INTENT权限,否则通知的
        //fullScreenIntent将被系统始终为null,即无效      
        if (notification.fullScreenIntent != null && ai.targetSdkVersion >= Build.VERSION_CODES.Q) {
            int fullscreenIntentPermission = mPackageManagerClient.checkPermission(
                    android.Manifest.permission.USE_FULL_SCREEN_INTENT, pkg);
            if (fullscreenIntentPermission != PERMISSION_GRANTED) {
                //权限不足,该属性设置为null
                notification.fullScreenIntent = null;
                //fullScreenIntent无效日志
                Slog.w(TAG, "Package " + pkg +
                        ": Use of fullScreenIntent requires the USE_FULL_SCREEN_INTENT permission");
            }
        }

        // 检查 Style 样式中的action事件
        if (notification.isStyle(Notification.CallStyle.class)) {
            Notification.Builder builder =
                    Notification.Builder.recoverBuilder(getContext(), notification);
            Notification.CallStyle style = (Notification.CallStyle) builder.getStyle();
            List<Notification.Action> actions = style.getActionsListWithSystemActions();
            notification.actions = new Notification.Action[actions.size()];
            actions.toArray(notification.actions);
        }

        // 检查RemoteView中的contentView,bigcontentView,headsUpContentView等是否超过
        checkRemoteViews(pkg, tag, id, notification);
    }


/**
 * 检查RemouteView 的大小,是否超过了指定的大小
*/

 private boolean removeRemoteView(String pkg, String tag, int id, RemoteViews contentView) {
        if (contentView == null) {
            return false;
        }
        //获取当前RemoteView的大小
        final int contentViewSize = contentView.estimateMemoryUsage();
        //其中 mWarnRemoteViewsSizeBytes = 2000000 bytes , mStripRemoteViewsSizeBytes = 5000000 bytes
        if (contentViewSize > mWarnRemoteViewsSizeBytes
                && contentViewSize < mStripRemoteViewsSizeBytes) {
            Slog.w(TAG, "RemoteViews too large on pkg: " + pkg + " tag: " + tag + " id: " + id
                    + " this might be stripped in a future release");
        }
         // contentViewSize >= 5000000 bytes
        if (contentViewSize >= mStripRemoteViewsSizeBytes) {
            mUsageStats.registerImageRemoved(pkg);
            Slog.w(TAG, "Removed too large RemoteViews (" + contentViewSize + " bytes) on pkg: "
                    + pkg + " tag: " + tag + " id: " + id);
            return true;
        }
        return false;
    }

(3) 限制通知速率和通知数量: checkDisqualifyingFeatures()

 源码路径: /frameworks/base/services/core/java/com/android/server/notification/NotificationManagerService.java
涉及的源码路径:
速率的计算: /frameworks/base/services/core/java/com/android/server/notification/RateEstimator.java
保存不发送的通知:/frameworks/base/services/core/java/com/android/server/notification/NotificationUsageStats.java


 /**
     * 检查是否可以发布通知。 检查速率限制器、暂停助手和阻止。
     * 如果通知检查不合格,则返回 false,
     * 应用速率不能超过5000毫秒,通知总数不能超过50条
     */
    boolean checkDisqualifyingFeatures(int userId, int uid, int id, String tag,
            NotificationRecord r, boolean isAutogroup) {
        Notification n = r.getNotification();
        final String pkg = r.getSbn().getPackageName();
        //是否为系统通知
        final boolean isSystemNotification =
                isUidSystemOrPhone(uid) || ("android".equals(pkg));
        //是否为通知监听器
        final boolean isNotificationFromListener = mListeners.isListenerPackage(pkg);

        // 限制除 android 之外的任何给定包的通知数量
        if (!isSystemNotification && !isNotificationFromListener) {
            final int callingUid = Binder.getCallingUid();
            if (mNotificationsByKey.get(r.getSbn().getKey()) == null
                    && isCallerInstantApp(callingUid, userId)) {
                
                 // 临时应用程序对通知有一些特殊的限制。
                 // 他们不被允许创建新的通知,但是他们被允许
                 // 更新系统创建的通知(例如前台服务通知)。
                throw new SecurityException("Instant app " + pkg
                        + " cannot create notifications");
            }

            //限制更新未完成进度通知(即:进度条通知还在更新进度,当前速度还未达到最大值)的速率,
            if (mNotificationsByKey.get(r.getSbn().getKey()) != null
                    && !r.getNotification().hasCompletedProgress()
                    && !isAutogroup) {
                //算出这条通知距离上一个通知的时间差,然后算出速率,过程请看下面文字分析
                final float appEnqueueRate = mUsageStats.getAppEnqueueRate(pkg);
                //如果这个通知的速率大于规定的最大值,其中mMaxPackageEnqueueRate=5f
                if (appEnqueueRate > mMaxPackageEnqueueRate) {
                    //把违规超速率的通知数量做好统计,保存在NotificationUsageStats.java中
                    mUsageStats.registerOverRateQuota(pkg);
                    final long now = SystemClock.elapsedRealtime();
                   //这条通知的时间-上条通知的时间 > 5000 毫秒
                    if ((now - mLastOverRateLogTime) > MIN_PACKAGE_OVERRATE_LOG_INTERVAL) {
                        Slog.e(TAG, "Package enqueue rate is " + appEnqueueRate
                                + ". Shedding " + r.getSbn().getKey() + ". package=" + pkg);
                        mLastOverRateLogTime = now;
                    }
                    return false;//速率不合格,直接返回false
                }
            }

            // 限制应用程序可以拥有的非前台服务 未完成通知记录的数量
            if (!n.isForegroundService()) {
                //计算应用通知的总数,该总数:发送成功的通知+发送不成功的通知
                int count = getNotificationCount(pkg, userId, id, tag);
                // 应用总通知数 >= 50 条
                if (count >= MAX_PACKAGE_NOTIFICATIONS) {
                   //把超出总数的通知保存在NotificationUsageStats.java中
                    mUsageStats.registerOverCountQuota(pkg);
                    Slog.e(TAG, "Package has already posted or enqueued " + count
                            + " notifications.  Not showing more.  package=" + pkg);
                    return false;//通知总数不合格,直接返回false
                }
            }
        }

        // 气泡或内联回复是不可变的?
        if (n.getBubbleMetadata() != null
                && n.getBubbleMetadata().getIntent() != null
                && hasFlag(mAmi.getPendingIntentFlags(
                        n.getBubbleMetadata().getIntent().getTarget()),
                        PendingIntent.FLAG_IMMUTABLE)) {
            throw new IllegalArgumentException(r.getKey() + " Not posted."
                    + " PendingIntents attached to bubbles must be mutable");
        }

        if (n.actions != null) {
            for (Notification.Action action : n.actions) {
                if ((action.getRemoteInputs() != null || action.getDataOnlyRemoteInputs() != null)
                        && hasFlag(mAmi.getPendingIntentFlags(action.actionIntent.getTarget()),
                        PendingIntent.FLAG_IMMUTABLE)) {
                    throw new IllegalArgumentException(r.getKey() + " Not posted."
                            + " PendingIntents attached to actions with remote"
                            + " inputs must be mutable");
                }
            }
        }

        if (r.getSystemGeneratedSmartActions() != null) {
            for (Notification.Action action : r.getSystemGeneratedSmartActions()) {
                if ((action.getRemoteInputs() != null || action.getDataOnlyRemoteInputs() != null)
                        && hasFlag(mAmi.getPendingIntentFlags(action.actionIntent.getTarget()),
                        PendingIntent.FLAG_IMMUTABLE)) {
                    throw new IllegalArgumentException(r.getKey() + " Not posted."
                            + " PendingIntents attached to contextual actions with remote inputs"
                            + " must be mutable");
                }
            }
        }

        if (n.isStyle(Notification.CallStyle.class)) {
            boolean isForegroundService = (n.flags & FLAG_FOREGROUND_SERVICE) != 0;
            boolean hasFullScreenIntent = n.fullScreenIntent != null;
            if (!isForegroundService && !hasFullScreenIntent) {
                throw new IllegalArgumentException(r.getKey() + " Not posted."
                        + " CallStyle notifications must either be for a foreground Service or"
                        + " use a fullScreenIntent.");
            }
        }

        // 不发送snoozed类型的通知,当用户在设置中设置了不允许显示某个应用的通知(blocked)时,不再发送
        if (mSnoozeHelper.isSnoozed(userId, pkg, r.getKey())) {
            MetricsLogger.action(r.getLogMaker()
                    .setType(MetricsProto.MetricsEvent.TYPE_UPDATE)
                    .setCategory(MetricsProto.MetricsEvent.NOTIFICATION_SNOOZED));
            mNotificationRecordLogger.log(
                    NotificationRecordLogger.NotificationEvent.NOTIFICATION_NOT_POSTED_SNOOZED,
                    r);
            if (DBG) {
                Slog.d(TAG, "Ignored enqueue for snoozed notification " + r.getKey());
            }
            mSnoozeHelper.update(userId, r);
            handleSavePolicyFile();
            return false;
        }


        // blocked apps
        if (isBlocked(r, mUsageStats)) {
            return false;
        }

        return true;
    }

5 . EnqueueNotificationRunnable@NMS

到此,通知经过优化后,最终进入到线程,下面是该线程的run() 方法 ,源码如下:

 源码路径: /frameworks/base/services/core/java/com/android/server/notification/NotificationManagerService.java 
public void run() {
            synchronized (mNotificationLock) {
                final Long snoozeAt =
                        mSnoozeHelper.getSnoozeTimeForUnpostedNotification(
                                r.getUser().getIdentifier(),
                                r.getSbn().getPackageName(), r.getSbn().getKey());
                final long currentTime = System.currentTimeMillis();
                if (snoozeAt.longValue() > currentTime) {
                    (new SnoozeNotificationRunnable(r.getSbn().getKey(),
                            snoozeAt.longValue() - currentTime, null)).snoozeLocked(r);
                    return;
                }

                final String contextId =
                        mSnoozeHelper.getSnoozeContextForUnpostedNotification(
                                r.getUser().getIdentifier(),
                                r.getSbn().getPackageName(), r.getSbn().getKey());
                if (contextId != null) {
                    (new SnoozeNotificationRunnable(r.getSbn().getKey(),
                            0, contextId)).snoozeLocked(r);
                    return;
                }
         
               //把通知添加到List中,即入队通知,指入队但未发送出去的通知,分析请看3
                mEnqueuedNotifications.add(r);
                scheduleTimeoutLocked(r);

                final StatusBarNotification n = r.getSbn();
                
                NotificationRecord old = mNotificationsByKey.get(n.getKey());
                //查看通知List中,是否已经存在该通知(通知的唯一标识为key),
                if (old != null) {
                    // 保留以前记录的排名信息
                    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();

                // 更新气泡通知
                updateNotificationBubbleFlags(r, isAppForeground);

                // 处理分组通知,详细介绍请看分析9
                handleGroupedNotificationLocked(r, old, callingUid, callingPid);

                if (n.isGroup() && notification.isGroupChild()) {
                    mSnoozeHelper.repostGroupSummary(pkg, r.getUserId(), n.getGroupKey());
                }

               
                if (mAssistants.isEnabled()) {
                    mAssistants.onNotificationEnqueuedLocked(r);
                    //处理完之后,延迟post到PostNotificationRunnable线程,请看分析6
                    mHandler.postDelayed(new PostNotificationRunnable(r.getKey()),
                            DELAY_FOR_ASSISTANT_TIME);
                } else {
                   //处理完之后,post到PostNotificationRunnable线程,请看分析6
                    mHandler.post(new PostNotificationRunnable(r.getKey()));
                }
            }
        }
    }

上面是把通知添加到 ArrayList<NotificationRecord> mEnqueuedNotifications 列表中,该列表保存了所有待处理的通知,如果通知被取消、超时、处理完成后也会从该列表移除.

6. PostNotificationRunnable@NMS

(1) 继续分析 PostNotificationRunnable 的 run() 方法,该方法主要是通知发送前的一些处理,

 源码路径: /frameworks/base/services/core/java/com/android/server/notification/NotificationManagerService.java
 public void run() {
            synchronized (mNotificationLock) {
                try {
                    NotificationRecord r = null;
                    int N = mEnqueuedNotifications.size();
                   //遍历待处理通知列表,如果传递过来的key能在列表中存在,则把通知赋值给r,
                    for (int i = 0; i < N; i++) {
                        final NotificationRecord enqueued = mEnqueuedNotifications.get(i);
                        if (Objects.equals(key, enqueued.getKey())) {
                            r = enqueued;
                            break;
                        }
                    }
                   //如果列表中不存在该key通知,就return
                    if (r == null) {
                        return;
                    }
                   //如果用户设置了不接收该通知,也return 
                    if (isBlocked(r)) {
                        return;
                    }
                   //判断应用是否被系统限制了,即应用程序当前是否已暂停。
                    final boolean isPackageSuspended =
                            isPackagePausedOrSuspended(r.getSbn().getPackageName(), r.getUid());
                    r.setHidden(isPackageSuspended);
                    if (isPackageSuspended) {
                        //统计被限制的通知的数量
                        mUsageStats.registerSuspendedByAdmin(r);
                    }
                    NotificationRecord old = mNotificationsByKey.get(key);
                    final StatusBarNotification n = r.getSbn();
                    final Notification notification = n.getNotification();

                    if (old == null || old.getSbn().getInstanceId() == null) {
                        n.setInstanceId(mNotificationInstanceIdSequence.newInstanceId());
                    } else {
                        n.setInstanceId(old.getSbn().getInstanceId());
                    }
                    
                    //判断通知是新的,还是已存在的通知,主要是通过遍历 待处理通知列表,如果存在则返回通知在列表的位置,如果是新的通知,则返回-1
                    int index = indexOfNotificationLocked(n.getKey());
                    if (index < 0) {
                       //将新的通知添加到 mNotificaitonList 列表中,
                        mNotificationList.add(r);
                        mUsageStats.registerPostedByApp(r);
                        r.setInterruptive(isVisuallyInterruptive(null, r));
                    } else {
                       //如果已存在该通知,则更新已存在的通知,即更新通知内容,key值不变,通知排序也不变
                        old = mNotificationList.get(index);  
                        mNotificationList.set(index, r);
                        mUsageStats.registerUpdatedByApp(r, old);
                        //确保通知更新过程中前台服务标志丢失
                        notification.flags |=
                                old.getNotification().flags & FLAG_FOREGROUND_SERVICE;
                        r.isUpdate = true;
                        final boolean isInterruptive = isVisuallyInterruptive(old, r);
                        r.setTextChanged(isInterruptive);
                        r.setInterruptive(isInterruptive);
                    }
                    //把通知添加到 列表中,这个列表在后面有说明
                    mNotificationsByKey.put(n.getKey(), r);

                    //如果是前台服务通知,不管应用是否设置常驻标志,系统都会强制加上FLAG_ONGOING_EVENT(常驻通知) 和 FLAG_NO_CLEAR(用户手动无法清除) 标志,
                    if ((notification.flags & FLAG_FOREGROUND_SERVICE) != 0) {
                        notification.flags |= FLAG_ONGOING_EVENT
                                | FLAG_NO_CLEAR;
                    }

                    mRankingHelper.extractSignals(r);
                    mRankingHelper.sort(mNotificationList);
                    final int position = mRankingHelper.indexOf(mNotificationList, r);

                    int buzzBeepBlinkLoggingCode = 0;
                    if (!r.isHidden()) {
                       //处理通知的震动,音效和呼吸灯
                        buzzBeepBlinkLoggingCode = buzzBeepBlinkLocked(r);
                    }

                    if (notification.getSmallIcon() != null) {
                        StatusBarNotification oldSbn = (old != null) ? old.getSbn() : null;
                       //*****发送通知,通知各个listeners,其中就包括了SystemUI,详情请看分析7
                        mListeners.notifyPostedLocked(r, old);
                        if ((oldSbn == null || !Objects.equals(oldSbn.getGroup(), n.getGroup()))
                                && !isCritical(r)) {
                            mHandler.post(new Runnable() {
                                @Override
                                public void run() { 
                                    //构建父通知
                                    mGroupHelper.onNotificationPosted(
                                            n, hasAutoGroupSummaryLocked(n));
                                }
                            });
                        } else if (oldSbn != null) {
                            final NotificationRecord finalRecord = r;
                            mHandler.post(() -> mGroupHelper.onNotificationUpdated(
                                    finalRecord.getSbn(), hasAutoGroupSummaryLocked(n)));
                        }
                    } else {
                       //由于没有设置smallIcon,通知无法发送,通知listeners移除该通知.
                        if (old != null && !old.isCanceled) {
                            mListeners.notifyRemovedLocked(r,
                                    NotificationListenerService.REASON_ERROR, r.getStats());
                            mHandler.post(new Runnable() {
                                @Override
                                public void run() {
                                    mGroupHelper.onNotificationRemoved(n);
                                }
                            });
                        }
                    }

                    if (mShortcutHelper != null) {
                        mShortcutHelper.maybeListenForShortcutChangesForBubbles(r,
                                false /* isRemoved */,
                                mHandler);
                    }

                    maybeRecordInterruptionLocked(r);
                    maybeRegisterMessageSent(r);
                    maybeReportForegroundServiceUpdate(r, true);
                } finally {
                   //该通知已被处理,应该把该通知从 待处理通知列表中移除
                    int N = mEnqueuedNotifications.size();
                    for (int i = 0; i < N; i++) {
                        final NotificationRecord enqueued = mEnqueuedNotifications.get(i);
                        if (Objects.equals(key, enqueued.getKey())) {
                            mEnqueuedNotifications.remove(i);
                            break;
                        }
                    }
                }
            }
        }
    

        这里从待处理通知 ArrayList<Notification> mEnqueuednotifications 取出通知,经过一些列步骤,之后把该通知添加到列表 ArrayMap<String,NotificationRecord> mNotificationsByKey 中, 该列表保存了服务端中未排序的所有通知,用于确定该通知是更新旧通知还是新类型的通知.最后, mListeners.notifyPostedLocked(r, old); 通知各个监听通知的listeners 通知更新了, 其中 mListeners 指 NotificationListeners, 它是NotificationManagerService的内部类,下面继续分析.

7. 通知监听者,通知发生变化: mListeners.notifyPostedLocked() 

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


private NotificationListeners mListeners; 

 
public class NotificationListeners extends ManagedServices {

                      ......

 private void notifyPostedLocked(NotificationRecord r, NotificationRecord old,
                boolean notifyAllListeners) {
   try {
        StatusBarNotification sbn = r.getSbn();
        StatusBarNotification oldSbn = (old != null) ? old.getSbn() : null;
        TrimCache trimCache = new TrimCache(sbn);

        //过滤部分listener,如:不可见用户,Android P 以下hidden类型的通知 
         for (final ManagedServiceInfo info : getServices()) {
             boolean sbnVisible = isVisibleToListener(sbn, r. getNotificationType(), info);
             boolean oldSbnVisible = (oldSbn != null)
                    && isVisibleToListener(oldSbn, old.getNotificationType(), info);
       //如果通知不可见,则忽略
        if (!oldSbnVisible && !sbnVisible) {
             continue;
         }
       
         if (r.isHidden() && info.targetSdkVersion < Build.VERSION_CODES.P) {
            continue;
          }

         //过滤不通知所有监听者,并且版本大于Android P 
        if (!notifyAllListeners && info.targetSdkVersion >= Build.VERSION_CODES.P) {
                        continue;
        }

        //构建通知映射表,分析请看分析8
        final NotificationRankingUpdate update = makeRankingUpdateLocked(info);

        // 移除旧以前可见,现在不可见的通知
                    if (oldSbnVisible && !sbnVisible) {
                        final StatusBarNotification oldSbnLightClone = oldSbn.cloneLight();
                        mHandler.post(() -> notifyRemoved(
                                info, oldSbnLightClone, update, null, REASON_USER_STOPPED));
                        continue;
                    }

                    //授权
                    final int targetUserId = (info.userid == UserHandle.USER_ALL)
                            ? UserHandle.USER_SYSTEM : info.userid;
                    updateUriPermissions(r, old, info.component.getPackageName(), targetUserId);

                    final StatusBarNotification sbnToPost = trimCache.ForListener(info);
                    //通知各个监听器,之后各个监听器就能收到通知,并对通知做处理了
                    mHandler.post(() -> notifyPosted(info, sbnToPost, update));
                }
            } catch (Exception e) {
                Slog.e(TAG, "Could not notify listeners for " + r.getKey(), e);
            }
        }

到此, 通过  mHandler.post(() -> notifyPosted(info, sbnToPost, update)) 方法将通知传递到各个监听器,其中,在发送通知给监听器之前,会对通知进行排序,然后构建通知Map, SystemUI 会根据这个map 对通知进行排序.

8. 通知发送前对通知进行排序

    /**
     * 仅对监听器可见的通知进行排序,构建通知map,
     * key = StatusBarNotification.getKey();
     * value = NotificationListenerService.Ranking
     */
    @GuardedBy("mNotificationLock")
    NotificationRankingUpdate makeRankingUpdateLocked(ManagedServiceInfo info) {
        final int N = mNotificationList.size();
        final ArrayList<NotificationListenerService.Ranking> rankings = new ArrayList<>();

        for (int i = 0; i < N; i++) {
            NotificationRecord record = mNotificationList.get(i);
            if (isInLockDownMode(record.getUser().getIdentifier())) {
                continue;
            }
            //过滤掉当前用户不可见的通知
            if (!isVisibleToListener(record.getSbn(), record.getNotificationType(), info)) {
                continue;
            }
            //获取通知关键字key
            final String key = record.getSbn().getKey();
            //根据每个关键字对应一个 NotificationListenerService.Ranking, 即构成通知ArrayMap
            final NotificationListenerService.Ranking ranking =
                    new NotificationListenerService.Ranking();
            //将通知的关键信息添加到ranking中
            ranking.populate(
                    key,
                    rankings.size(),
                    !record.isIntercepted(),
                    record.getPackageVisibilityOverride(),
                    record.getSuppressedVisualEffects(),
                    record.getImportance(),
                    record.getImportanceExplanation(),
                    record.getSbn().getOverrideGroupKey(),
                    record.getChannel(),
                    record.getPeopleOverride(),
                    record.getSnoozeCriteria(),
                    record.canShowBadge(),
                    record.getUserSentiment(),
                    record.isHidden(),
                    record.getLastAudiblyAlertedMs(),
                    record.getSound() != null || record.getVibration() != null,
                    record.getSystemGeneratedSmartActions(),
                    record.getSmartReplies(),
                    record.canBubble(),
                    record.isTextChanged(),
                    record.isConversation(),
                    record.getShortcutInfo(),
                    record.getRankingScore() == 0
                            ? RANKING_UNCHANGED
                            : (record.getRankingScore() > 0 ?  RANKING_PROMOTED : RANKING_DEMOTED),
                    record.getNotification().isBubbleNotification(),
                    record.getProposedImportance()
            );
            rankings.add(ranking);
        }

        return new NotificationRankingUpdate(
                rankings.toArray(new NotificationListenerService.Ranking[0]));
    }

9. 通知的分组

通知组简介

继续分析 4标题中 handleGroupedNotificationLocked() 系统处理分组的源码如下:

    /**
     * 确保分组通知得到特殊处理
     *
     * 如果新通知导致组丢失其摘要,则取消组子项。
     *
     * <p>Updates mSummaryByGroupKey.</p>
     */
    @GuardedBy("mNotificationLock")
    private void handleGroupedNotificationLocked(NotificationRecord r, NotificationRecord old,
            int callingUid, int callingPid) {
        StatusBarNotification sbn = r.getSbn();
        Notification n = sbn.getNotification();
        if (n.isGroupSummary() && !sbn.isAppGroup())  {
            // 没有组的通知不应该是摘要,否则自动成组可能会导致错误,分析请看9.(1)
            n.flags &= ~Notification.FLAG_GROUP_SUMMARY;
        }

        String group = sbn.getGroupKey();
        boolean isSummary = n.isGroupSummary();

        Notification oldN = old != null ? old.getSbn().getNotification() : null;
        String oldGroup = old != null ? old.getSbn().getGroupKey() : null;
        boolean oldIsSummary = old != null && oldN.isGroupSummary();
        //更新 mSummaryByGroupKey,分析请看3
        if (oldIsSummary) {
            NotificationRecord removedSummary = mSummaryByGroupKey.remove(oldGroup);
            if (removedSummary != old) {
                String removedKey =
                        removedSummary != null ? removedSummary.getKey() : "<null>";
                Slog.w(TAG, "Removed summary didn't match old notification: old=" + old.getKey() +
                        ", removed=" + removedKey);
            }
        }
        if (isSummary) {
            mSummaryByGroupKey.put(group, r);
        }

        FlagChecker childrenFlagChecker = (flags) -> {
            if ((flags & FLAG_FOREGROUND_SERVICE) != 0) {
                return false;
            }
            return true;
        };

        // 如果更新导致组摘要消失,则清除旧通知的组子项。当旧通知是摘要而新通知不是摘要时,
        // 或者当旧通知是摘要并且其groupKey发生更改时,则原来父通知下的所有子通知会被移除
        if (oldIsSummary && (!isSummary || !oldGroup.equals(group))) {
            cancelGroupChildrenLocked(old, callingUid, callingPid, null, false /* sendDelete */,
                    childrenFlagChecker, REASON_APP_CANCEL, SystemClock.elapsedRealtime());
        }
    }

(1) 如果 setGroupSummary(boolean isGroupSummary)设置了Notification.FLAG_GROUP_SUMMARY这个flag,但是没有调用setGroup(String groupKey)设置对应的groupKey, 则Notification.FLAG_GROUP_SUMMARY这个flag会被去掉,否则会导致后续系统的自动成组导致出错。

10. 使用规则更新通知属性值(排序前更新)

源码路径:frameworks/base/services/core/java/com/android/server/notification/RankingConfig.java

public interface RankingConfig {

    void setImportance(String packageName, int uid, int importance);
    int getImportance(String packageName, int uid);
    void setShowBadge(String packageName, int uid, boolean showBadge);
    boolean canShowBadge(String packageName, int uid);
    boolean badgingEnabled(UserHandle userHandle);
    int getBubblePreference(String packageName, int uid);
    boolean bubblesEnabled(UserHandle userHandle);
    boolean isMediaNotificationFilteringEnabled();
    boolean isGroupBlocked(String packageName, int uid, String groupId);
    boolean canShowNotificationsOnLockscreen(int userId);
    boolean canShowPrivateNotificationsOnLockScreen(int userId);

    Collection<NotificationChannelGroup> getNotificationChannelGroups(String pkg,int uid);

    void createNotificationChannelGroup(String pkg, int uid, NotificationChannelGroup group,boolean fromTargetApp);

    ParceledListSlice<NotificationChannelGroup> getNotificationChannelGroups(String pkg, int uid, boolean includeDeleted, boolean includeNonGrouped, boolean includeEmpty);

    boolean createNotificationChannel(String pkg, int uid, NotificationChannel channel, boolean fromTargetApp, boolean hasDndAccess);

    void updateNotificationChannel(String pkg, int uid, NotificationChannel channel,
            boolean fromUser);

    NotificationChannel getNotificationChannel(String pkg, int uid, String channelId,
            boolean includeDeleted);

    NotificationChannel getConversationNotificationChannel(String pkg, int uid, String channelId, String conversationId,  boolean returnParentIfNoConversationChannel,boolean includeDeleted);

    boolean deleteNotificationChannel(String pkg, int uid, String channelId);

    void permanentlyDeleteNotificationChannel(String pkg, int uid, String channelId);

    void permanentlyDeleteNotificationChannels(String pkg, int uid);

    ParceledListSlice<NotificationChannel> getNotificationChannels(String pkg, int uid,boolean includeDeleted);
}

上面是规则接口类,下面分析该接口的实现类,举例通知圆点进行说明:

源码路径: frameworks/base/services/core/java/com/android/server/notification/PreferencesHelper.java

public class PreferencesHelper implements RankingConfig {
    ......
   
    @Override
    public boolean canShowBadge(String packageName, int uid) {
        synchronized (mPackagePreferences) {
            return getOrCreatePackagePreferencesLocked(packageName, uid).showBadge;
        }
    }

    //设置某个应用的通知圆点开关,开启或者关闭
    @Override
    public void setShowBadge(String packageName, int uid, boolean showBadge) {
        synchronized (mPackagePreferences) {
            getOrCreatePackagePreferencesLocked(packageName, uid).showBadge = showBadge;
        }
        updateConfig();//更新属性配置
    }
 ......

(1) 两个方法中都调用了同一个方法 getOrCreatePackagePreferencesLocked(),


    private PackagePreferences getOrCreatePackagePreferencesLocked(String pkg,
            @UserIdInt int userId, int uid, int importance, int priority, int visibility,
            boolean showBadge, int bubblePreference) {
        final String key = packagePreferencesKey(pkg, uid);
        PackagePreferences r = (uid == UNKNOWN_UID)
                ? mRestoredWithoutUids.get(unrestoredPackageKey(pkg, userId))
                : mPackagePreferences.get(key);
        if (r == null) {
            r = new PackagePreferences();
            r.pkg = pkg;
            r.uid = uid;
            r.importance = importance;
            r.priority = priority;
            r.visibility = visibility;
            r.showBadge = showBadge;
            r.bubblePreference = bubblePreference;
            if (mOemLockedApps.containsKey(r.pkg)) {
                List<String> channels = mOemLockedApps.get(r.pkg);
                if (channels == null || channels.isEmpty()) {
                    r.oemLockedImportance = true;
                } else {
                    r.oemLockedChannels = channels;
                }
            }

            try {
                createDefaultChannelIfNeededLocked(r);
            } catch (PackageManager.NameNotFoundException e) {
                Slog.e(TAG, "createDefaultChannelIfNeededLocked - Exception: " + e);
            }

            if (r.uid == UNKNOWN_UID) {
                mRestoredWithoutUids.put(unrestoredPackageKey(pkg, userId), r);
            } else {
                mPackagePreferences.put(key, r);
            }
        }
        return r;
    }

(2) 该方法返回 PackagePreferences 对象,它是PreferencesHelper.java的内部类,接着看下该对象有哪些属性:

    private static class PackagePreferences {
        String pkg;
        int uid = UNKNOWN_UID;
        int importance = DEFAULT_IMPORTANCE;//通知重要性
        int priority = DEFAULT_PRIORITY; //通知优先级
        int visibility = DEFAULT_VISIBILITY; //通知可见性
        boolean showBadge = DEFAULT_SHOW_BADGE; //通知原点
        int bubblePreference = DEFAULT_BUBBLE_PREFERENCE; //通知气泡
        int lockedAppFields = DEFAULT_LOCKED_APP_FIELDS; 

        boolean oemLockedImportance = DEFAULT_OEM_LOCKED_IMPORTANCE;
        List<String> oemLockedChannels = new ArrayList<>();
        boolean defaultAppLockedImportance = DEFAULT_APP_LOCKED_IMPORTANCE;

        boolean hasSentInvalidMessage = false;
        boolean hasSentValidMessage = false;
       
        boolean userDemotedMsgApp = false;

        Delegate delegate = null;
        ArrayMap<String, NotificationChannel> channels = new ArrayMap<>();
        Map<String, NotificationChannelGroup> groups = new ConcurrentHashMap<>();

        public boolean isValidDelegate(String pkg, int uid) {
            return delegate != null && delegate.isAllowed(pkg, uid);
        }
    }

(3)该内部类对象保存了通知的一些属性,是通知属性的封装类,如上面两个方法中,
都用到了getOrCreatePackagePreferencesLocked(packageName, uid).showBadge 
来获取通知是否开启通知原点功能, 该方法相当于是通过 PackagePreferences.showBadge 
获取属性值,之后便可以通过PreferencesHelper 来获取通知最新的属性.

通过 设置 或者 桌面快捷方式 可以打开通知圆点功能,请求会从 设置 跨进程发送到NotificationManagerService(NMS), NMS 会通过setShowBadge()@PreferencesHelper来更新属性,并把最新属性值保存到PreferencesHelper对象中.