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
This commit is contained in:
@@ -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);
|
||||
}
|
||||
@@ -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)));
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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()) {
|
||||
|
||||
Reference in New Issue
Block a user