Android N NotificationManagerService源码分析

关于NotificationManagerService的使用推荐参考下面资料,写的不错:

Android4.4 NotificationManagerService使用详解与原理分析(一)、(二):点击打开链接

这里主要分析framework层的源码。

相关文件主要有:

frameworks\base\core\java\android\app\NotificationManager.java;

frameworks\base\services\core\java\com\android\server\notification\NotificationManagerService.java;

frameworks\base\services\core\java\com\android\server\statusbar\StatusBarManagerService.java;



第三方应用发起通知时,调用的是NotificationManager的notify方法,我们就从这里入手。

notify方法表示发送一个显示在状态栏上的通知,代码如下:

    /**
     * Post a notification to be shown in the status bar. If a notification with
     * the same id has already been posted by your application and has not yet been canceled, it
     * will be replaced by the updated information.
     *
     * @param id An identifier for this notification unique within your
     *        application.
     * @param notification A {@link Notification} object describing what to show the user. Must not
     *        be null.
     */
    public void notify(int id, Notification notification)
    {
        notify(null, id, notification);
    }

如果你的第三方应用发送了一个相同id的通知,并且没有被取消,它将被更新的信息所取代。

    /**
     * Post a notification to be shown in the status bar. If a notification with
     * the same tag and id has already been posted by your application and has not yet been
     * canceled, it will be replaced by the updated information.
     *
     * @param tag A string identifier for this notification.  May be {@code null}.
     * @param id An identifier for this notification.  The pair (tag, id) must be unique
     *        within your application.
     * @param notification A {@link Notification} object describing what to
     *        show the user. Must not be null.
     */
    public void notify(String tag, int id, Notification notification)
    {
        notifyAsUser(tag, id, notification, new UserHandle(UserHandle.myUserId()));
    }
如果你的第三方应用发送了一个相同tag和id的通知,并且没有被取消,它将被更新的信息所取代。

    /**
     * @hide
     */
    public void notifyAsUser(String tag, int id, Notification notification, UserHandle user)
    {
        int[] idOut = new int[1];
        INotificationManager service = getService();//获取IBinder对象(NotificationManagerService)
        String pkg = mContext.getPackageName();
        // Fix the notification as best we can.
        Notification.addFieldsFromContext(mContext, notification);//"android.appinfo"、"android.originatingUserId"
        if (notification.sound != null) {
            notification.sound = notification.sound.getCanonicalUri();
            if (StrictMode.vmFileUriExposureEnabled()) {
                notification.sound.checkFileUriExposed("Notification.sound");
            }
        }
        fixLegacySmallIcon(notification, pkg);
        if (mContext.getApplicationInfo().targetSdkVersion > Build.VERSION_CODES.LOLLIPOP_MR1) {
            if (notification.getSmallIcon() == null) {
                throw new IllegalArgumentException("Invalid notification (no valid small icon): "
                        + notification);
            }
        }
        if (localLOGV) Log.v(TAG, pkg + ": notify(" + id + ", " + notification + ")");
        final Notification copy = Builder.maybeCloneStrippedForDelivery(notification);
        try {
            service.enqueueNotificationWithTag(pkg, mContext.getOpPackageName(), tag, id,
                    copy, idOut, user.getIdentifier());
            if (id != idOut[0]) {
                Log.w(TAG, "notify: id corrupted: sent " + id + ", got back " + idOut[0]);
            }
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }
  • 我们看下Notification的addFieldsFromContext方法做了哪些事情:

    /**
     * @hide
     */
    public static void addFieldsFromContext(Context context, Notification notification) {
        addFieldsFromContext(context.getApplicationInfo(), context.getUserId(), notification);
    }

    /**
     * @hide
     */
    public static void addFieldsFromContext(ApplicationInfo ai, int userId,
            Notification notification) {
        notification.extras.putParcelable(EXTRA_BUILDER_APPLICATION_INFO, ai);//"android.appinfo"
        notification.extras.putInt(EXTRA_ORIGINATING_USERID, userId);//"android.originatingUserId"
    }

主要是保存了当前应用对应的ApplicationInfo对象和对应的userId。在Android4.4中Notification新增了extras字段,用来保存系统通知的一些具体信息,定义如下:

    /**
     * Additional semantic data to be carried around with this Notification.
     * <p>
     * The extras keys defined here are intended to capture the original inputs to {@link Builder}
     * APIs, and are intended to be used by
     * {@link android.service.notification.NotificationListenerService} implementations to extract
     * detailed information from notification objects.
     */
    public Bundle extras = new Bundle();
addFieldsFromContext方法主要实现将当前应用的ApplicationInfo对象保存到“android.appinfo”字段,将当前的userId保存到“android.originatingUserId”字段中。

  • fixLegacySmalIcon,notify函数会判断notification是否有small icon,如果没有设icon或small icon,用notify方法时会抛出异常。fixLegacySmallIcon方法如下:

    private void fixLegacySmallIcon(Notification n, String pkg) {
        if (n.getSmallIcon() == null && n.icon != 0) {
            n.setSmallIcon(Icon.createWithResource(pkg, n.icon));
        }
    }

完成上面的操作后,调用通知管理类NotificationManagerService的enqueueNotificationWithTag方法,

        @Override
        public void enqueueNotificationWithTag(String pkg, String opPkg, String tag, int id,
                Notification notification, int[] idOut, int userId) throws RemoteException {
            enqueueNotificationInternal(pkg, opPkg, Binder.getCallingUid(),
                    Binder.getCallingPid(), tag, id, notification, idOut, userId);
        }

我们看到在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);
            }
        }
    }
最后调用buzzBeepBlinkLocked方法对消息进行处理。
    void buzzBeepBlinkLocked(NotificationRecord record) {
        boolean buzz = false;
        boolean beep = false;
        boolean blink = false;

        final Notification notification = record.sbn.getNotification();
        final String key = record.getKey();

        // Should this notification make noise, vibe, or use the LED?是否需要声音、震动、led
        final boolean aboveThreshold = record.getImportance() >= IMPORTANCE_DEFAULT;
        final boolean canInterrupt = aboveThreshold && !record.isIntercepted();
        if (DBG || record.isIntercepted())
            Slog.v(TAG,
                    "pkg=" + record.sbn.getPackageName() + " canInterrupt=" + canInterrupt +
                            " intercept=" + record.isIntercepted()
            );

        final int currentUser;
        final long token = Binder.clearCallingIdentity();
        try {
            currentUser = ActivityManager.getCurrentUser();
        } finally {
            Binder.restoreCallingIdentity(token);
        }

        // If we're not supposed to beep, vibrate, etc. then don't.
        final String disableEffects = disableNotificationEffects(record);
        if (disableEffects != null) {
            ZenLog.traceDisableEffects(record, disableEffects);
        }

        // Remember if this notification already owns the notification channels.
        boolean wasBeep = key != null && key.equals(mSoundNotificationKey);
        boolean wasBuzz = key != null && key.equals(mVibrateNotificationKey);

        // These are set inside the conditional if the notification is allowed to make noise.
        boolean hasValidVibrate = false;
        boolean hasValidSound = false;

        boolean smsRingtone =  false;
        if (mCarrierConfig == null) {
            mCarrierConfig = mConfigManager.getConfig();
        } else {
            smsRingtone = mCarrierConfig.getBoolean(
                CarrierConfigManager.KEY_CONFIG_SMS_RINGTONE_INCALL);
        }
        if ((disableEffects == null || (smsRingtone && mInCall))
                && (record.getUserId() == UserHandle.USER_ALL ||
                    record.getUserId() == currentUser ||
                    mUserProfiles.isCurrentProfile(record.getUserId()))
                && canInterrupt
                && mSystemReady
                && mAudioManager != null) {
            if (DBG) Slog.v(TAG, "Interrupting!");

            // should we use the default notification sound? (indicated either by
            // DEFAULT_SOUND or because notification.sound is pointing at
            // Settings.System.NOTIFICATION_SOUND)
            final boolean useDefaultSound =
                   (notification.defaults & Notification.DEFAULT_SOUND) != 0 ||
                           Settings.System.DEFAULT_NOTIFICATION_URI
                                   .equals(notification.sound);

            Uri soundUri = null;
            if (useDefaultSound) {
                soundUri = Settings.System.DEFAULT_NOTIFICATION_URI;

                // check to see if the default notification sound is silent
                ContentResolver resolver = getContext().getContentResolver();
                hasValidSound = Settings.System.getString(resolver,
                       Settings.System.NOTIFICATION_SOUND) != null;
            } else if (notification.sound != null) {
                soundUri = notification.sound;
                hasValidSound = (soundUri != null);
            }

            // Does the notification want to specify its own vibration?
            final boolean hasCustomVibrate = notification.vibrate != null;

            // new in 4.2: if there was supposed to be a sound and we're in vibrate
            // mode, and no other vibration is specified, we fall back to vibration
            final boolean convertSoundToVibration =
                    !hasCustomVibrate
                            && hasValidSound
                            && (mAudioManager.getRingerModeInternal() == AudioManager.RINGER_MODE_VIBRATE);

            // The DEFAULT_VIBRATE flag trumps any custom vibration AND the fallback.
            final boolean useDefaultVibrate =
                    (notification.defaults & Notification.DEFAULT_VIBRATE) != 0;

            hasValidVibrate = useDefaultVibrate || convertSoundToVibration ||
                    hasCustomVibrate;

            // We can alert, and we're allowed to alert, but if the developer asked us to only do
            // it once, and we already have, then don't.
            if (!(record.isUpdate
                    && (notification.flags & Notification.FLAG_ONLY_ALERT_ONCE) != 0)) {

                sendAccessibilityEvent(notification, record.sbn.getPackageName());

                if (hasValidSound) {
                    boolean looping =
                            (notification.flags & Notification.FLAG_INSISTENT) != 0;
                    AudioAttributes audioAttributes = audioAttributesForNotification(notification);
                    mSoundNotificationKey = key;
                    // do not play notifications if stream volume is 0 (typically because
                    // ringer mode is silent) or if there is a user of exclusive audio focus
                    if ((mAudioManager.getStreamVolume(
                            AudioAttributes.toLegacyStreamType(audioAttributes)) != 0)
                            && !mAudioManager.isAudioFocusExclusive()) {
                        final long identity = Binder.clearCallingIdentity();
                        try {
                            final IRingtonePlayer player =
                                    mAudioManager.getRingtonePlayer();
                            if (player != null) {
                                if (DBG) Slog.v(TAG, "Playing sound " + soundUri
                                        + " with attributes " + audioAttributes);
                                player.playAsync(soundUri, record.sbn.getUser(), looping,
                                        audioAttributes);
                                beep = true;
                            }
                        } catch (RemoteException e) {
                        } finally {
                            Binder.restoreCallingIdentity(identity);
                        }
                    }
                }

                if (hasValidVibrate && !(mAudioManager.getRingerModeInternal()
                        == AudioManager.RINGER_MODE_SILENT)) {
                    mVibrateNotificationKey = key;

                    if (useDefaultVibrate || convertSoundToVibration) {
                        // Escalate privileges so we can use the vibrator even if the
                        // notifying app does not have the VIBRATE permission.
                        long identity = Binder.clearCallingIdentity();
                        try {
                            mVibrator.vibrate(record.sbn.getUid(), record.sbn.getOpPkg(),
                                useDefaultVibrate ? mDefaultVibrationPattern
                                    : mFallbackVibrationPattern,
                                ((notification.flags & Notification.FLAG_INSISTENT) != 0)
                                    ? 0: -1, audioAttributesForNotification(notification));
                            buzz = true;
                        } finally {
                            Binder.restoreCallingIdentity(identity);
                        }
                    } else if (notification.vibrate.length > 1) {
                        // If you want your own vibration pattern, you need the VIBRATE
                        // permission
                        
                            mVibrator.vibrate(record.sbn.getUid(), record.sbn.getOpPkg(),
                                    notification.vibrate,
                                ((notification.flags & Notification.FLAG_INSISTENT) != 0)
                                        ? 0: -1, audioAttributesForNotification(notification));
                            buzz = true;
                    }
                }
            }

        }
        // If a notification is updated to remove the actively playing sound or vibrate,
        // cancel that feedback now
        if (wasBeep && !hasValidSound) {
            clearSoundLocked();
        }
        if (wasBuzz && !hasValidVibrate) {
            clearVibrateLocked();
        }

        // light
        // release the light
        boolean wasShowLights = mLights.remove(key);
        if ((notification.flags & Notification.FLAG_SHOW_LIGHTS) != 0 && aboveThreshold
                && ((record.getSuppressedVisualEffects()
                & NotificationListenerService.SUPPRESSED_EFFECT_SCREEN_OFF) == 0)) {
            mLights.add(key);
            updateLightsLocked();
            if (mUseAttentionLight) {
                mAttentionLight.pulse();
            }
            blink = true;
        } else if (wasShowLights) {
            updateLightsLocked();
        }
        if (buzz || beep || blink) {
            if (((record.getSuppressedVisualEffects()
                    & NotificationListenerService.SUPPRESSED_EFFECT_SCREEN_OFF) != 0)) {
                if (DBG) Slog.v(TAG, "Suppressed SystemUI from triggering screen on");
            } else {
                EventLogTags.writeNotificationAlert(key,
                        buzz ? 1 : 0, beep ? 1 : 0, blink ? 1 : 0);
                // 后将mBuzzBeepBlinked post到工作handler,最后会调用到mStatusBar.buzzBeepBlinked()
                mHandler.post(mBuzzBeepBlinked);
            }
        }
    }












发布了70 篇原创文章 · 获赞 42 · 访问量 31万+
展开阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 大白 设计师: CSDN官方博客

分享到微信朋友圈

×

扫一扫,手机浏览