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

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

Merge "Combine Notifications check with media sessions check for FGS" into main

parents b8114bfb 8653c6ef
Loading
Loading
Loading
Loading
+7 −0
Original line number Diff line number Diff line
@@ -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;
@@ -168,6 +169,12 @@ public class MediaSession2Record extends MediaSessionRecordImpl {
        return false;
    }

    @Override
    boolean isLinkedToNotification(Notification notification) {
        // Currently it's not possible to link MediaSession2 with a Notification
        return false;
    }

    @Override
    public int getSessionPolicies() {
        synchronized (mLock) {
+11 −0
Original line number Diff line number Diff line
@@ -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;

/**
@@ -638,6 +640,15 @@ public class MediaSessionRecord extends MediaSessionRecordImpl implements IBinde
        return foundNonSystemSession && remoteSessionAllowVolumeAdjustment;
    }

    @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) {
+4 −0
Original line number Diff line number Diff line
@@ -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.
+178 −17
Original line number Diff line number Diff line
@@ -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 {
            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(
                    foregroundServiceDelegationOptions);
                        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);
            }
            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);
                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;
        }
    }
}