diff options
| author | 2024-03-23 08:57:14 +0000 | |
|---|---|---|
| committer | 2024-03-23 08:57:14 +0000 | |
| commit | c6d558544234c6b87d86d70813a012f94d141012 (patch) | |
| tree | 9eb6d2ed434bb1092ba26b3d509063744bfb0455 | |
| parent | b8114bfbabe3b5bdfb098e4a3390f376ab9d3c82 (diff) | |
| parent | 8653c6ef3346478ecaebd9582597983db659d676 (diff) | |
Merge "Combine Notifications check with media sessions check for FGS" into main
4 files changed, 200 insertions, 17 deletions
diff --git a/services/core/java/com/android/server/media/MediaSession2Record.java b/services/core/java/com/android/server/media/MediaSession2Record.java index 1dc86f2b7f1e..0cd7654f70ea 100644 --- a/services/core/java/com/android/server/media/MediaSession2Record.java +++ b/services/core/java/com/android/server/media/MediaSession2Record.java @@ -17,6 +17,7 @@ package com.android.server.media; import android.app.ForegroundServiceDelegationOptions; +import android.app.Notification; import android.media.MediaController2; import android.media.Session2CommandGroup; import android.media.Session2Token; @@ -169,6 +170,12 @@ public class MediaSession2Record extends MediaSessionRecordImpl { } @Override + boolean isLinkedToNotification(Notification notification) { + // Currently it's not possible to link MediaSession2 with a Notification + return false; + } + + @Override public int getSessionPolicies() { synchronized (mLock) { return mPolicies; diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java index 5b3934ea9b13..ce31ac84cbe3 100644 --- a/services/core/java/com/android/server/media/MediaSessionRecord.java +++ b/services/core/java/com/android/server/media/MediaSessionRecord.java @@ -30,6 +30,7 @@ import android.annotation.RequiresPermission; import android.app.ActivityManager; import android.app.ActivityManagerInternal; import android.app.ForegroundServiceDelegationOptions; +import android.app.Notification; import android.app.PendingIntent; import android.app.compat.CompatChanges; import android.compat.annotation.ChangeId; @@ -89,6 +90,7 @@ import java.util.Arrays; import java.util.Collection; import java.util.List; import java.util.NoSuchElementException; +import java.util.Objects; import java.util.concurrent.CopyOnWriteArrayList; /** @@ -639,6 +641,15 @@ public class MediaSessionRecord extends MediaSessionRecordImpl implements IBinde } @Override + boolean isLinkedToNotification(Notification notification) { + return notification.isMediaNotification() + && Objects.equals( + notification.extras.getParcelable( + Notification.EXTRA_MEDIA_SESSION, MediaSession.Token.class), + mSessionToken); + } + + @Override public int getSessionPolicies() { synchronized (mLock) { return mPolicies; diff --git a/services/core/java/com/android/server/media/MediaSessionRecordImpl.java b/services/core/java/com/android/server/media/MediaSessionRecordImpl.java index e4b2fad5f309..09991995099e 100644 --- a/services/core/java/com/android/server/media/MediaSessionRecordImpl.java +++ b/services/core/java/com/android/server/media/MediaSessionRecordImpl.java @@ -17,6 +17,7 @@ package com.android.server.media; import android.app.ForegroundServiceDelegationOptions; +import android.app.Notification; import android.media.AudioManager; import android.media.session.PlaybackState; import android.os.ResultReceiver; @@ -153,6 +154,9 @@ public abstract class MediaSessionRecordImpl { */ public abstract boolean canHandleVolumeKey(); + /** Returns whether this session is linked to the passed notification. */ + abstract boolean isLinkedToNotification(Notification notification); + /** * Get session policies from custom policy provider set when MediaSessionRecord is instantiated. * If custom policy does not exist, will return null. diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java index e2163c54f4e2..53c32cf31d21 100644 --- a/services/core/java/com/android/server/media/MediaSessionService.java +++ b/services/core/java/com/android/server/media/MediaSessionService.java @@ -32,6 +32,7 @@ import android.app.ActivityManager; import android.app.ActivityManagerInternal; import android.app.ForegroundServiceDelegationOptions; import android.app.KeyguardManager; +import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; import android.app.usage.UsageStatsManager; @@ -81,6 +82,8 @@ import android.os.ShellCallback; import android.os.UserHandle; import android.os.UserManager; import android.provider.Settings; +import android.service.notification.NotificationListenerService; +import android.service.notification.StatusBarNotification; import android.speech.RecognizerIntent; import android.text.TextUtils; import android.util.Log; @@ -105,6 +108,7 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.Set; /** @@ -136,9 +140,9 @@ public class MediaSessionService extends SystemService implements Monitor { /** * Action reported to UsageStatsManager when a media session is no longer active and user * engaged for a given app. If media session only pauses for a brief time the event will not - * necessarily be reported in case user is still "engaging" and will restart it momentarily. + * necessarily be reported in case user is still "engaged" and will restart it momentarily. * In such case, action may be reported after a short delay to ensure user is truly no longer - * engaging. Afterwards, the app is no longer expected to show an ongoing notification. + * engaged. Afterwards, the app is no longer expected to show an ongoing notification. */ private static final String USAGE_STATS_ACTION_STOP = "stop"; private static final String USAGE_STATS_CATEGORY = "android.media"; @@ -164,14 +168,35 @@ public class MediaSessionService extends SystemService implements Monitor { private KeyguardManager mKeyguardManager; private AudioManager mAudioManager; + private NotificationListener mNotificationListener; private boolean mHasFeatureLeanback; private ActivityManagerInternal mActivityManagerInternal; private UsageStatsManagerInternal mUsageStatsManagerInternal; - /* Maps uid with all user engaging session tokens associated to it */ - private final SparseArray<Set<MediaSessionRecordImpl>> mUserEngagingSessions = + /** + * Maps uid with all user engaged session records associated to it. It's used for logging start + * and stop events to UsageStatsManagerInternal. This collection contains MediaSessionRecord(s) + * and MediaSession2Record(s). + * When the media session is paused, the stop event is being logged immediately unlike fgs which + * waits for a certain timeout before considering it disengaged. + */ + private final SparseArray<Set<MediaSessionRecordImpl>> mUserEngagedSessionsForUsageLogging = new SparseArray<>(); + /** + * Maps uid with all user engaged session records associated to it. It's used for calling + * ActivityManagerInternal startFGS and stopFGS. This collection doesn't contain + * MediaSession2Record(s). When the media session is paused, There exists a timeout before + * calling stopFGS unlike usage logging which considers it disengaged immediately. + */ + @GuardedBy("mLock") + private final Map<Integer, Set<MediaSessionRecordImpl>> mUserEngagedSessionsForFgs = + new HashMap<>(); + + /* Maps uid with all media notifications associated to it */ + @GuardedBy("mLock") + private final Map<Integer, Set<Notification>> mMediaNotifications = new HashMap<>(); + // The FullUserRecord of the current users. (i.e. The foreground user that isn't a profile) // It's always not null after the MediaSessionService is started. private FullUserRecord mCurrentFullUserRecord; @@ -228,6 +253,7 @@ public class MediaSessionService extends SystemService implements Monitor { mMediaEventWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "handleMediaEvent"); mNotificationManager = mContext.getSystemService(NotificationManager.class); mAudioManager = mContext.getSystemService(AudioManager.class); + mNotificationListener = new NotificationListener(); } @Override @@ -283,6 +309,16 @@ public class MediaSessionService extends SystemService implements Monitor { mCommunicationManager = mContext.getSystemService(MediaCommunicationManager.class); mCommunicationManager.registerSessionCallback(new HandlerExecutor(mHandler), mSession2TokenCallback); + if (Flags.enableNotifyingActivityManagerWithMediaSessionStatusChange()) { + try { + mNotificationListener.registerAsSystemService( + mContext, + new ComponentName(mContext, NotificationListener.class), + UserHandle.USER_ALL); + } catch (RemoteException e) { + // Intra-process call, should never happen. + } + } break; case PHASE_ACTIVITY_MANAGER_READY: MediaSessionDeviceConfig.initialize(mContext); @@ -630,11 +666,52 @@ public class MediaSessionService extends SystemService implements Monitor { return; } if (allowRunningInForeground) { - mActivityManagerInternal.startForegroundServiceDelegate( - foregroundServiceDelegationOptions, /* connection= */ null); + onUserSessionEngaged(record); } else { - mActivityManagerInternal.stopForegroundServiceDelegate( - foregroundServiceDelegationOptions); + onUserDisengaged(record); + } + } + + private void onUserSessionEngaged(MediaSessionRecordImpl mediaSessionRecord) { + synchronized (mLock) { + int uid = mediaSessionRecord.getUid(); + mUserEngagedSessionsForFgs.putIfAbsent(uid, new HashSet<>()); + mUserEngagedSessionsForFgs.get(uid).add(mediaSessionRecord); + for (Notification mediaNotification : mMediaNotifications.getOrDefault(uid, Set.of())) { + if (mediaSessionRecord.isLinkedToNotification(mediaNotification)) { + mActivityManagerInternal.startForegroundServiceDelegate( + mediaSessionRecord.getForegroundServiceDelegationOptions(), + /* connection= */ null); + return; + } + } + } + } + + private void onUserDisengaged(MediaSessionRecordImpl mediaSessionRecord) { + synchronized (mLock) { + int uid = mediaSessionRecord.getUid(); + if (mUserEngagedSessionsForFgs.containsKey(uid)) { + mUserEngagedSessionsForFgs.get(uid).remove(mediaSessionRecord); + if (mUserEngagedSessionsForFgs.get(uid).isEmpty()) { + mUserEngagedSessionsForFgs.remove(uid); + } + } + + boolean shouldStopFgs = true; + for (MediaSessionRecordImpl sessionRecord : + mUserEngagedSessionsForFgs.getOrDefault(uid, Set.of())) { + for (Notification mediaNotification : mMediaNotifications.getOrDefault(uid, + Set.of())) { + if (sessionRecord.isLinkedToNotification(mediaNotification)) { + shouldStopFgs = false; + } + } + } + if (shouldStopFgs) { + mActivityManagerInternal.stopForegroundServiceDelegate( + mediaSessionRecord.getForegroundServiceDelegationOptions()); + } } } @@ -646,18 +723,18 @@ public class MediaSessionService extends SystemService implements Monitor { String packageName = record.getPackageName(); int sessionUid = record.getUid(); if (userEngaged) { - if (!mUserEngagingSessions.contains(sessionUid)) { - mUserEngagingSessions.put(sessionUid, new HashSet<>()); + if (!mUserEngagedSessionsForUsageLogging.contains(sessionUid)) { + mUserEngagedSessionsForUsageLogging.put(sessionUid, new HashSet<>()); reportUserInteractionEvent( - USAGE_STATS_ACTION_START, record.getUserId(), packageName); + USAGE_STATS_ACTION_START, record.getUserId(), packageName); } - mUserEngagingSessions.get(sessionUid).add(record); - } else if (mUserEngagingSessions.contains(sessionUid)) { - mUserEngagingSessions.get(sessionUid).remove(record); - if (mUserEngagingSessions.get(sessionUid).isEmpty()) { + mUserEngagedSessionsForUsageLogging.get(sessionUid).add(record); + } else if (mUserEngagedSessionsForUsageLogging.contains(sessionUid)) { + mUserEngagedSessionsForUsageLogging.get(sessionUid).remove(record); + if (mUserEngagedSessionsForUsageLogging.get(sessionUid).isEmpty()) { reportUserInteractionEvent( - USAGE_STATS_ACTION_STOP, record.getUserId(), packageName); - mUserEngagingSessions.remove(sessionUid); + USAGE_STATS_ACTION_STOP, record.getUserId(), packageName); + mUserEngagedSessionsForUsageLogging.remove(sessionUid); } } } @@ -3043,4 +3120,88 @@ public class MediaSessionService extends SystemService implements Monitor { obtainMessage(msg, userIdInteger).sendToTarget(); } } + + private final class NotificationListener extends NotificationListenerService { + @Override + public void onNotificationPosted(StatusBarNotification sbn) { + super.onNotificationPosted(sbn); + Notification postedNotification = sbn.getNotification(); + int uid = sbn.getUid(); + + if (!postedNotification.isMediaNotification()) { + return; + } + synchronized (mLock) { + mMediaNotifications.putIfAbsent(uid, new HashSet<>()); + mMediaNotifications.get(uid).add(postedNotification); + for (MediaSessionRecordImpl mediaSessionRecord : + mUserEngagedSessionsForFgs.getOrDefault(uid, Set.of())) { + ForegroundServiceDelegationOptions foregroundServiceDelegationOptions = + mediaSessionRecord.getForegroundServiceDelegationOptions(); + if (mediaSessionRecord.isLinkedToNotification(postedNotification) + && foregroundServiceDelegationOptions != null) { + mActivityManagerInternal.startForegroundServiceDelegate( + foregroundServiceDelegationOptions, + /* connection= */ null); + return; + } + } + } + } + + @Override + public void onNotificationRemoved(StatusBarNotification sbn) { + super.onNotificationRemoved(sbn); + Notification removedNotification = sbn.getNotification(); + int uid = sbn.getUid(); + if (!removedNotification.isMediaNotification()) { + return; + } + synchronized (mLock) { + Set<Notification> uidMediaNotifications = mMediaNotifications.get(uid); + if (uidMediaNotifications != null) { + uidMediaNotifications.remove(removedNotification); + if (uidMediaNotifications.isEmpty()) { + mMediaNotifications.remove(uid); + } + } + + MediaSessionRecordImpl notificationRecord = + getLinkedMediaSessionRecord(uid, removedNotification); + + if (notificationRecord == null) { + return; + } + + boolean shouldStopFgs = true; + for (MediaSessionRecordImpl mediaSessionRecord : + mUserEngagedSessionsForFgs.getOrDefault(uid, Set.of())) { + for (Notification mediaNotification : + mMediaNotifications.getOrDefault(uid, Set.of())) { + if (mediaSessionRecord.isLinkedToNotification(mediaNotification)) { + shouldStopFgs = false; + } + } + } + if (shouldStopFgs + && notificationRecord.getForegroundServiceDelegationOptions() != null) { + mActivityManagerInternal.stopForegroundServiceDelegate( + notificationRecord.getForegroundServiceDelegationOptions()); + } + } + } + + private MediaSessionRecordImpl getLinkedMediaSessionRecord( + int uid, Notification notification) { + synchronized (mLock) { + for (MediaSessionRecordImpl mediaSessionRecord : + mUserEngagedSessionsForFgs.getOrDefault(uid, Set.of())) { + if (mediaSessionRecord.isLinkedToNotification(notification)) { + return mediaSessionRecord; + } + } + } + return null; + } + } } |