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
diff --git a/media/java/android/media/session/ISessionManager.aidl b/media/java/android/media/session/ISessionManager.aidl
index 7a8c22e..e341647 100644
--- a/media/java/android/media/session/ISessionManager.aidl
+++ b/media/java/android/media/session/ISessionManager.aidl
@@ -25,6 +25,6 @@
  * @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
diff --git a/media/java/android/media/session/SessionManager.java b/media/java/android/media/session/SessionManager.java
index fd022fc..1eb3b7a 100644
--- a/media/java/android/media/session/SessionManager.java
+++ b/media/java/android/media/session/SessionManager.java
@@ -22,6 +22,7 @@
 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 @@
      * @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 @@
      * @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)));
diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java
index 015032b..41ab626 100644
--- a/services/core/java/com/android/server/media/MediaSessionRecord.java
+++ b/services/core/java/com/android/server/media/MediaSessionRecord.java
@@ -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.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 @@
 
     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 @@
 
     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();
@@ -187,6 +193,15 @@
     }
 
     /**
+     * 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 @@
         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);
diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java
index fb858fc..008f9be 100644
--- a/services/core/java/com/android/server/media/MediaSessionService.java
+++ b/services/core/java/com/android/server/media/MediaSessionService.java
@@ -63,7 +63,6 @@
     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 @@
     // 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 @@
      * <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 @@
             }
             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 @@
         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 @@
         // 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 @@
                     mRecords.get(i).dump(pw, "");
                     pw.println();
                 }
-                mPriorityStack.dumpLocked(pw, "");
+                mPriorityStack.dump(pw, "");
 
                 pw.println("Providers:");
                 count = mProviders.size();
diff --git a/services/core/java/com/android/server/media/MediaSessionStack.java b/services/core/java/com/android/server/media/MediaSessionStack.java
index f9f004d..1e1818d 100644
--- a/services/core/java/com/android/server/media/MediaSessionStack.java
+++ b/services/core/java/com/android/server/media/MediaSessionStack.java
@@ -18,6 +18,7 @@
 
 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 @@
      * 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 @@
      * 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 @@
     /**
      * 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 @@
     /**
      * 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 @@
      *            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 @@
         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()) {