diff options
6 files changed, 144 insertions, 5 deletions
diff --git a/media/java/android/media/flags/media_better_together.aconfig b/media/java/android/media/flags/media_better_together.aconfig index 3da52ccbafed..7f9588644052 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 @@ flag { 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 7891ee6d5861..60497fe22dcf 100644 --- a/media/java/android/media/session/PlaybackState.java +++ b/media/java/android/media/session/PlaybackState.java @@ -524,6 +524,28 @@ public final class PlaybackState implements Parcelable { 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 b424c2083bd4..07b333a0fda6 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 @@ public class MediaSession2Record implements MediaSessionRecordImpl { } @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 @@ public class MediaSession2Record implements MediaSessionRecordImpl { 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 994d3ca1124f..cce66e28a8f9 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.Nullable; 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 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR 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 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR 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 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR 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 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR 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 8f01f02f2ab1..99c8ea93936e 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 @@ public interface MediaSessionRecordImpl extends AutoCloseable { 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 2c595116afed..d4666bac89eb 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 static com.android.server.media.MediaKeyDispatcher.isTripleTapOverridden; 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.ISessionManager; 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 @@ public class MediaSessionService extends SystemService implements Monitor { 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 @@ public class MediaSessionService extends SystemService implements Monitor { mContext.registerReceiver(mNotificationListenerEnabledChangedReceiver, filter); mActivityManagerLocal = LocalManagerRegistry.getManager(ActivityManagerLocal.class); + mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class); } @Override @@ -285,11 +290,31 @@ public class MediaSessionService extends SystemService implements Monitor { } 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 @@ public class MediaSessionService extends SystemService implements Monitor { } } - 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 @@ public class MediaSessionService extends SystemService implements Monitor { 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 @@ public class MediaSessionService extends SystemService implements Monitor { } 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(); |