Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 365ec196 authored by Bishoy Gendy's avatar Bishoy Gendy Committed by Android (Google) Code Review
Browse files

Merge "Implement Paused state logic for FGS" into main

parents dcfd1d22 e60a53a8
Loading
Loading
Loading
Loading
+3 −0
Original line number Diff line number Diff line
@@ -80,4 +80,7 @@ interface ISessionManager {
    boolean hasCustomMediaSessionPolicyProvider(String componentName);
    int getSessionPolicies(in MediaSession.Token token);
    void setSessionPolicies(in MediaSession.Token token, int policies);

    // For testing of temporarily engaged sessions.
    void expireTempEngagedSessions();
}
+5 −0
Original line number Diff line number Diff line
@@ -156,6 +156,11 @@ public class MediaSession2Record extends MediaSessionRecordImpl {
        }
    }

    @Override
    public void expireTempEngaged() {
        // NA as MediaSession2 doesn't support UserEngagementStates for FGS.
    }

    @Override
    public boolean sendMediaButton(String packageName, int pid, int uid, boolean asSystemService,
            KeyEvent ke, int sequenceId, ResultReceiver cb) {
+97 −2
Original line number Diff line number Diff line
@@ -24,6 +24,7 @@ import static android.media.session.MediaController.PlaybackInfo.PLAYBACK_TYPE_L
import static android.media.session.MediaController.PlaybackInfo.PLAYBACK_TYPE_REMOTE;

import android.Manifest;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
@@ -85,6 +86,8 @@ import com.android.server.LocalServices;
import com.android.server.uri.UriGrantsManagerInternal;

import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
@@ -225,6 +228,49 @@ public class MediaSessionRecord extends MediaSessionRecordImpl implements IBinde

    private int mPolicies;

    private @UserEngagementState int mUserEngagementState = USER_DISENGAGED;

    @IntDef({USER_PERMANENTLY_ENGAGED, USER_TEMPORARY_ENGAGED, USER_DISENGAGED})
    @Retention(RetentionPolicy.SOURCE)
    private @interface UserEngagementState {}

    /**
     * Indicates that the session is active and in one of the user engaged states.
     *
     * @see #updateUserEngagedStateIfNeededLocked(boolean) ()
     */
    private static final int USER_PERMANENTLY_ENGAGED = 0;

    /**
     * Indicates that the session is active and in {@link PlaybackState#STATE_PAUSED} state.
     *
     * @see #updateUserEngagedStateIfNeededLocked(boolean) ()
     */
    private static final int USER_TEMPORARY_ENGAGED = 1;

    /**
     * Indicates that the session is either not active or in one of the user disengaged states
     *
     * @see #updateUserEngagedStateIfNeededLocked(boolean) ()
     */
    private static final int USER_DISENGAGED = 2;

    /**
     * Indicates the duration of the temporary engaged states.
     *
     * <p>Some {@link MediaSession} states like {@link PlaybackState#STATE_PAUSED} are temporarily
     * engaged, meaning the corresponding session is only considered in an engaged state for the
     * duration of this timeout, and only if coming from an engaged state.
     *
     * <p>For example, if a session is transitioning from a user-engaged state {@link
     * PlaybackState#STATE_PLAYING} to a temporary user-engaged state {@link
     * PlaybackState#STATE_PAUSED}, then the session will be considered in a user-engaged state for
     * the duration of this timeout, starting at the transition instant. However, a temporary
     * user-engaged state is not considered user-engaged when transitioning from a non-user engaged
     * state {@link PlaybackState#STATE_STOPPED}.
     */
    private static final int TEMP_USER_ENGAGED_TIMEOUT = 600000;

    public MediaSessionRecord(
            int ownerPid,
            int ownerUid,
@@ -548,6 +594,7 @@ public class MediaSessionRecord extends MediaSessionRecordImpl implements IBinde
            mSessionCb.mCb.asBinder().unlinkToDeath(this, 0);
            mDestroyed = true;
            mPlaybackState = null;
            updateUserEngagedStateIfNeededLocked(/* isTimeoutExpired= */ true);
            mHandler.post(MessageHandler.MSG_DESTROYED);
        }
    }
@@ -559,6 +606,12 @@ public class MediaSessionRecord extends MediaSessionRecordImpl implements IBinde
        }
    }

    @Override
    public void expireTempEngaged() {
        mHandler.removeCallbacks(mHandleTempEngagedSessionTimeout);
        updateUserEngagedStateIfNeededLocked(/* isTimeoutExpired= */ true);
    }

    /**
     * Sends media button.
     *
@@ -1129,6 +1182,11 @@ public class MediaSessionRecord extends MediaSessionRecordImpl implements IBinde
                }
            };

    private final Runnable mHandleTempEngagedSessionTimeout =
            () -> {
                updateUserEngagedStateIfNeededLocked(/* isTimeoutExpired= */ true);
            };

    @RequiresPermission(Manifest.permission.INTERACT_ACROSS_USERS)
    private static boolean componentNameExists(
            @NonNull ComponentName componentName, @NonNull Context context, int userId) {
@@ -1145,6 +1203,40 @@ public class MediaSessionRecord extends MediaSessionRecordImpl implements IBinde
        return !resolveInfos.isEmpty();
    }

    private void updateUserEngagedStateIfNeededLocked(boolean isTimeoutExpired) {
        int oldUserEngagedState = mUserEngagementState;
        int newUserEngagedState;
        if (!isActive() || mPlaybackState == null) {
            newUserEngagedState = USER_DISENGAGED;
        } else if (isActive() && mPlaybackState.isActive()) {
            newUserEngagedState = USER_PERMANENTLY_ENGAGED;
        } else if (mPlaybackState.getState() == PlaybackState.STATE_PAUSED) {
            newUserEngagedState =
                    oldUserEngagedState == USER_PERMANENTLY_ENGAGED || !isTimeoutExpired
                            ? USER_TEMPORARY_ENGAGED
                            : USER_DISENGAGED;
        } else {
            newUserEngagedState = USER_DISENGAGED;
        }
        if (oldUserEngagedState == newUserEngagedState) {
            return;
        }

        if (newUserEngagedState == USER_TEMPORARY_ENGAGED) {
            mHandler.postDelayed(mHandleTempEngagedSessionTimeout, TEMP_USER_ENGAGED_TIMEOUT);
        } else if (oldUserEngagedState == USER_TEMPORARY_ENGAGED) {
            mHandler.removeCallbacks(mHandleTempEngagedSessionTimeout);
        }

        boolean wasUserEngaged = oldUserEngagedState != USER_DISENGAGED;
        boolean isNowUserEngaged = newUserEngagedState != USER_DISENGAGED;
        mUserEngagementState = newUserEngagedState;
        if (wasUserEngaged != isNowUserEngaged) {
            mService.onSessionUserEngagementStateChange(
                    /* mediaSessionRecord= */ this, /* isUserEngaged= */ isNowUserEngaged);
        }
    }

    private final class SessionStub extends ISession.Stub {
        @Override
        public void destroySession() throws RemoteException {
@@ -1182,8 +1274,10 @@ public class MediaSessionRecord extends MediaSessionRecordImpl implements IBinde
                        .logFgsApiEnd(ActivityManager.FOREGROUND_SERVICE_API_TYPE_MEDIA_PLAYBACK,
                                callingUid, callingPid);
            }

            synchronized (mLock) {
                mIsActive = active;
                updateUserEngagedStateIfNeededLocked(/* isTimeoutExpired= */ false);
            }
            long token = Binder.clearCallingIdentity();
            try {
                mService.onSessionActiveStateChanged(MediaSessionRecord.this, mPlaybackState);
@@ -1341,6 +1435,7 @@ public class MediaSessionRecord extends MediaSessionRecordImpl implements IBinde
                    && TRANSITION_PRIORITY_STATES.contains(newState));
            synchronized (mLock) {
                mPlaybackState = state;
                updateUserEngagedStateIfNeededLocked(/* isTimeoutExpired= */ false);
            }
            final long token = Binder.clearCallingIdentity();
            try {
+6 −0
Original line number Diff line number Diff line
@@ -196,6 +196,12 @@ public abstract class MediaSessionRecordImpl {
     */
    public abstract boolean isClosed();

    /**
     * Note: This method is only used for testing purposes If the session is temporary engaged, the
     * timeout will expire and it will become disengaged.
     */
    public abstract void expireTempEngaged();

    @Override
    public final boolean equals(Object o) {
        if (this == o) return true;
+108 −66
Original line number Diff line number Diff line
@@ -367,11 +367,13 @@ public class MediaSessionService extends SystemService implements Monitor {
            }
            boolean isUserEngaged = isUserEngaged(record, playbackState);

            Log.d(TAG, "onSessionActiveStateChanged: "
                    + "record=" + record
                    + "playbackState=" + playbackState
                    + "allowRunningInForeground=" + isUserEngaged);
            setForegroundServiceAllowance(record, /* allowRunningInForeground= */ isUserEngaged);
            Log.d(
                    TAG,
                    "onSessionActiveStateChanged:"
                            + " record="
                            + record
                            + " playbackState="
                            + playbackState);
            reportMediaInteractionEvent(record, isUserEngaged);
            mHandler.postSessionsChanged(record);
        }
@@ -479,11 +481,13 @@ public class MediaSessionService extends SystemService implements Monitor {
            }
            user.mPriorityStack.onPlaybackStateChanged(record, shouldUpdatePriority);
            boolean isUserEngaged = isUserEngaged(record, playbackState);
            Log.d(TAG, "onSessionPlaybackStateChanged: "
                    + "record=" + record
                    + "playbackState=" + playbackState
                    + "allowRunningInForeground=" + isUserEngaged);
            setForegroundServiceAllowance(record, /* allowRunningInForeground= */ isUserEngaged);
            Log.d(
                    TAG,
                    "onSessionPlaybackStateChanged:"
                            + " record="
                            + record
                            + " playbackState="
                            + playbackState);
            reportMediaInteractionEvent(record, isUserEngaged);
        }
    }
@@ -650,69 +654,113 @@ public class MediaSessionService extends SystemService implements Monitor {
        session.close();

        Log.d(TAG, "destroySessionLocked: record=" + session);
        setForegroundServiceAllowance(session, /* allowRunningInForeground= */ false);

        reportMediaInteractionEvent(session, /* userEngaged= */ false);
        mHandler.postSessionsChanged(session);
    }

    private void setForegroundServiceAllowance(
            MediaSessionRecordImpl record, boolean allowRunningInForeground) {
        if (!Flags.enableNotifyingActivityManagerWithMediaSessionStatusChange()) {
            return;
        }
        ForegroundServiceDelegationOptions foregroundServiceDelegationOptions =
                record.getForegroundServiceDelegationOptions();
        if (foregroundServiceDelegationOptions == null) {
            return;
        }
        if (allowRunningInForeground) {
            onUserSessionEngaged(record);
    void onSessionUserEngagementStateChange(
            MediaSessionRecordImpl mediaSessionRecord, boolean isUserEngaged) {
        if (isUserEngaged) {
            addUserEngagedSession(mediaSessionRecord);
            startFgsIfSessionIsLinkedToNotification(mediaSessionRecord);
        } else {
            onUserDisengaged(record);
            removeUserEngagedSession(mediaSessionRecord);
            stopFgsIfNoSessionIsLinkedToNotification(mediaSessionRecord);
        }
    }

    private void onUserSessionEngaged(MediaSessionRecordImpl mediaSessionRecord) {
    private void addUserEngagedSession(MediaSessionRecordImpl mediaSessionRecord) {
        synchronized (mLock) {
            int uid = mediaSessionRecord.getUid();
            mUserEngagedSessionsForFgs.putIfAbsent(uid, new HashSet<>());
            mUserEngagedSessionsForFgs.get(uid).add(mediaSessionRecord);
        }
    }

    private void removeUserEngagedSession(MediaSessionRecordImpl mediaSessionRecord) {
        synchronized (mLock) {
            int uid = mediaSessionRecord.getUid();
            Set<MediaSessionRecordImpl> mUidUserEngagedSessionsForFgs =
                    mUserEngagedSessionsForFgs.get(uid);
            if (mUidUserEngagedSessionsForFgs == null) {
                return;
            }

            mUidUserEngagedSessionsForFgs.remove(mediaSessionRecord);
            if (mUidUserEngagedSessionsForFgs.isEmpty()) {
                mUserEngagedSessionsForFgs.remove(uid);
            }
        }
    }

    private void startFgsIfSessionIsLinkedToNotification(
            MediaSessionRecordImpl mediaSessionRecord) {
        Log.d(TAG, "startFgsIfSessionIsLinkedToNotification: record=" + mediaSessionRecord);
        if (!Flags.enableNotifyingActivityManagerWithMediaSessionStatusChange()) {
            return;
        }
        synchronized (mLock) {
            int uid = mediaSessionRecord.getUid();
            for (Notification mediaNotification : mMediaNotifications.getOrDefault(uid, Set.of())) {
                if (mediaSessionRecord.isLinkedToNotification(mediaNotification)) {
                    mActivityManagerInternal.startForegroundServiceDelegate(
                            mediaSessionRecord.getForegroundServiceDelegationOptions(),
                            /* connection= */ null);
                    startFgsDelegate(mediaSessionRecord.getForegroundServiceDelegationOptions());
                    return;
                }
            }
        }
    }

    private void onUserDisengaged(MediaSessionRecordImpl mediaSessionRecord) {
    private void startFgsDelegate(
            ForegroundServiceDelegationOptions foregroundServiceDelegationOptions) {
        final long token = Binder.clearCallingIdentity();
        try {
            mActivityManagerInternal.startForegroundServiceDelegate(
                    foregroundServiceDelegationOptions, /* connection= */ null);
        } finally {
            Binder.restoreCallingIdentity(token);
        }
    }

    private void stopFgsIfNoSessionIsLinkedToNotification(
            MediaSessionRecordImpl mediaSessionRecord) {
        Log.d(TAG, "stopFgsIfNoSessionIsLinkedToNotification: record=" + mediaSessionRecord);
        if (!Flags.enableNotifyingActivityManagerWithMediaSessionStatusChange()) {
            return;
        }
        synchronized (mLock) {
            int uid = mediaSessionRecord.getUid();
            if (mUserEngagedSessionsForFgs.containsKey(uid)) {
                mUserEngagedSessionsForFgs.get(uid).remove(mediaSessionRecord);
                if (mUserEngagedSessionsForFgs.get(uid).isEmpty()) {
                    mUserEngagedSessionsForFgs.remove(uid);
                }
            ForegroundServiceDelegationOptions foregroundServiceDelegationOptions =
                    mediaSessionRecord.getForegroundServiceDelegationOptions();
            if (foregroundServiceDelegationOptions == null) {
                return;
            }

            boolean shouldStopFgs = true;
            for (MediaSessionRecordImpl sessionRecord :
            for (MediaSessionRecordImpl record :
                    mUserEngagedSessionsForFgs.getOrDefault(uid, Set.of())) {
                for (Notification mediaNotification : mMediaNotifications.getOrDefault(uid,
                        Set.of())) {
                    if (sessionRecord.isLinkedToNotification(mediaNotification)) {
                        shouldStopFgs = false;
                for (Notification mediaNotification :
                        mMediaNotifications.getOrDefault(uid, Set.of())) {
                    if (record.isLinkedToNotification(mediaNotification)) {
                        // A user engaged session linked with a media notification is found.
                        // We shouldn't call stop FGS in this case.
                        return;
                    }
                }
            }
            if (shouldStopFgs) {
                mActivityManagerInternal.stopForegroundServiceDelegate(
                        mediaSessionRecord.getForegroundServiceDelegationOptions());

            stopFgsDelegate(foregroundServiceDelegationOptions);
        }
    }

    private void stopFgsDelegate(
            ForegroundServiceDelegationOptions foregroundServiceDelegationOptions) {
        final long token = Binder.clearCallingIdentity();
        try {
            mActivityManagerInternal.stopForegroundServiceDelegate(
                    foregroundServiceDelegationOptions);
        } finally {
            Binder.restoreCallingIdentity(token);
        }
    }

    private void reportMediaInteractionEvent(MediaSessionRecordImpl record, boolean userEngaged) {
@@ -2502,7 +2550,6 @@ public class MediaSessionService extends SystemService implements Monitor {
            }
            MediaSessionRecord session = null;
            MediaButtonReceiverHolder mediaButtonReceiverHolder = null;

            if (mCustomMediaKeyDispatcher != null) {
                MediaSession.Token token = mCustomMediaKeyDispatcher.getMediaSession(
                        keyEvent, uid, asSystemService);
@@ -2630,6 +2677,18 @@ public class MediaSessionService extends SystemService implements Monitor {
                    && streamType <= AudioManager.STREAM_NOTIFICATION;
        }

        @Override
        public void expireTempEngagedSessions() {
            synchronized (mLock) {
                for (Set<MediaSessionRecordImpl> uidSessions :
                        mUserEngagedSessionsForFgs.values()) {
                    for (MediaSessionRecordImpl sessionRecord : uidSessions) {
                        sessionRecord.expireTempEngaged();
                    }
                }
            }
        }

        private class MediaKeyListenerResultReceiver extends ResultReceiver implements Runnable {
            private final String mPackageName;
            private final int mPid;
@@ -3127,7 +3186,6 @@ public class MediaSessionService extends SystemService implements Monitor {
            super.onNotificationPosted(sbn);
            Notification postedNotification = sbn.getNotification();
            int uid = sbn.getUid();

            if (!postedNotification.isMediaNotification()) {
                return;
            }
@@ -3138,11 +3196,9 @@ public class MediaSessionService extends SystemService implements Monitor {
                        mUserEngagedSessionsForFgs.getOrDefault(uid, Set.of())) {
                    ForegroundServiceDelegationOptions foregroundServiceDelegationOptions =
                            mediaSessionRecord.getForegroundServiceDelegationOptions();
                    if (mediaSessionRecord.isLinkedToNotification(postedNotification)
                            && foregroundServiceDelegationOptions != null) {
                        mActivityManagerInternal.startForegroundServiceDelegate(
                                foregroundServiceDelegationOptions,
                                /* connection= */ null);
                    if (foregroundServiceDelegationOptions != null
                            && mediaSessionRecord.isLinkedToNotification(postedNotification)) {
                        startFgsDelegate(foregroundServiceDelegationOptions);
                        return;
                    }
                }
@@ -3173,21 +3229,7 @@ public class MediaSessionService extends SystemService implements Monitor {
                    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());
                }
                stopFgsIfNoSessionIsLinkedToNotification(notificationRecord);
            }
        }

Loading