Notify ActivityManager with changes in MediaSession playback state.
Bug: 295518668
Test: InProgress
Change-Id: I5df00218b4053291c357c7c758f9c34cac0eba69
diff --git a/media/java/android/media/flags/media_better_together.aconfig b/media/java/android/media/flags/media_better_together.aconfig
index 3da52cc..7f95886 100644
--- a/media/java/android/media/flags/media_better_together.aconfig
+++ b/media/java/android/media/flags/media_better_together.aconfig
@@ -77,3 +77,9 @@
bug: "279555229"
}
+flag {
+ name: "enable_notifying_activity_manager_with_media_session_status_change"
+ namespace: "media_solutions"
+ description: "Notify ActivityManager with the changes in playback state of the media session."
+ bug: "295518668"
+}
diff --git a/media/java/android/media/session/PlaybackState.java b/media/java/android/media/session/PlaybackState.java
index 7891ee6..60497fe 100644
--- a/media/java/android/media/session/PlaybackState.java
+++ b/media/java/android/media/session/PlaybackState.java
@@ -524,6 +524,28 @@
return false;
}
+ /**
+ * Returns whether the service holding the media session should run in the foreground when the
+ * media session has this playback state or not.
+ *
+ * @hide
+ */
+ public boolean shouldAllowServiceToRunInForeground() {
+ switch (mState) {
+ case PlaybackState.STATE_PLAYING:
+ case PlaybackState.STATE_FAST_FORWARDING:
+ case PlaybackState.STATE_REWINDING:
+ case PlaybackState.STATE_BUFFERING:
+ case PlaybackState.STATE_CONNECTING:
+ case PlaybackState.STATE_SKIPPING_TO_PREVIOUS:
+ case PlaybackState.STATE_SKIPPING_TO_NEXT:
+ case PlaybackState.STATE_SKIPPING_TO_QUEUE_ITEM:
+ return true;
+ default:
+ return false;
+ }
+ }
+
public static final @android.annotation.NonNull Parcelable.Creator<PlaybackState> CREATOR =
new Parcelable.Creator<PlaybackState>() {
@Override
diff --git a/services/core/java/com/android/server/media/MediaSession2Record.java b/services/core/java/com/android/server/media/MediaSession2Record.java
index b424c20..07b333a 100644
--- a/services/core/java/com/android/server/media/MediaSession2Record.java
+++ b/services/core/java/com/android/server/media/MediaSession2Record.java
@@ -16,6 +16,7 @@
package com.android.server.media;
+import android.app.ForegroundServiceDelegationOptions;
import android.media.MediaController2;
import android.media.Session2CommandGroup;
import android.media.Session2Token;
@@ -89,6 +90,12 @@
}
@Override
+ public ForegroundServiceDelegationOptions getForegroundServiceDelegationOptions() {
+ // TODO: Implement when MediaSession2 knows about its owner pid.
+ return null;
+ }
+
+ @Override
public boolean isSystemPriority() {
// System priority session is currently only allowed for telephony, so it's OK to stick to
// the media1 API at this moment.
@@ -217,7 +224,8 @@
synchronized (mLock) {
service = mService;
}
- service.onSessionPlaybackStateChanged(MediaSession2Record.this, playbackActive);
+ service.onSessionPlaybackStateChanged(
+ MediaSession2Record.this, playbackActive, /* playbackState= */ null);
}
}
}
diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java
index 994d3ca..cce66e2 100644
--- a/services/core/java/com/android/server/media/MediaSessionRecord.java
+++ b/services/core/java/com/android/server/media/MediaSessionRecord.java
@@ -29,6 +29,7 @@
import android.annotation.RequiresPermission;
import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
+import android.app.ForegroundServiceDelegationOptions;
import android.app.PendingIntent;
import android.app.compat.CompatChanges;
import android.compat.annotation.ChangeId;
@@ -182,6 +183,8 @@
private final Context mContext;
private final boolean mVolumeAdjustmentForRemoteGroupSessions;
+ private final ForegroundServiceDelegationOptions mForegroundServiceDelegationOptions;
+
private final Object mLock = new Object();
private final CopyOnWriteArrayList<ISessionControllerCallbackHolder>
mControllerCallbackHolders = new CopyOnWriteArrayList<>();
@@ -244,10 +247,32 @@
mVolumeAdjustmentForRemoteGroupSessions = mContext.getResources().getBoolean(
com.android.internal.R.bool.config_volumeAdjustmentForRemoteGroupSessions);
+ mForegroundServiceDelegationOptions = createForegroundServiceDelegationOptions();
+
// May throw RemoteException if the session app is killed.
mSessionCb.mCb.asBinder().linkToDeath(this, 0);
}
+ private ForegroundServiceDelegationOptions createForegroundServiceDelegationOptions() {
+ return new ForegroundServiceDelegationOptions.Builder()
+ .setClientPid(mOwnerPid)
+ .setClientUid(getUid())
+ .setClientPackageName(getPackageName())
+ .setClientAppThread(null)
+ .setSticky(false)
+ .setClientInstanceName(
+ "MediaSessionFgsDelegate_"
+ + getUid()
+ + "_"
+ + mOwnerPid
+ + "_"
+ + getPackageName())
+ .setForegroundServiceTypes(0)
+ .setDelegationService(
+ ForegroundServiceDelegationOptions.DELEGATION_SERVICE_MEDIA_PLAYBACK)
+ .build();
+ }
+
/**
* Get the session binder for the {@link MediaSession}.
*
@@ -681,6 +706,11 @@
return mPackageName + "/" + mTag + " (userId=" + mUserId + ")";
}
+ @Override
+ public ForegroundServiceDelegationOptions getForegroundServiceDelegationOptions() {
+ return mForegroundServiceDelegationOptions;
+ }
+
private void postAdjustLocalVolume(final int stream, final int direction, final int flags,
final String callingOpPackageName, final int callingPid, final int callingUid,
final boolean asSystemService, final boolean useSuggested,
@@ -1273,7 +1303,7 @@
final long token = Binder.clearCallingIdentity();
try {
mService.onSessionPlaybackStateChanged(
- MediaSessionRecord.this, shouldUpdatePriority);
+ MediaSessionRecord.this, shouldUpdatePriority, mPlaybackState);
} finally {
Binder.restoreCallingIdentity(token);
}
diff --git a/services/core/java/com/android/server/media/MediaSessionRecordImpl.java b/services/core/java/com/android/server/media/MediaSessionRecordImpl.java
index 8f01f02..99c8ea9 100644
--- a/services/core/java/com/android/server/media/MediaSessionRecordImpl.java
+++ b/services/core/java/com/android/server/media/MediaSessionRecordImpl.java
@@ -16,7 +16,9 @@
package com.android.server.media;
+import android.app.ForegroundServiceDelegationOptions;
import android.media.AudioManager;
+import android.media.session.PlaybackState;
import android.os.ResultReceiver;
import android.view.KeyEvent;
@@ -51,6 +53,15 @@
int getUserId();
/**
+ * Get the {@link ForegroundServiceDelegationOptions} needed for notifying activity manager
+ * service with changes in the {@link PlaybackState} for this session.
+ *
+ * @return the {@link ForegroundServiceDelegationOptions} needed for notifying the activity
+ * manager service with changes in the {@link PlaybackState} for this session.
+ */
+ ForegroundServiceDelegationOptions getForegroundServiceDelegationOptions();
+
+ /**
* Check if this session has system priority and should receive media buttons before any other
* sessions.
*
diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java
index 2c59511..d4666ba 100644
--- a/services/core/java/com/android/server/media/MediaSessionService.java
+++ b/services/core/java/com/android/server/media/MediaSessionService.java
@@ -29,6 +29,8 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManager;
+import android.app.ActivityManagerInternal;
+import android.app.ForegroundServiceDelegationOptions;
import android.app.KeyguardManager;
import android.app.NotificationManager;
import android.app.PendingIntent;
@@ -59,6 +61,7 @@
import android.media.session.MediaController;
import android.media.session.MediaSession;
import android.media.session.MediaSessionManager;
+import android.media.session.PlaybackState;
import android.os.Binder;
import android.os.Bundle;
import android.os.Handler;
@@ -144,6 +147,7 @@
private AudioManager mAudioManager;
private boolean mHasFeatureLeanback;
private ActivityManagerLocal mActivityManagerLocal;
+ private ActivityManagerInternal mActivityManagerInternal;
// 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.
@@ -229,6 +233,7 @@
mContext.registerReceiver(mNotificationListenerEnabledChangedReceiver, filter);
mActivityManagerLocal = LocalManagerRegistry.getManager(ActivityManagerLocal.class);
+ mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class);
}
@Override
@@ -285,11 +290,31 @@
}
user.mPriorityStack.onSessionActiveStateChanged(record);
}
-
+ notifyActivityManagerWithActiveStateChanges(record, record.isActive());
mHandler.postSessionsChanged(record);
}
}
+ private void notifyActivityManagerWithActiveStateChanges(
+ MediaSessionRecordImpl record, boolean isActive) {
+ if (!Flags.enableNotifyingActivityManagerWithMediaSessionStatusChange()) {
+ return;
+ }
+ ForegroundServiceDelegationOptions foregroundServiceDelegationOptions =
+ record.getForegroundServiceDelegationOptions();
+ if (foregroundServiceDelegationOptions == null) {
+ // This record doesn't support FGS delegation. In practice, this is MediaSession2.
+ return;
+ }
+ if (isActive) {
+ mActivityManagerInternal.startForegroundServiceDelegate(
+ foregroundServiceDelegationOptions, /* connection= */ null);
+ } else {
+ mActivityManagerInternal.stopForegroundServiceDelegate(
+ foregroundServiceDelegationOptions);
+ }
+ }
+
// Currently only media1 can become global priority session.
void setGlobalPrioritySession(MediaSessionRecord record) {
synchronized (mLock) {
@@ -371,8 +396,10 @@
}
}
- void onSessionPlaybackStateChanged(MediaSessionRecordImpl record,
- boolean shouldUpdatePriority) {
+ void onSessionPlaybackStateChanged(
+ MediaSessionRecordImpl record,
+ boolean shouldUpdatePriority,
+ @Nullable PlaybackState playbackState) {
synchronized (mLock) {
FullUserRecord user = getFullUserRecordLocked(record.getUserId());
if (user == null || !user.mPriorityStack.contains(record)) {
@@ -380,6 +407,27 @@
return;
}
user.mPriorityStack.onPlaybackStateChanged(record, shouldUpdatePriority);
+ notifyActivityManagerWithPlaybackStateChanges(record, playbackState);
+ }
+ }
+
+ private void notifyActivityManagerWithPlaybackStateChanges(
+ MediaSessionRecordImpl record, PlaybackState playbackState) {
+ if (!Flags.enableNotifyingActivityManagerWithMediaSessionStatusChange()) {
+ return;
+ }
+ ForegroundServiceDelegationOptions foregroundServiceDelegationOptions =
+ record.getForegroundServiceDelegationOptions();
+ if (foregroundServiceDelegationOptions == null || playbackState == null) {
+ // This record doesn't support FGS delegation. In practice, this is MediaSession2.
+ return;
+ }
+ if (playbackState.shouldAllowServiceToRunInForeground()) {
+ mActivityManagerInternal.startForegroundServiceDelegate(
+ foregroundServiceDelegationOptions, /* connection= */ null);
+ } else {
+ mActivityManagerInternal.stopForegroundServiceDelegate(
+ foregroundServiceDelegationOptions);
}
}
@@ -543,9 +591,23 @@
}
session.close();
+ notifyActivityManagerWithSessionDestroyed(session);
mHandler.postSessionsChanged(session);
}
+ private void notifyActivityManagerWithSessionDestroyed(MediaSessionRecordImpl record) {
+ if (!Flags.enableNotifyingActivityManagerWithMediaSessionStatusChange()) {
+ return;
+ }
+ ForegroundServiceDelegationOptions foregroundServiceDelegationOptions =
+ record.getForegroundServiceDelegationOptions();
+ if (foregroundServiceDelegationOptions == null) {
+ // This record doesn't support FGS delegation. In practice, this is MediaSession2.
+ return;
+ }
+ mActivityManagerInternal.stopForegroundServiceDelegate(foregroundServiceDelegationOptions);
+ }
+
void tempAllowlistTargetPkgIfPossible(int targetUid, String targetPackage,
int callingPid, int callingUid, String callingPackage, String reason) {
final long token = Binder.clearCallingIdentity();