In-depth understanding of the principle of notification service NotificationListenerService

foreword

In the previous article How to use NotificationListenerService , we have introduced how to use NotificationListenerService to listen to message notifications. At the end, we also simulated how to realize the function of automatically grabbing red envelopes on WeChat.

So how does NotificationListenerService implement system notification monitoring? (This source code analysis is based on API 32)

NotificationListenerService method collection

NotificationLisenerService is a subclass of Service

public abstract class NotificationListenerService extends Service

In addition to the method properties of Service, NotificationListenerService also provides us with methods such as receiving notifications, removing notifications, and connecting to notification managers, as shown in the figure below.

In general business, we only focus on the four methods with labels.

NotificationListenerService receiving process

Since NotificationListenerService is inherited from Service, let's first look at its onBind method, the code is as follows.

@Override
public IBinder onBind(Intent intent) {
    if (mWrapper == null) {
        mWrapper = new NotificationListenerWrapper();
    }
    return mWrapper;
}

A NotificationListenerWrapper instance is returned in the onBind method, and the NotificationListenerWrapper object is an internal class defined in NotificationListenerService. The main method is shown below.

/** @hide */
protected class NotificationListenerWrapper extends INotificationListener.Stub {
    @Override
    public void onNotificationPosted(IStatusBarNotificationHolder sbnHolder,
            NotificationRankingUpdate update) {
        StatusBarNotification sbn;
        try {
            sbn = sbnHolder.get();
        } catch (RemoteException e) {
            Log.w(TAG, "onNotificationPosted: Error receiving StatusBarNotification", e);
            return;
        }
        if (sbn == null) {
            Log.w(TAG, "onNotificationPosted: Error receiving StatusBarNotification");
            return;
        }

        try {
            // convert icon metadata to legacy format for older clients
            createLegacyIconExtras(sbn.getNotification());
            maybePopulateRemoteViews(sbn.getNotification());
            maybePopulatePeople(sbn.getNotification());
        } catch (IllegalArgumentException e) {
            // warn and drop corrupt notification
            Log.w(TAG, "onNotificationPosted: can't rebuild notification from " +
                    sbn.getPackageName());
            sbn = null;
        }

        // protect subclass from concurrent modifications of (@link mNotificationKeys}.
        synchronized (mLock) {
            applyUpdateLocked(update);
            if (sbn != null) {
                SomeArgs args = SomeArgs.obtain();
                args.arg1 = sbn;
                args.arg2 = mRankingMap;
                mHandler.obtainMessage(MyHandler.MSG_ON_NOTIFICATION_POSTED,
                        args).sendToTarget();
            } else {
                // still pass along the ranking map, it may contain other information
                mHandler.obtainMessage(MyHandler.MSG_ON_NOTIFICATION_RANKING_UPDATE,
                        mRankingMap).sendToTarget();
            }
        }
        ...省略onNotificationRemoved等方法

    }

 NotificationListenerWrapper inherits from INotificationListener.Stub. When we see the keyword Stub, we should know that AIDL is used to realize cross-process communication.

Pass code in onNotificationPosted of NotificationListenerWrapper

mHandler.obtainMessage(MyHandler.MSG_ON_NOTIFICATION_POSTED,
                        args).sendToTarget();

Send the message, and after the handler accepts it, it calls the onNotificationPosted method of NotificationListernerService to monitor the notification message. The code is shown below.

private final class MyHandler extends Handler {
        public static final int MSG_ON_NOTIFICATION_POSTED = 1;

        @Override
        public void handleMessage(Message msg) {
            if (!isConnected) {
                return;
            }
            switch (msg.what) {
                case MSG_ON_NOTIFICATION_POSTED: {
                    SomeArgs args = (SomeArgs) msg.obj;
                    StatusBarNotification sbn = (StatusBarNotification) args.arg1;
                    RankingMap rankingMap = (RankingMap) args.arg2;
                    args.recycle();
                    onNotificationPosted(sbn, rankingMap);
                } break;

           ...
            }
        }
    }

 So, how to communicate with NotificationListenerWrapper when the message notification is sent?

Notification message sending process

When the client sends a notification, the code shown below will be called

notificationManager.notify(1, notification)

notify will call the notifyAsUser method again, the code is as follows

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

 Then it will go to the enqueueNotificationWithTag method of INotificationManager, enqueueNotificationWithTag is the interface declared in the INotificationManager.aidl file

/** {@hide} */
interface INotificationManager
{
    @UnsupportedAppUsage
    void cancelAllNotifications(String pkg, int userId);
    ...
    void cancelToast(String pkg, IBinder token);
    void finishToken(String pkg, IBinder token);

    void enqueueNotificationWithTag(String pkg, String opPkg, String tag, int id,
            in Notification notification, int userId);
    ...
 }

This interface is implemented in NotificationManagerService, and then we go to NotificationManagerService to view, the relevant main code is as follows.

@VisibleForTesting
final IBinder mService = new INotificationManager.Stub() {
  @Override
       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);
         }
}

The enqueueNotificationWithTag method will enter the enqueueNotificationInternal method. At the end of the method, an EnqueueNotificationRunnable will be sent through the Handler. The code is as follows.

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) {
        ...

        //构造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();
        }
        ...

        // 设置intent的白名点,是否盛典、是否后台启动等
        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));
                    }
                }
            }
        }
        ...
        mHandler.post(new EnqueueNotificationRunnable(userId, r, isAppForeground));
    }

The source code of EnqueueNotificationRunnable is as follows.

protected class EnqueueNotificationRunnable implements Runnable {
        private final NotificationRecord r;
        private final int userId;
        private final boolean isAppForeground;

        EnqueueNotificationRunnable(int userId, NotificationRecord r, boolean foreground) {
            this.userId = userId;
            this.r = r;
            this.isAppForeground = foreground;
        }

        @Override
        public void run() {
            synchronized (mNotificationLock) {
                ...
                //将通知加入队列
                mEnqueuedNotifications.add(r);
                scheduleTimeoutLocked(r);
                ...
                if (mAssistants.isEnabled()) {
                    mAssistants.onNotificationEnqueuedLocked(r);
                    mHandler.postDelayed(new PostNotificationRunnable(r.getKey()),
                            DELAY_FOR_ASSISTANT_TIME);
                } else {
                    mHandler.post(new PostNotificationRunnable(r.getKey()));
                }
            }
        }
    }

At the end of EnqueueNotificationRunnable, a PostNotificationRunable will be sent,

The source code of PostNotificationRunable is as follows.

protected class PostNotificationRunnable implements Runnable {
        private final String key;

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

        @Override
        public void run() {
            synchronized (mNotificationLock) {
                try {
                    ...
                    //发送通知
                    if (notification.getSmallIcon() != null) {
                        StatusBarNotification oldSbn = (old != null) ? old.getSbn() : null;
                        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 {
                        //...
                    }

                } finally {
                    ...
                }
            }
        }
    }

It can be seen from the code that the notifyPostedLocked method will be called in the PostNotificationRunable class. Here you may have doubts: here it is clearly judged whether notification.getSmallIcon() is null, and only when it is not null will the notifyPostedLocked method be entered. Why is it directly defaulted here? This is because it is stipulated in Android 5.0 that smallIcon cannot be null, and NotificationListenerService is only applicable to 5.0 and above, so the notifyPostedLocked method is bound to be executed here.

The method source code is as follows.

 private void notifyPostedLocked(NotificationRecord r, NotificationRecord old,
                boolean notifyAllListeners) {
            try {
                // Lazily initialized snapshots of the notification.
                StatusBarNotification sbn = r.getSbn();
                StatusBarNotification oldSbn = (old != null) ? old.getSbn() : null;
                TrimCache trimCache = new TrimCache(sbn);
                //循环通知每个ManagedServiceInfo对象

                for (final ManagedServiceInfo info : getServices()) {
                    ...
                    mHandler.post(() -> notifyPosted(info, sbnToPost, update));
                }
            } catch (Exception e) {
                Slog.e(TAG, "Could not notify listeners for " + r.getKey(), e);
            }
        }

The notifyPostedLocked method will eventually call the notifyPosted method, let's look at the notifyPosted method again.

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

 The notifyPosted method will eventually call the onNotificationPosted method of the INotificationListerner, thus notifying the onNotificationPosted method of the NotificationListenerService.

The flow chart of the above method is shown in the figure below.

NotificationListenerService registration

In NotificationListenerService, register the service through the registerAsSystemService method, and the code is as follows.

 @SystemApi
    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);
    }

The registerAsSystemService method registers the NotificationListenerWrapper object to the NotificationManagerService. In this way, the monitoring of system notifications is realized.

Summarize

NotificationListenerService implements the monitoring of system notifications, which can be summarized into three steps:

  • NotificationListenerService registers NotificationListenerWrapper with NotificationManagerService.

  • NotificationManagerService notifies each NotificationListenerWrapper across processes when a notification is sent.

  • The information in the NotificationListenerWrapper is processed by the Handler in the NotificationListenerService class, thus calling the corresponding callback method in the NotificationListenerService.

Guess you like

Origin blog.csdn.net/huangliniqng/article/details/127890379