summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--services/core/java/com/android/server/media/MediaSession2Record.java7
-rw-r--r--services/core/java/com/android/server/media/MediaSessionRecord.java11
-rw-r--r--services/core/java/com/android/server/media/MediaSessionRecordImpl.java4
-rw-r--r--services/core/java/com/android/server/media/MediaSessionService.java195
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;
+ }
+ }
}