Android Notification

  1. 应用层调用过程,先生成一个notification对象,然后通过调用NotificationManager的norify方法,把nofication发布出去。

MainActivity.java

NotificationManager notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
NotificationChannel notificationChannel = new NotificationChannel("1000", "test notification", NotificationManager.IMPORTANCE_DEFAULT);
notificationManager.createNotificationChannel(notificationChannel);
Notification.Builder builder = new Notification.Builder(this, notificationChannel.getId());
builder.setSmallIcon(R.mipmap.ic_launcher);
builder.setContentTitle("hello");
builder.setContentText("for test");
Notification notification = builder.build();
notificationManager.notify(1000, notification);
  1. notify 【通知,公布】 是NotificationManager中的方法

frameworks/base/core/java/android/app/NotificationManager.java

public void notify(int id, Notification notification)
{
    notify(null, id, notification);
}

public void notify(String tag, int id, Notification notification)
{
    notifyAsUser(tag, id, notification, new UserHandle(UserHandle.myUserId()));
}

public void notifyAsUser(String tag, int id, Notification notification, UserHandle user)
{
    INotificationManager service = getService();
    String pkg = mContext.getPackageName();
    // Fix the notification as best we can.
    Notification.addFieldsFromContext(mContext, notification);
    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, user.getIdentifier());
    } catch (RemoteException e) {
        throw e.rethrowFromSystemServer();
    }
}
  1. 下面通过RPC到service端

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

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

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) {
    if (DBG) {
        Slog.v(TAG, "enqueueNotificationInternal: pkg=" + pkg + " id=" + id
                + " notification=" + notification);
    }
    checkCallerIsSystemOrSameApp(pkg);

    final int userId = ActivityManager.handleIncomingUser(callingPid,
            callingUid, incomingUserId, true, false, "enqueueNotification", pkg);
    final UserHandle user = new UserHandle(userId);

    if (pkg == null || notification == null) {
        throw new IllegalArgumentException("null not allowed: pkg=" + pkg
                + " id=" + id + " notification=" + notification);
    }

    // The system can post notifications for any package, let us resolve that.
    final int notificationUid = resolveNotificationUid(opPkg, callingUid, userId);

    // Fix the notification as best we can.
    try {
        final ApplicationInfo ai = mPackageManagerClient.getApplicationInfoAsUser(
                pkg, PackageManager.MATCH_DEBUG_TRIAGED_MISSING,
                (userId == UserHandle.USER_ALL) ? UserHandle.USER_SYSTEM : userId);
        Notification.addFieldsFromContext(ai, notification);
    } catch (NameNotFoundException e) {
        Slog.e(TAG, "Cannot create a context for sending app", e);
        return;
    }

    mUsageStats.registerEnqueuedByApp(pkg);

    // setup local book-keeping
    String channelId = notification.getChannelId();
    if (mIsTelevision && (new Notification.TvExtender(notification)).getChannelId() != null) {
        channelId = (new Notification.TvExtender(notification)).getChannelId();
    }
    final NotificationChannel channel = mRankingHelper.getNotificationChannel(pkg,
            notificationUid, channelId, 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;
        Log.e(TAG, noChannelStr);
        doChannelWarningToast("Developer warning for package \"" + pkg + "\"\n" +
                "Failed to post notification on channel \"" + channelId + "\"\n" +
                "See log for more details");
        return;
    }

    final StatusBarNotification n = new StatusBarNotification(
            pkg, opPkg, id, tag, notificationUid, callingPid, notification,
            user, null, System.currentTimeMillis());
    final NotificationRecord r = new NotificationRecord(getContext(), n, channel);

    if (!checkDisqualifyingFeatures(userId, notificationUid, id, tag, r)) {
        return;
    }

    // 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(),
                            WHITELIST_TOKEN, duration);
                }
            }
        }
    }

    mHandler.post(new EnqueueNotificationRunnable(userId, r));
}

protected 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 (mNotificationLock) {
            mEnqueuedNotifications.add(r);
            scheduleTimeoutLocked(r);

            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();

            // Handle grouped notifications and bail out early if we
            // can to avoid extracting signals.
            handleGroupedNotificationLocked(r, old, callingUid, callingPid);

            // if this is a group child, unsnooze parent summary
            if (n.isGroup() && notification.isGroupChild()) {
                mSnoozeHelper.repostGroupSummary(pkg, r.getUserId(), n.getGroupKey());
            }

            // 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);

            // tell the assistant service about the notification
            if (mNotificationAssistants.isEnabled()) {
                mNotificationAssistants.onNotificationEnqueued(r);
                mHandler.postDelayed(new PostNotificationRunnable(r.getKey()),
                        DELAY_FOR_ASSISTANT_TIME);
            } else {
                mHandler.post(new PostNotificationRunnable(r.getKey()));
            }
        }
    }
}

protected class PostNotificationRunnable implements Runnable {
    private final String key;

    PostNotificationRunnable(String key) {
        this.key = key;
    }

    @Override
    public void run() {
        synchronized (mNotificationLock) {
            try {
                NotificationRecord r = null;
                int N = mEnqueuedNotifications.size();
                for (int i = 0; i < N; i++) {
                    final NotificationRecord enqueued = mEnqueuedNotifications.get(i);
                    if (Objects.equals(key, enqueued.getKey())) {
                        r = enqueued;
                        break;
                    }
                }
                if (r == null) {
                    Slog.i(TAG, "Cannot find enqueued record for key: " + key);
                    return;
                }
                NotificationRecord old = mNotificationsByKey.get(key);
                final StatusBarNotification n = r.sbn;
                final Notification notification = n.getNotification();
                int index = indexOfNotificationLocked(n.getKey());
                if (index < 0) {
                    mNotificationList.add(r);
                    mUsageStats.registerPostedByApp(r);
                } else {
                    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;
                }

                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);

                if (notification.getSmallIcon() != null) {
                    StatusBarNotification oldSbn = (old != null) ? old.sbn : null;
                    mListeners.notifyPostedLocked(n, oldSbn);
                    mHandler.post(new Runnable() {
                        @Override
                        public void run() {
                            mGroupHelper.onNotificationPosted(n);
                        }
                    });
                } else {
                    Slog.e(TAG, "Not posting notification without small icon: " + notification);
                    if (old != null && !old.isCanceled) {
                        mListeners.notifyRemovedLocked(n,
                                NotificationListenerService.REASON_ERROR);
                        mHandler.post(new Runnable() {
                            @Override
                            public void run() {
                                mGroupHelper.onNotificationRemoved(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(r);
            } 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;
                    }
                }
            }
        }
    }
}
  1. NotificationManagerService 的内部类 NotificationListeners

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

public void notifyPostedLocked(StatusBarNotification sbn, StatusBarNotification oldSbn) {
    // Lazily initialized snapshots of the notification.
    TrimCache trimCache = new TrimCache(sbn);

    for (final ManagedServiceInfo info : getServices()) {
        boolean sbnVisible = isVisibleToListener(sbn, info);
        boolean oldSbnVisible = oldSbn != null ? isVisibleToListener(oldSbn, info) : false;
        // This notification hasn't been and still isn't visible -> ignore.
        if (!oldSbnVisible && !sbnVisible) {
            continue;
        }
        final NotificationRankingUpdate update = makeRankingUpdateLocked(info);

        // This notification became invisible -> remove the old one.
        if (oldSbnVisible && !sbnVisible) {
            final StatusBarNotification oldSbnLightClone = oldSbn.cloneLight();
            mHandler.post(new Runnable() {
                @Override
                public void run() {
                    notifyRemoved(info, oldSbnLightClone, update, REASON_USER_STOPPED);
                }
            });
            continue;
        }

        final StatusBarNotification sbnToPost =  trimCache.ForListener(info);
        mHandler.post(new Runnable() {
            @Override
            public void run() {
                notifyPosted(info, sbnToPost, update);
            }
        });
    }
}

private void notifyPosted(final ManagedServiceInfo info,
        final StatusBarNotification sbn, NotificationRankingUpdate rankingUpdate) {
    final INotificationListener listener = (INotificationListener) info.service;
    StatusBarNotificationHolder sbnHolder = new StatusBarNotificationHolder(sbn);
    try {
        listener.onNotificationPosted(sbnHolder, rankingUpdate);
    } catch (RemoteException ex) {
        Log.e(TAG, "unable to notify listener (posted): " + listener, ex);
    }
}

至此,调用过程完成。

  1. 更进一步,需要了解getServices()返回的变量的初始化及赋值过程。

frameworks/base/services/core/java/com/android/server/notification/ManagedServices.java

protected List<ManagedServiceInfo> getServices() {
    synchronized (mMutex) {
        List<ManagedServiceInfo> services = new ArrayList<>(mServices);
        return services;
    }
}
  1. 下面来看看mServices变量的初始化及赋值过程
    搜索过滤发现,ManagedServices类中一共有两处向mService中add something的地方。
    下面是一个倒序的过程

6.1 第一处

private void registerServiceLocked(final ComponentName name, final int userid,
        final boolean isSystem) {
    if (DEBUG) Slog.v(TAG, "registerService: " + name + " u=" + userid);

    final String servicesBindingTag = name.toString() + "/" + userid;
    if (mServicesBinding.contains(servicesBindingTag)) {
        // stop registering this thing already! we're working on it
        return;
    }
    mServicesBinding.add(servicesBindingTag);

    final int N = mServices.size();
    for (int i = N - 1; i >= 0; i--) {
        final ManagedServiceInfo info = mServices.get(i);
        if (name.equals(info.component)
            && info.userid == userid) {
            // cut old connections
            if (DEBUG) Slog.v(TAG, "    disconnecting old " + getCaption() + ": "
                + info.service);
            removeServiceLocked(i);
            if (info.connection != null) {
                mContext.unbindService(info.connection);
            }
        }
    }

    Intent intent = new Intent(mConfig.serviceInterface);
    intent.setComponent(name);

    intent.putExtra(Intent.EXTRA_CLIENT_LABEL, mConfig.clientLabel);

    final PendingIntent pendingIntent = PendingIntent.getActivity(
        mContext, 0, new Intent(mConfig.settingsAction), 0);
    intent.putExtra(Intent.EXTRA_CLIENT_INTENT, pendingIntent);

    ApplicationInfo appInfo = null;
    try {
        appInfo = mContext.getPackageManager().getApplicationInfo(
            name.getPackageName(), 0);
    } catch (NameNotFoundException e) {
        // Ignore if the package doesn't exist we won't be able to bind to the service.
    }
    final int targetSdkVersion =
        appInfo != null ? appInfo.targetSdkVersion : Build.VERSION_CODES.BASE;

    try {
        if (DEBUG) Slog.v(TAG, "binding: " + intent);
        ServiceConnection serviceConnection = new ServiceConnection() {
            IInterface mService;

            @Override
            public void onServiceConnected(ComponentName name, IBinder binder) {
                boolean added = false;
                ManagedServiceInfo info = null;
                synchronized (mMutex) {
                    mServicesBinding.remove(servicesBindingTag);
                    try {
                        mService = asInterface(binder);
                        info = newServiceInfo(mService, name,
                            userid, isSystem, this, targetSdkVersion);
                        binder.linkToDeath(info, 0);
                        added = mServices.add(info);
                    } catch (RemoteException e) {
                        // already dead
                    }
                }
                if (added) {
                    onServiceAdded(info);
                }
            }

            @Override
            public void onServiceDisconnected(ComponentName name) {
                Slog.v(TAG, getCaption() + " connection lost: " + name);
            }
        };
        if (!mContext.bindServiceAsUser(intent,
            serviceConnection,
            BIND_AUTO_CREATE | BIND_FOREGROUND_SERVICE | BIND_ALLOW_WHITELIST_MANAGEMENT,
            new UserHandle(userid))) {
            mServicesBinding.remove(servicesBindingTag);
            Slog.w(TAG, "Unable to bind " + getCaption() + " service: " + intent);
            return;
        }
    } catch (SecurityException ex) {
        Slog.e(TAG, "Unable to bind " + getCaption() + " service: " + intent, ex);
        return;
    }
}

public void registerSystemService(final ComponentName name, final int userid) {
    synchronized (mMutex) {
        registerServiceLocked(name, userid, true /* isSystem */);
    }
}

6.2 第二处,registerService()和registerGuestService()是同级的,都是调用registerServiceImpl():

private ManagedServiceInfo registerServiceImpl(ManagedServiceInfo info) {
    synchronized (mMutex) {
        try {
            info.service.asBinder().linkToDeath(info, 0);
            mServices.add(info);
            return info;
        } catch (RemoteException e) {
            // already dead
        }
    }
    return null;
}

public void registerService(IInterface service, ComponentName component, int userid) {
    checkNotNull(service);
    ManagedServiceInfo info = registerServiceImpl(service, component, userid);
    if (info != null) {
        onServiceAdded(info);
    }
}

public void registerGuestService(ManagedServiceInfo guest) {
    checkNotNull(guest.service);
    if (!checkType(guest.service)) {
        throw new IllegalArgumentException();
    }
    if (registerServiceImpl(guest) != null) {
        onServiceAdded(guest);
    }
}

再往下,就不能继续往下跟了。猜测应该是其它地方会调用这里的三个public方法中的一个,注册自己为这种类型的服务。

  1. 根据官方文档或经验,系统的默认notification listener service 是在SystemUI中实现的。

frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java

public void start() {
    ... ...
    // Set up the initial notification state.
    try {
        mNotificationListener.registerAsSystemService(mContext,
                new ComponentName(mContext.getPackageName(), getClass().getCanonicalName()),
                UserHandle.USER_ALL);
    } catch (RemoteException e) {
        Log.e(TAG, "Unable to register notification listener", e);
    }
    ... ...
}

frameworks/base/core/java/android/service/notification/NotificationListenerService.java

public void registerAsSystemService(Context context, ComponentName componentName,
        int currentUser) throws RemoteException {
    if (mWrapper == null) {
        mWrapper = new NotificationListenerWrapper();
    }
    mSystemContext = context;
    INotificationManager noMan = getNotificationInterface();
    mHandler = new MyHandler(context.getMainLooper());
    mCurrentUser = currentUser;
    noMan.registerListener(mWrapper, componentName, currentUser);
}

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

public void registerListener(final INotificationListener listener,
        final ComponentName component, final int userid) {
    enforceSystemOrSystemUI("INotificationManager.registerListener");
    mListeners.registerService(listener, component, userid);
}

到这里,就看到了6.2的方法。

  1. 下面看一看notification的UI是如何显示出来的
    接着4中,最后会调用notification listener service 的onNotificationPosted()方法。下面来看一下SystemUI的这个方法:

frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java

public void onNotificationPosted(final StatusBarNotification sbn,
        final RankingMap rankingMap) {
    if (DEBUG) Log.d(TAG, "onNotificationPosted: " + sbn);
    if (sbn != null) {
        mHandler.post(new Runnable() {
            @Override
            public void run() {
                processForRemoteInput(sbn.getNotification());
                String key = sbn.getKey();
                mKeysKeptForRemoteInput.remove(key);
                boolean isUpdate = mNotificationData.get(key) != null;
                // In case we don't allow child notifications, we ignore children of
                // notifications that have a summary, since we're not going to show them
                // anyway. This is true also when the summary is canceled,
                // because children are automatically canceled by NoMan in that case.
                if (!ENABLE_CHILD_NOTIFICATIONS
                    && mGroupManager.isChildInGroupWithSummary(sbn)) {
                    if (DEBUG) {
                        Log.d(TAG, "Ignoring group child due to existing summary: " + sbn);
                    }

                    // Remove existing notification to avoid stale data.
                    if (isUpdate) {
                        removeNotification(key, rankingMap);
                    } else {
                        mNotificationData.updateRanking(rankingMap);
                    }
                    return;
                }
                try {
                    if (isUpdate) {
                        updateNotification(sbn, rankingMap);
                    } else {
                        addNotification(sbn, rankingMap);
                    }
                } catch (InflationException e) {
                    handleInflationException(sbn, e);
                }
            }
        });
    }
}

public void addNotification(StatusBarNotification notification, RankingMap ranking)
        throws InflationException {
    String key = notification.getKey();
    if (DEBUG) Log.d(TAG, "addNotification key=" + key);

    mNotificationData.updateRanking(ranking);
    Entry shadeEntry = createNotificationViews(notification);
    boolean isHeadsUped = shouldPeek(shadeEntry);
    if (!isHeadsUped && notification.getNotification().fullScreenIntent != null) {
        if (shouldSuppressFullScreenIntent(key)) {
            if (DEBUG) {
                Log.d(TAG, "No Fullscreen intent: suppressed by DND: " + key);
            }
        } else if (mNotificationData.getImportance(key)
                < NotificationManager.IMPORTANCE_HIGH) {
            if (DEBUG) {
                Log.d(TAG, "No Fullscreen intent: not important enough: "
                        + key);
            }
        } else {
            // Stop screensaver if the notification has a full-screen intent.
            // (like an incoming phone call)
            awakenDreams();

            // not immersive & a full-screen alert should be shown
            if (DEBUG)
                Log.d(TAG, "Notification has fullScreenIntent; sending fullScreenIntent");
            try {
                EventLog.writeEvent(EventLogTags.SYSUI_FULLSCREEN_NOTIFICATION,
                        key);
                notification.getNotification().fullScreenIntent.send();
                shadeEntry.notifyFullScreenIntentLaunched();
                mMetricsLogger.count("note_fullscreen", 1);
            } catch (PendingIntent.CanceledException e) {
            }
        }
    }
    abortExistingInflation(key);

    mForegroundServiceController.addNotification(notification,
            mNotificationData.getImportance(key));

    mPendingNotifications.put(key, shadeEntry);
}

protected NotificationData.Entry createNotificationViews(StatusBarNotification sbn)
        throws InflationException {
    if (DEBUG) {
        Log.d(TAG, "createNotificationViews(notification=" + sbn);
    }
    NotificationData.Entry entry = new NotificationData.Entry(sbn);
    Dependency.get(LeakDetector.class).trackInstance(entry);
    entry.createIcons(mContext, sbn);
    // Construct the expanded view.
    inflateViews(entry, mStackScroller);
    return entry;
}

protected void inflateViews(Entry entry, ViewGroup parent) {
    PackageManager pmUser = getPackageManagerForUser(mContext,
            entry.notification.getUser().getIdentifier());

    final StatusBarNotification sbn = entry.notification;
    if (entry.row != null) {
        entry.reset();
        updateNotification(entry, pmUser, sbn, entry.row);
    } else {
        new RowInflaterTask().inflate(mContext, parent, entry,
                row -> {
                    bindRow(entry, pmUser, sbn, row);
                    updateNotification(entry, pmUser, sbn, row);
                });
    }

}

private void updateNotification(Entry entry, PackageManager pmUser,
        StatusBarNotification sbn, ExpandableNotificationRow row) {
    row.setNeedsRedaction(needsRedaction(entry));
    boolean isLowPriority = mNotificationData.isAmbient(sbn.getKey());
    boolean isUpdate = mNotificationData.get(entry.key) != null;
    boolean wasLowPriority = row.isLowPriority();
    row.setIsLowPriority(isLowPriority);
    row.setLowPriorityStateUpdated(isUpdate && (wasLowPriority != isLowPriority));
    // bind the click event to the content area
    mNotificationClicker.register(row, sbn);

    // Extract target SDK version.
    try {
        ApplicationInfo info = pmUser.getApplicationInfo(sbn.getPackageName(), 0);
        entry.targetSdk = info.targetSdkVersion;
    } catch (NameNotFoundException ex) {
        Log.e(TAG, "Failed looking up ApplicationInfo for " + sbn.getPackageName(), ex);
    }
    row.setLegacy(entry.targetSdk >= Build.VERSION_CODES.GINGERBREAD
            && entry.targetSdk < Build.VERSION_CODES.LOLLIPOP);
    entry.setIconTag(R.id.icon_is_pre_L, entry.targetSdk < Build.VERSION_CODES.LOLLIPOP);
    entry.autoRedacted = entry.notification.getNotification().publicVersion == null;

    // MStar Android Patch Begin
    if (((sbn.getNotification().extras.getCharSequence(Notification.EXTRA_TITLE)) != null) || ((sbn.getNotification().extras.getCharSequence(Notification.EXTRA_TEXT)) != null)) {
        final StatusBarIcon ic = new StatusBarIcon(
                entry.notification.getUser(),
                entry.notification.getPackageName(),
                entry.notification.getNotification().getSmallIcon(),
                entry.notification.getNotification().iconLevel,
                entry.notification.getNotification().number,
                entry.notification.getNotification().tickerText);

        Drawable iconDrawable = StatusBarIconView.getIcon(mContext, ic);

        final View popupNotificationView = LayoutInflater.from(mContext).inflate(
                com.android.internal.R.layout.notification_template_material_base,
                null);
        popupNotificationView.setBackgroundColor(0xffffffff);
        final ImageView popupNotificationIcon = (ImageView) popupNotificationView.findViewById(
                com.android.internal.R.id.icon);
        final ImageView popupNotificationProfileBadge = (ImageView) popupNotificationView.findViewById(
                com.android.internal.R.id.profile_badge);
        popupNotificationIcon.setImageDrawable(iconDrawable);
        if (entry.targetSdk >= Build.VERSION_CODES.LOLLIPOP) {
            popupNotificationIcon.setBackgroundResource(
                    com.android.internal.R.drawable.notification_template_icon_bg);
            int padding = mContext.getResources().getDimensionPixelSize(
                    com.android.internal.R.dimen.notification_large_icon_circle_padding);
            popupNotificationIcon.setPadding(padding, padding, padding, padding);
            if (sbn.getNotification().color != Notification.COLOR_DEFAULT) {
                popupNotificationIcon.getBackground().setColorFilter(
                        sbn.getNotification().color, PorterDuff.Mode.SRC_ATOP);
            }
        }

        if (popupNotificationProfileBadge != null) {
            Drawable profileDrawable = mContext.getPackageManager().getUserBadgeForDensity(
                    entry.notification.getUser(), 0);
            if (profileDrawable != null) {
                popupNotificationProfileBadge.setImageDrawable(profileDrawable);
                popupNotificationProfileBadge.setVisibility(View.VISIBLE);
            } else {
                popupNotificationProfileBadge.setVisibility(View.GONE);
            }
        }

        final TextView popupNotificationTitle = (TextView) popupNotificationView.findViewById(
                com.android.internal.R.id.title);
        final TextView popupNotificationText = (TextView) popupNotificationView.findViewById(
                com.android.internal.R.id.text);
        if (sbn.getNotification().extras != null) {
            popupNotificationTitle.setText(sbn.getNotification().extras.getCharSequence(Notification.EXTRA_TITLE));
            popupNotificationText.setText(sbn.getNotification().extras.getCharSequence(Notification.EXTRA_TEXT));
        }
        else if (sbn.getNotification().tickerText != null){
            popupNotificationTitle.setText(sbn.getNotification().tickerText);
        }

        WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
                LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT,
                WindowManager.LayoutParams.TYPE_SYSTEM_ALERT,
                WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
                PixelFormat.RGBA_8888);
        lp.gravity = Gravity.BOTTOM | Gravity.RIGHT;
        lp.windowAnimations = R.style.Animation_StatusBar_HeadsUp;
        mWindowManager.addView(popupNotificationView, lp);
        mNotificationClicker.register(row, popupNotificationView, sbn);
        mHandler.postDelayed(new Runnable() {
            @Override
            public void run() {
                mWindowManager.removeView(popupNotificationView);
            }
        }, 3000);
    } else {
        Log.e(TAG, "Notification String Title and Text is null ");
    }
    // MStar Android Patch End

    entry.row = row;
    entry.row.setOnActivatedListener(this);

    boolean useIncreasedCollapsedHeight = mMessagingUtil.isImportantMessaging(sbn,
            mNotificationData.getImportance(sbn.getKey()));
    boolean useIncreasedHeadsUp = useIncreasedCollapsedHeight && mPanelExpanded;
    row.setUseIncreasedCollapsedHeight(useIncreasedCollapsedHeight);
    row.setUseIncreasedHeadsUpHeight(useIncreasedHeadsUp);
    row.updateNotification(entry);
}

最终,可以知道,notification的通知UI是通过WindowManager添加的无焦点小窗口。
至此,整个过程完结。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容