创建一个通知,然后调用notify发送:
scss
//frameworks/base/core/java/android/app/NotificationManager.java
public void notify(String tag, int id, Notification notification){
notifyAsUser(tag, id, notification, mContext.getUser());
}
public void notifyAsUser(String tag, int id, Notification notification, UserHandle user)
{
INotificationManager service = getService();
String pkg = mContext.getPackageName();
try {
if (localLOGV) Log.v(TAG, pkg + ": notify(" + id + ", " + notification + ")");
service.enqueueNotificationWithTag(pkg, mContext.getOpPackageName(), tag, id,
fixNotification(notification), user.getIdentifier());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
调用NotificationManagerService的enqueueNotificationWithTag
arduino
//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);
}
调用NotificationManagerService的enqueueNotificationInternal
scss
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) {
//校验调用者是否有权代表该包名发通知
final int notificationUid = resolveNotificationUid(opPkg, pkg, callingUid, userId);
//如果 UID 不匹配(`INVALID_UID`),直接抛 `SecurityException`,通知被丢弃
if (notificationUid == INVALID_UID) {
throw new SecurityException("Caller " + opPkg + ":" + callingUid
+ " trying to post for invalid pkg " + pkg + " in user " + incomingUserId);
}
...
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
final StatusBarNotification n = new StatusBarNotification(
pkg, opPkg, id, tag, notificationUid, callingPid, notification,
user, null, System.currentTimeMillis());
// setup local book-keeping
String channelId = notification.getChannelId();
if (mIsTelevision && (new Notification.TvExtender(notification)).getChannelId() != null) {
channelId = (new Notification.TvExtender(notification)).getChannelId();
}
String shortcutId = n.getShortcutId();
//获取通知渠道,如果渠道为空,直接 `return`(丢弃通知)
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;
}
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));
//如果通知带有 `FLAG_FOREGROUND_SERVICE` 标志
//系统会强制将重要性(`Importance`)提至 `IMPORTANCE_LOW`(如果用户未锁定),并调用 `updateNotificationChannel`
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)) {
// Increase the importance of foreground service notifications unless the user had
// an opinion otherwise (and the channel hasn't yet shown a fg service).
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));
//检查应用是否被系统挂起(`suspended`)、是否处于"勿扰模式"下被禁止、或者该渠道是否被屏蔽
if (!checkDisqualifyingFeatures(userId, notificationUid, id, tag, r,
r.getSbn().getOverrideGroupKey() != null)) {
return;
}
if (info != null) {
//缓存快捷方式信息
mShortcutHelper.cacheShortcut(info, user);
}
// temporarily allow apps to perform extra work when their pending intents are launched
//如果通知包含 `PendingIntent`,系统会授予接收者临时的电池优化白名单(`setPendingIntentAllowlistDuration`)和后台活动启动权限
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));
}
}
}
}
// Need escalated privileges to get package importance
final long token = Binder.clearCallingIdentity();
boolean isAppForeground;
try {
isAppForeground = mActivityManager.getPackageImportance(pkg) == IMPORTANCE_FOREGROUND;
} finally {
Binder.restoreCallingIdentity(token);
}
//Post消息
mHandler.post(new EnqueueNotificationRunnable(userId, r, isAppForeground));
}
//EnqueueNotificationRunnable
public void run() {
synchronized (mNotificationLock) {
//处理"稍后提醒"
final Long snoozeAt =
mSnoozeHelper.getSnoozeTimeForUnpostedNotification(
r.getUser().getIdentifier(),
r.getSbn().getPackageName(), r.getSbn().getKey());
final long currentTime = System.currentTimeMillis();
//如果将来提醒,创建一个 `SnoozeNotificationRunnable` 并 `return`
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;
}
//将通知添加到"已入队但尚未发布"的列表
mEnqueuedNotifications.add(r);
//设置超时
scheduleTimeoutLocked(r);
final StatusBarNotification n = r.getSbn();
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();
// We need to fix the notification up a little for bubbles
//调整气泡设置
updateNotificationBubbleFlags(r, isAppForeground);
// 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);
}
// tell the assistant service about the notification
//发布通知
if (mAssistants.isEnabled()) {
mAssistants.onNotificationEnqueuedLocked(r);
mHandler.postDelayed(new PostNotificationRunnable(r.getKey()),
DELAY_FOR_ASSISTANT_TIME);
} else {
mHandler.post(new PostNotificationRunnable(r.getKey()));
}
}
}
}
//PostNotificationRunnable
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;
}
if (isBlocked(r)) {
Slog.i(TAG, "notification blocked by assistant request");
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();
// Make sure the SBN has an instance ID for statsd logging.
if (old == null || old.getSbn().getInstanceId() == null) {
n.setInstanceId(mNotificationInstanceIdSequence.newInstanceId());
} else {
n.setInstanceId(old.getSbn().getInstanceId());
}
//获取通知在mNotificationList中的索引
int index = indexOfNotificationLocked(n.getKey());
if (index < 0) {
//将通知记录NotificationRecord添加到mNotificationList
mNotificationList.add(r);
mUsageStats.registerPostedByApp(r);
r.setInterruptive(isVisuallyInterruptive(null, r));
} else {
//倘若已存在则直接更新为新的NotificationRecord
old = mNotificationList.get(index); // Potentially *changes* old
mNotificationList.set(index, r);
mUsageStats.registerUpdatedByApp(r, old);
// Make sure we don't lose the foreground service state.
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);
// Ensure if this is a foreground service that the proper additional
// flags are set.
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;
//通知SystemUI显示图标
//NotificationListeners mListeners
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 {
Slog.e(TAG, "Not posting notification without small icon: " + notification);
if (old != null && !old.isCanceled) {
// 移除该通知并通知SystemUI移除它
//NotificationListeners mListeners
mListeners.notifyRemovedLocked(r,
NotificationListenerService.REASON_ERROR, r.getStats());
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());
}
if (mShortcutHelper != null) {
mShortcutHelper.maybeListenForShortcutChangesForBubbles(r,
false /* isRemoved */,
mHandler);
}
maybeRecordInterruptionLocked(r);
maybeRegisterMessageSent(r);
maybeReportForegroundServiceUpdate(r, true);
// Log event to statsd
mNotificationRecordLogger.maybeLogNotificationPosted(r, old, position,
buzzBeepBlinkLoggingCode, getGroupInstanceId(r.getSbn().getGroupKey()));
} 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;
}
}
}
}
}
}
通知SystemUI显示图标
java
//frameworks/base/services/core/java/com/android/server/notification/NotificationManagerService.java)
private void notifyPostedLocked(NotificationRecord r, NotificationRecord old,
boolean notifyAllListeners) {
try {
// Lazily initialized snapshots of the notification.
StatusBarNotification sbn = r.getSbn();
...
mHandler.post(() -> notifyPosted(info, sbnToPost, update));
}
} catch (Exception e) {
Slog.e(TAG, "Could not notify listeners for " + r.getKey(), e);
}
}
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) {
Slog.e(TAG, "unable to notify listener (posted): " + info, ex);
}
}
通知SystemUI移除通知
java
//frameworks/base/services/core/java/com/android/server/notification/NotificationManagerService.java)
public void notifyRemovedLocked(NotificationRecord r, int reason,
NotificationStats notificationStats) {
...
mHandler.post(() -> notifyRemoved(info, sbnLight, update, stats, reason));
}
...
}
private void notifyRemoved(ManagedServiceInfo info, StatusBarNotification sbn,
NotificationRankingUpdate rankingUpdate, NotificationStats stats, int reason) {
final INotificationListener listener = (INotificationListener) info.service;
StatusBarNotificationHolder sbnHolder = new StatusBarNotificationHolder(sbn);
try {
if (!CompatChanges.isChangeEnabled(NOTIFICATION_CANCELLATION_REASONS, info.uid)
&& (reason == REASON_CHANNEL_REMOVED || reason == REASON_CLEAR_DATA)) {
reason = REASON_CHANNEL_BANNED;
}
listener.onNotificationRemoved(sbnHolder, rankingUpdate, stats, reason);
} catch (RemoteException ex) {
Slog.e(TAG, "unable to notify listener (removed): " + info, ex);
}
}