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

Commit a5b02329 authored by RoboErik's avatar RoboErik
Browse files

Make sessions aware of user id

This tags all sessions with the user id that they were created for. It
also adds API for creating and querying sessions for a specific user.

This does not wrap providers per user yet which will be done in a
separate CL.

Change-Id: Icdaf701b0614a95301657998602c45208d548c27
parent 12319bad
Loading
Loading
Loading
Loading
+2 −2
Original line number Diff line number Diff line
@@ -25,6 +25,6 @@ import android.os.Bundle;
 * @hide
 */
interface ISessionManager {
    ISession createSession(String packageName, in ISessionCallback cb, String tag);
    List<IBinder> getSessions(in ComponentName compName);
    ISession createSession(String packageName, in ISessionCallback cb, String tag, int userId);
    List<IBinder> getSessions(in ComponentName compName, int userId);
}
 No newline at end of file
+36 −2
Original line number Diff line number Diff line
@@ -22,6 +22,7 @@ import android.media.session.ISessionManager;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.UserHandle;
import android.service.notification.NotificationListenerService;
import android.util.Log;

@@ -65,10 +66,25 @@ public final class SessionManager {
     * @return a {@link Session} for the new session
     */
    public Session createSession(String tag) {
        return createSessionAsUser(tag, UserHandle.myUserId());
    }

    /**
     * Creates a new session as the specified user. To create a session as a
     * user other than your own you must hold the
     * {@link android.Manifest.permission#INTERACT_ACROSS_USERS_FULL}
     * permission.
     *
     * @param tag A short name for debugging purposes
     * @param userId The user id to create the session as.
     * @return a {@link Session} for the new session
     * @hide
     */
    public Session createSessionAsUser(String tag, int userId) {
        try {
            Session.CallbackStub cbStub = new Session.CallbackStub();
            Session session = new Session(mService
                    .createSession(mContext.getPackageName(), cbStub, tag), cbStub);
                    .createSession(mContext.getPackageName(), cbStub, tag, userId), cbStub);
            cbStub.setMediaSession(session);

            return session;
@@ -91,9 +107,27 @@ public final class SessionManager {
     * @return A list of controllers for ongoing sessions
     */
    public List<SessionController> getActiveSessions(ComponentName notificationListener) {
        return getActiveSessionsForUser(notificationListener, UserHandle.myUserId());
    }

    /**
     * Get active sessions for a specific user. To retrieve actions for a user
     * other than your own you must hold the
     * {@link android.Manifest.permission#INTERACT_ACROSS_USERS_FULL} permission
     * in addition to any other requirements. If you are an enabled notification
     * listener you may only get sessions for the users you are enabled for.
     *
     * @param notificationListener The enabled notification listener component.
     *            May be null.
     * @param userId The user id to fetch sessions for.
     * @return A list of controllers for ongoing sessions.
     * @hide
     */
    public List<SessionController> getActiveSessionsForUser(ComponentName notificationListener,
            int userId) {
        ArrayList<SessionController> controllers = new ArrayList<SessionController>();
        try {
            List<IBinder> binders = mService.getSessions(notificationListener);
            List<IBinder> binders = mService.getSessions(notificationListener, userId);
            for (int i = binders.size() - 1; i >= 0; i--) {
                SessionController controller = SessionController.fromBinder(ISessionController.Stub
                        .asInterface(binders.get(i)));
+22 −6
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@

package com.android.server.media;

import android.app.ActivityManager;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.media.routeprovider.RouteRequest;
@@ -42,6 +43,7 @@ import android.os.Message;
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.SystemClock;
import android.os.UserHandle;
import android.text.TextUtils;
import android.util.Log;
import android.util.Pair;
@@ -80,7 +82,9 @@ public class MediaSessionRecord implements IBinder.DeathRecipient {

    private final MessageHandler mHandler;

    private final int mPid;
    private final int mOwnerPid;
    private final int mOwnerUid;
    private final int mUserId;
    private final SessionInfo mSessionInfo;
    private final String mTag;
    private final ControllerStub mController;
@@ -110,10 +114,12 @@ public class MediaSessionRecord implements IBinder.DeathRecipient {

    private boolean mIsActive = false;

    public MediaSessionRecord(int pid, String packageName, ISessionCallback cb, String tag,
            MediaSessionService service, Handler handler) {
        mPid = pid;
        mSessionInfo = new SessionInfo(UUID.randomUUID().toString(), packageName);
    public MediaSessionRecord(int ownerPid, int ownerUid, int userId, String ownerPackageName,
            ISessionCallback cb, String tag, MediaSessionService service, Handler handler) {
        mOwnerPid = ownerPid;
        mOwnerUid = ownerUid;
        mUserId = userId;
        mSessionInfo = new SessionInfo(UUID.randomUUID().toString(), ownerPackageName);
        mTag = tag;
        mController = new ControllerStub();
        mSession = new SessionStub();
@@ -186,6 +192,15 @@ public class MediaSessionRecord implements IBinder.DeathRecipient {
        return (mFlags & flag) != 0;
    }

    /**
     * Get the user id this session was created for.
     *
     * @return The user id for this session.
     */
    public int getUserId() {
        return mUserId;
    }

    /**
     * Check if this session has system priorty and should receive media buttons
     * before any other sessions.
@@ -305,7 +320,8 @@ public class MediaSessionRecord implements IBinder.DeathRecipient {
        pw.println(prefix + mTag + " " + this);

        final String indent = prefix + "  ";
        pw.println(indent + "pid=" + mPid);
        pw.println(indent + "ownerPid=" + mOwnerPid + ", ownerUid=" + mOwnerUid
                + ", userId=" + mUserId);
        pw.println(indent + "info=" + mSessionInfo.toString());
        pw.println(indent + "published=" + mIsActive);
        pw.println(indent + "flags=" + mFlags);
+53 −28
Original line number Diff line number Diff line
@@ -63,7 +63,6 @@ public class MediaSessionService extends SystemService implements Monitor {
    private final ArrayList<MediaRouteProviderProxy> mProviders
            = new ArrayList<MediaRouteProviderProxy>();
    private final Object mLock = new Object();
    // TODO do we want a separate thread for handling mediasession messages?
    private final Handler mHandler = new Handler();

    private MediaSessionRecord mPrioritySession;
@@ -72,8 +71,8 @@ public class MediaSessionService extends SystemService implements Monitor {
    // session so we drop late callbacks properly.
    private int mShowRoutesRequestId = 0;

    // TODO refactor to have per user state. See MediaRouterService for an
    // example
    // TODO refactor to have per user state for providers. See
    // MediaRouterService for an example

    public MediaSessionService(Context context) {
        super(context);
@@ -211,25 +210,42 @@ public class MediaSessionService extends SystemService implements Monitor {
     * <ul>
     * <li>the caller has android.Manifest.permission.MEDIA_CONTENT_CONTROL
     * permission</li>
     * <li>the caller's listener is one of the enabled notification listeners</li>
     * <li>the caller's listener is one of the enabled notification listeners
     * for the caller's user</li>
     * </ul>
     */
    private void enforceMediaPermissions(ComponentName compName, int pid, int uid) {
    private void enforceMediaPermissions(ComponentName compName, int pid, int uid,
            int resolvedUserId) {
        if (getContext()
                .checkPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL, pid, uid)
                    != PackageManager.PERMISSION_GRANTED
                && !isEnabledNotificationListener(compName)) {
                && !isEnabledNotificationListener(compName, UserHandle.getUserId(uid),
                        resolvedUserId)) {
            throw new SecurityException("Missing permission to control media.");
        }
    }

    private boolean isEnabledNotificationListener(ComponentName compName) {
    /**
     * This checks if the component is an enabled notification listener for the
     * specified user. Enabled components may only operate on behalf of the user
     * they're running as.
     *
     * @param compName The component that is enabled.
     * @param userId The user id of the caller.
     * @param forUserId The user id they're making the request on behalf of.
     * @return True if the component is enabled, false otherwise
     */
    private boolean isEnabledNotificationListener(ComponentName compName, int userId,
            int forUserId) {
        if (userId != forUserId) {
            // You may not access another user's content as an enabled listener.
            return false;
        }
        if (compName != null) {
            final int currentUser = ActivityManager.getCurrentUser();
            final String enabledNotifListeners = Settings.Secure.getStringForUser(
                    getContext().getContentResolver(),
                    Settings.Secure.ENABLED_NOTIFICATION_LISTENERS,
                    currentUser);
                    userId);
            if (enabledNotifListeners != null) {
                final String[] components = enabledNotifListeners.split(":");
                for (int i = 0; i < components.length; i++) {
@@ -248,23 +264,23 @@ public class MediaSessionService extends SystemService implements Monitor {
            }
            if (DEBUG) {
                Log.d(TAG, "not ok to get sessions, " + compName +
                        " is not in list of ENABLED_NOTIFICATION_LISTENERS");
                        " is not in list of ENABLED_NOTIFICATION_LISTENERS for user " + userId);
            }
        }
        return false;
    }

    private MediaSessionRecord createSessionInternal(int pid, String packageName,
            ISessionCallback cb, String tag, boolean forCalls) {
    private MediaSessionRecord createSessionInternal(int callerPid, int callerUid, int userId,
            String callerPackageName, ISessionCallback cb, String tag) {
        synchronized (mLock) {
            return createSessionLocked(pid, packageName, cb, tag);
            return createSessionLocked(callerPid, callerUid, userId, callerPackageName, cb, tag);
        }
    }

    private MediaSessionRecord createSessionLocked(int pid, String packageName,
            ISessionCallback cb, String tag) {
        final MediaSessionRecord session = new MediaSessionRecord(pid, packageName, cb, tag, this,
                mHandler);
    private MediaSessionRecord createSessionLocked(int callerPid, int callerUid, int userId,
            String callerPackageName, ISessionCallback cb, String tag) {
        final MediaSessionRecord session = new MediaSessionRecord(callerPid, callerUid, userId,
                callerPackageName, cb, tag, this, mHandler);
        try {
            cb.asBinder().linkToDeath(session, 0);
        } catch (RemoteException e) {
@@ -273,7 +289,7 @@ public class MediaSessionService extends SystemService implements Monitor {
        mRecords.add(session);
        mPriorityStack.addSession(session);
        if (DEBUG) {
            Log.d(TAG, "Created session for package " + packageName + " with tag " + tag);
            Log.d(TAG, "Created session for package " + callerPackageName + " with tag " + tag);
        }
        return session;
    }
@@ -358,41 +374,50 @@ public class MediaSessionService extends SystemService implements Monitor {
        // ActivityManagerNative.handleIncomingUser and stash result for use
        // when starting services on that session's behalf.
        @Override
        public ISession createSession(String packageName, ISessionCallback cb, String tag)
                throws RemoteException {
        public ISession createSession(String packageName, ISessionCallback cb, String tag,
                int userId) throws RemoteException {
            final int pid = Binder.getCallingPid();
            final int uid = Binder.getCallingUid();
            final long token = Binder.clearCallingIdentity();
            try {
                enforcePackageName(packageName, uid);
                int resolvedUserId = ActivityManager.handleIncomingUser(pid, uid, userId,
                        false /* allowAll */, true /* requireFull */, "createSession", packageName);
                if (cb == null) {
                    throw new IllegalArgumentException("Controller callback cannot be null");
                }
                return createSessionInternal(pid, packageName, cb, tag, false).getSessionBinder();
                return createSessionInternal(pid, uid, resolvedUserId, packageName, cb, tag)
                        .getSessionBinder();
            } finally {
                Binder.restoreCallingIdentity(token);
            }
        }

        @Override
        public List<IBinder> getSessions(ComponentName componentName) {
        public List<IBinder> getSessions(ComponentName componentName, int userId) {
            final int pid = Binder.getCallingPid();
            final int uid = Binder.getCallingUid();
            final long token = Binder.clearCallingIdentity();

            try {
                String packageName = null;
                if (componentName != null) {
                    // If they gave us a component name verify they own the
                    // package
                    enforcePackageName(componentName.getPackageName(), uid);
                    packageName = componentName.getPackageName();
                    enforcePackageName(packageName, uid);
                }
                // Then check if they have the permissions or their component is
                // allowed
                enforceMediaPermissions(componentName, pid, uid);
                // Check that they can make calls on behalf of the user and
                // get the final user id
                int resolvedUserId = ActivityManager.handleIncomingUser(pid, uid, userId,
                        true /* allowAll */, true /* requireFull */, "getSessions", packageName);
                // Check if they have the permissions or their component is
                // enabled for the user they're calling from.
                enforceMediaPermissions(componentName, pid, uid, resolvedUserId);
                ArrayList<IBinder> binders = new ArrayList<IBinder>();
                synchronized (mLock) {
                    ArrayList<MediaSessionRecord> records = mPriorityStack
                            .getActiveSessions();
                            .getActiveSessions(resolvedUserId);
                    int size = records.size();
                    for (int i = 0; i < size; i++) {
                        binders.add(records.get(i).getControllerBinder().asBinder());
@@ -428,7 +453,7 @@ public class MediaSessionService extends SystemService implements Monitor {
                    mRecords.get(i).dump(pw, "");
                    pw.println();
                }
                mPriorityStack.dumpLocked(pw, "");
                mPriorityStack.dump(pw, "");

                pw.println("Providers:");
                count = mProviders.size();
+25 −11
Original line number Diff line number Diff line
@@ -18,6 +18,7 @@ package com.android.server.media;

import android.media.session.PlaybackState;
import android.media.session.Session;
import android.os.UserHandle;
import android.text.TextUtils;

import java.io.PrintWriter;
@@ -104,11 +105,12 @@ public class MediaSessionStack {
     * Get the current priority sorted list of active sessions. The most
     * important session is at index 0 and the least important at size - 1.
     *
     * @param userId The user to check.
     * @return All the active sessions in priority order.
     */
    public ArrayList<MediaSessionRecord> getActiveSessions() {
    public ArrayList<MediaSessionRecord> getActiveSessions(int userId) {
        if (mCachedActiveList == null) {
            mCachedActiveList = getPriorityListLocked(true, 0);
            mCachedActiveList = getPriorityListLocked(true, 0, userId);
        }
        return mCachedActiveList;
    }
@@ -118,13 +120,14 @@ public class MediaSessionStack {
     * transport controls. The most important session is at index 0 and the
     * least important at size -1.
     *
     * @param userId The user to check.
     * @return All the active sessions that handle transport controls in
     *         priority order.
     */
    public ArrayList<MediaSessionRecord> getTransportControlSessions() {
    public ArrayList<MediaSessionRecord> getTransportControlSessions(int userId) {
        if (mCachedTransportControlList == null) {
            mCachedTransportControlList = getPriorityListLocked(true,
                    Session.FLAG_HANDLES_TRANSPORT_CONTROLS);
                    Session.FLAG_HANDLES_TRANSPORT_CONTROLS, userId);
        }
        return mCachedTransportControlList;
    }
@@ -132,13 +135,14 @@ public class MediaSessionStack {
    /**
     * Get the highest priority active session.
     *
     * @param userId The user to check.
     * @return The current highest priority session or null.
     */
    public MediaSessionRecord getDefaultSession() {
    public MediaSessionRecord getDefaultSession(int userId) {
        if (mCachedDefault != null) {
            return mCachedDefault;
        }
        ArrayList<MediaSessionRecord> records = getPriorityListLocked(true, 0);
        ArrayList<MediaSessionRecord> records = getPriorityListLocked(true, 0, userId);
        if (records.size() > 0) {
            return records.get(0);
        }
@@ -148,22 +152,24 @@ public class MediaSessionStack {
    /**
     * Get the highest priority session that can handle media buttons.
     *
     * @param userId The user to check.
     * @return The default media button session or null.
     */
    public MediaSessionRecord getDefaultMediaButtonSession() {
    public MediaSessionRecord getDefaultMediaButtonSession(int userId) {
        if (mCachedButtonReceiver != null) {
            return mCachedButtonReceiver;
        }
        ArrayList<MediaSessionRecord> records = getPriorityListLocked(true,
                Session.FLAG_HANDLES_MEDIA_BUTTONS);
                Session.FLAG_HANDLES_MEDIA_BUTTONS, userId);
        if (records.size() > 0) {
            mCachedButtonReceiver = records.get(0);
        }
        return mCachedButtonReceiver;
    }

    public void dumpLocked(PrintWriter pw, String prefix) {
        ArrayList<MediaSessionRecord> sortedSessions = getPriorityListLocked(false, 0);
    public void dump(PrintWriter pw, String prefix) {
        ArrayList<MediaSessionRecord> sortedSessions = getPriorityListLocked(false, 0,
                UserHandle.USER_ALL);
        int count = sortedSessions.size();
        pw.println(prefix + "Sessions Stack - have " + count + " sessions:");
        String indent = prefix + "  ";
@@ -182,9 +188,12 @@ public class MediaSessionStack {
     *            all sessions.
     * @param withFlags Only return sessions with all the specified flags set. 0
     *            returns all sessions.
     * @param userId The user to get sessions for. {@link UserHandle#USER_ALL}
     *            will return sessions for all users.
     * @return The priority sorted list of sessions.
     */
    private ArrayList<MediaSessionRecord> getPriorityListLocked(boolean activeOnly, int withFlags) {
    private ArrayList<MediaSessionRecord> getPriorityListLocked(boolean activeOnly, int withFlags,
            int userId) {
        ArrayList<MediaSessionRecord> result = new ArrayList<MediaSessionRecord>();
        int lastLocalIndex = 0;
        int lastActiveIndex = 0;
@@ -194,7 +203,12 @@ public class MediaSessionStack {
        for (int i = 0; i < size; i++) {
            final MediaSessionRecord session = mSessions.get(i);

            if (userId != UserHandle.USER_ALL && userId != session.getUserId()) {
                // Filter out sessions for the wrong user
                continue;
            }
            if ((session.getFlags() & withFlags) != withFlags) {
                // Filter out sessions with the wrong flags
                continue;
            }
            if (!session.isActive()) {