Merge "MediaSession2: Protect MediaSessionManager APIs with permission" into pi-dev
am: 9e5a7f2a63
Change-Id: Id70eb6d8fe60a8755b73bfe6be2f93c2c3de6bcb
This commit is contained in:
@@ -52,12 +52,13 @@ interface ISessionManager {
|
||||
void setOnMediaKeyListener(in IOnMediaKeyListener listener);
|
||||
|
||||
// MediaSession2
|
||||
boolean isTrusted(int uid, String packageName);
|
||||
boolean isTrusted(String controllerPackageName, int controllerPid, int controllerUid);
|
||||
boolean createSession2(in Bundle sessionToken);
|
||||
void destroySession2(in Bundle sessionToken);
|
||||
List<Bundle> getSessionTokens(boolean activeSessionOnly, boolean sessionServiceOnly);
|
||||
List<Bundle> getSessionTokens(boolean activeSessionOnly, boolean sessionServiceOnly,
|
||||
String packageName);
|
||||
|
||||
void addSessionTokensListener(in ISessionTokensListener listener, int userId,
|
||||
String packageName);
|
||||
void removeSessionTokensListener(in ISessionTokensListener listener);
|
||||
void removeSessionTokensListener(in ISessionTokensListener listener, String packageName);
|
||||
}
|
||||
|
||||
@@ -342,16 +342,17 @@ public final class MediaSessionManager {
|
||||
/**
|
||||
* Returns whether the api
|
||||
*
|
||||
* @param uid uid of the app
|
||||
* @param packageName packageName
|
||||
* @param pid pid of the app
|
||||
* @param uid uid of the app
|
||||
* @hide
|
||||
*/
|
||||
public boolean isTrusted(int uid, @NonNull String packageName) {
|
||||
public boolean isTrusted(@NonNull String packageName, int pid, int uid) {
|
||||
if (packageName == null) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
return mService.isTrusted(uid, packageName);
|
||||
return mService.isTrusted(packageName, pid, uid);
|
||||
} catch (RemoteException e) {
|
||||
Log.wtf(TAG, "Cannot communicate with the service.", e);
|
||||
}
|
||||
@@ -404,7 +405,8 @@ public final class MediaSessionManager {
|
||||
public List<SessionToken2> getActiveSessionTokens() {
|
||||
try {
|
||||
List<Bundle> bundles = mService.getSessionTokens(
|
||||
/* activeSessionOnly */ true, /* sessionServiceOnly */ false);
|
||||
/* activeSessionOnly */ true, /* sessionServiceOnly */ false,
|
||||
mContext.getPackageName());
|
||||
return toTokenList(mContext, bundles);
|
||||
} catch (RemoteException e) {
|
||||
Log.wtf(TAG, "Cannot communicate with the service.", e);
|
||||
@@ -426,7 +428,8 @@ public final class MediaSessionManager {
|
||||
public List<SessionToken2> getSessionServiceTokens() {
|
||||
try {
|
||||
List<Bundle> bundles = mService.getSessionTokens(
|
||||
/* activeSessionOnly */ false, /* sessionServiceOnly */ true);
|
||||
/* activeSessionOnly */ false, /* sessionServiceOnly */ true,
|
||||
mContext.getPackageName());
|
||||
return toTokenList(mContext, bundles);
|
||||
} catch (RemoteException e) {
|
||||
Log.wtf(TAG, "Cannot communicate with the service.", e);
|
||||
@@ -450,7 +453,8 @@ public final class MediaSessionManager {
|
||||
public List<SessionToken2> getAllSessionTokens() {
|
||||
try {
|
||||
List<Bundle> bundles = mService.getSessionTokens(
|
||||
/* activeSessionOnly */ false, /* sessionServiceOnly */ false);
|
||||
/* activeSessionOnly */ false, /* sessionServiceOnly */ false,
|
||||
mContext.getPackageName());
|
||||
return toTokenList(mContext, bundles);
|
||||
} catch (RemoteException e) {
|
||||
Log.wtf(TAG, "Cannot communicate with the service.", e);
|
||||
@@ -526,7 +530,7 @@ public final class MediaSessionManager {
|
||||
SessionTokensChangedWrapper wrapper = mSessionTokensListener.remove(listener);
|
||||
if (wrapper != null) {
|
||||
try {
|
||||
mService.removeSessionTokensListener(wrapper.mStub);
|
||||
mService.removeSessionTokensListener(wrapper.mStub, mContext.getPackageName());
|
||||
} catch (RemoteException e) {
|
||||
Log.e(TAG, "Error in removeSessionTokensListener.", e);
|
||||
} finally {
|
||||
|
||||
@@ -608,7 +608,7 @@ public class MediaSessionService extends SystemService implements Monitor {
|
||||
*/
|
||||
private void enforceMediaPermissions(ComponentName compName, int pid, int uid,
|
||||
int resolvedUserId) {
|
||||
if (isCurrentVolumeController(uid, pid)) return;
|
||||
if (isCurrentVolumeController(pid, uid)) return;
|
||||
if (getContext()
|
||||
.checkPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL, pid, uid)
|
||||
!= PackageManager.PERMISSION_GRANTED
|
||||
@@ -618,13 +618,13 @@ public class MediaSessionService extends SystemService implements Monitor {
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isCurrentVolumeController(int uid, int pid) {
|
||||
private boolean isCurrentVolumeController(int pid, int uid) {
|
||||
return getContext().checkPermission(android.Manifest.permission.STATUS_BAR_SERVICE,
|
||||
pid, uid) == PackageManager.PERMISSION_GRANTED;
|
||||
}
|
||||
|
||||
private void enforceSystemUiPermission(String action, int pid, int uid) {
|
||||
if (!isCurrentVolumeController(uid, pid)) {
|
||||
if (!isCurrentVolumeController(pid, uid)) {
|
||||
throw new SecurityException("Only system ui may " + action);
|
||||
}
|
||||
}
|
||||
@@ -1501,53 +1501,21 @@ public class MediaSessionService extends SystemService implements Monitor {
|
||||
* Returns if the controller's package is trusted (i.e. has either MEDIA_CONTENT_CONTROL
|
||||
* permission or an enabled notification listener)
|
||||
*
|
||||
* @param uid uid of the controller app
|
||||
* @param packageName package name of the controller app
|
||||
* @param controllerPackageName package name of the controller app
|
||||
* @param controllerPid pid of the controller app
|
||||
* @param controllerUid uid of the controller app
|
||||
*/
|
||||
@Override
|
||||
public boolean isTrusted(int uid, String packageName) throws RemoteException {
|
||||
public boolean isTrusted(String controllerPackageName, int controllerPid, int controllerUid)
|
||||
throws RemoteException {
|
||||
final int uid = Binder.getCallingUid();
|
||||
final long token = Binder.clearCallingIdentity();
|
||||
try {
|
||||
int userId = UserHandle.getUserId(uid);
|
||||
// Sanity check whether uid and packageName matches
|
||||
if (uid != mPackageManager.getPackageUid(packageName, 0, userId)) {
|
||||
throw new IllegalArgumentException("uid=" + uid + " and packageName="
|
||||
+ packageName + " doesn't match");
|
||||
}
|
||||
|
||||
// Check if it's system server or has MEDIA_CONTENT_CONTROL.
|
||||
// Note that system server doesn't have MEDIA_CONTENT_CONTROL, so we need extra
|
||||
// check here.
|
||||
if (uid == Process.SYSTEM_UID || mPackageManager.checkPermission(
|
||||
android.Manifest.permission.MEDIA_CONTENT_CONTROL, packageName, uid)
|
||||
== PackageManager.PERMISSION_GRANTED) {
|
||||
return true;
|
||||
}
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, packageName + " (uid=" + uid + ") hasn't granted"
|
||||
+ " MEDIA_CONTENT_CONTROL");
|
||||
}
|
||||
|
||||
// TODO(jaewan): Add hasEnabledNotificationListener(String pkgName) for
|
||||
// optimization (Post-P)
|
||||
final List<ComponentName> enabledNotificationListeners =
|
||||
mNotificationManager.getEnabledNotificationListeners(userId);
|
||||
if (enabledNotificationListeners != null) {
|
||||
for (int i = 0; i < enabledNotificationListeners.size(); i++) {
|
||||
if (TextUtils.equals(packageName,
|
||||
enabledNotificationListeners.get(i).getPackageName())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return hasMediaControlPermission(UserHandle.getUserId(uid), controllerPackageName,
|
||||
controllerPid, controllerUid);
|
||||
} finally {
|
||||
Binder.restoreCallingIdentity(token);
|
||||
}
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, packageName + " (uid=" + uid + ") doesn't have an enabled notification"
|
||||
+ " listener");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1614,60 +1582,85 @@ public class MediaSessionService extends SystemService implements Monitor {
|
||||
destroySession2Internal(token);
|
||||
}
|
||||
|
||||
// TODO(jaewan): Protect this API with permission (b/73226436)
|
||||
// TODO(jaewan): Make this API take userId as an argument (b/73597722)
|
||||
@Override
|
||||
public List<Bundle> getSessionTokens(boolean activeSessionOnly,
|
||||
boolean sessionServiceOnly) throws RemoteException {
|
||||
boolean sessionServiceOnly, String packageName) throws RemoteException {
|
||||
final int pid = Binder.getCallingPid();
|
||||
final int uid = Binder.getCallingUid();
|
||||
final long token = Binder.clearCallingIdentity();
|
||||
|
||||
List<Bundle> tokens = new ArrayList<>();
|
||||
synchronized (mLock) {
|
||||
for (Map.Entry<SessionToken2, MediaController2> record
|
||||
: mSessionRecords.entrySet()) {
|
||||
boolean isSessionService = (record.getKey().getType() != TYPE_SESSION);
|
||||
boolean isActive = record.getValue() != null;
|
||||
if ((activeSessionOnly && !isActive)
|
||||
|| (sessionServiceOnly && !isSessionService) ){
|
||||
continue;
|
||||
try {
|
||||
verifySessionsRequest2(UserHandle.getUserId(uid), packageName, pid, uid);
|
||||
synchronized (mLock) {
|
||||
for (Map.Entry<SessionToken2, MediaController2> record
|
||||
: mSessionRecords.entrySet()) {
|
||||
boolean isSessionService = (record.getKey().getType() != TYPE_SESSION);
|
||||
boolean isActive = record.getValue() != null;
|
||||
if ((activeSessionOnly && !isActive)
|
||||
|| (sessionServiceOnly && !isSessionService)) {
|
||||
continue;
|
||||
}
|
||||
tokens.add(record.getKey().toBundle());
|
||||
}
|
||||
tokens.add(record.getKey().toBundle());
|
||||
}
|
||||
} finally {
|
||||
Binder.restoreCallingIdentity(token);
|
||||
}
|
||||
return tokens;
|
||||
}
|
||||
|
||||
// TODO(jaewan): Protect this API with permission (b/73226436)
|
||||
// TODO(jaewan): "userId != calling user" needs extra protection (b/73226436)
|
||||
@Override
|
||||
public void addSessionTokensListener(ISessionTokensListener listener, int userId,
|
||||
String packageName) {
|
||||
synchronized (mLock) {
|
||||
final SessionTokensListenerRecord record =
|
||||
new SessionTokensListenerRecord(listener, userId);
|
||||
try {
|
||||
listener.asBinder().linkToDeath(record, 0);
|
||||
} catch (RemoteException e) {
|
||||
String packageName) throws RemoteException {
|
||||
final int pid = Binder.getCallingPid();
|
||||
final int uid = Binder.getCallingUid();
|
||||
final long token = Binder.clearCallingIdentity();
|
||||
try {
|
||||
int resolvedUserId = verifySessionsRequest2(userId, packageName, pid, uid);
|
||||
synchronized (mLock) {
|
||||
final SessionTokensListenerRecord record =
|
||||
new SessionTokensListenerRecord(listener, resolvedUserId);
|
||||
try {
|
||||
listener.asBinder().linkToDeath(record, 0);
|
||||
} catch (RemoteException e) {
|
||||
}
|
||||
mSessionTokensListeners.add(record);
|
||||
}
|
||||
mSessionTokensListeners.add(record);
|
||||
} finally {
|
||||
Binder.restoreCallingIdentity(token);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(jaewan): Protect this API with permission (b/73226436)
|
||||
// TODO(jaewan): Make this API take userId as an argument (b/73597722)
|
||||
@Override
|
||||
public void removeSessionTokensListener(ISessionTokensListener listener) {
|
||||
synchronized (mLock) {
|
||||
IBinder listenerBinder = listener.asBinder();
|
||||
for (SessionTokensListenerRecord record : mSessionTokensListeners) {
|
||||
if (listenerBinder.equals(record.mListener.asBinder())) {
|
||||
try {
|
||||
listenerBinder.unlinkToDeath(record, 0);
|
||||
} catch (NoSuchElementException e) {
|
||||
public void removeSessionTokensListener(ISessionTokensListener listener,
|
||||
String packageName) throws RemoteException {
|
||||
final int pid = Binder.getCallingPid();
|
||||
final int uid = Binder.getCallingUid();
|
||||
final long token = Binder.clearCallingIdentity();
|
||||
try {
|
||||
verifySessionsRequest2(UserHandle.getUserId(uid), packageName, pid, uid);
|
||||
synchronized (mLock) {
|
||||
IBinder listenerBinder = listener.asBinder();
|
||||
for (SessionTokensListenerRecord record : mSessionTokensListeners) {
|
||||
if (listenerBinder.equals(record.mListener.asBinder())) {
|
||||
try {
|
||||
listenerBinder.unlinkToDeath(record, 0);
|
||||
} catch (NoSuchElementException e) {
|
||||
}
|
||||
mSessionTokensListeners.remove(record);
|
||||
break;
|
||||
}
|
||||
mSessionTokensListeners.remove(record);
|
||||
break;
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
Binder.restoreCallingIdentity(token);
|
||||
}
|
||||
}
|
||||
|
||||
// For MediaSession
|
||||
private int verifySessionsRequest(ComponentName componentName, int userId, final int pid,
|
||||
final int uid) {
|
||||
String packageName = null;
|
||||
@@ -1687,6 +1680,66 @@ public class MediaSessionService extends SystemService implements Monitor {
|
||||
return resolvedUserId;
|
||||
}
|
||||
|
||||
// For MediaSession2
|
||||
private int verifySessionsRequest2(int targetUserId, String callerPackageName,
|
||||
int callerPid, int callerUid) throws RemoteException {
|
||||
// Check that they can make calls on behalf of the user and get the final user id.
|
||||
int resolvedUserId = ActivityManager.handleIncomingUser(callerPid, callerUid,
|
||||
targetUserId, true /* allowAll */, true /* requireFull */, "getSessionTokens",
|
||||
callerPackageName);
|
||||
// Check if they have the permissions or their component is
|
||||
// enabled for the user they're calling from.
|
||||
if (!hasMediaControlPermission(
|
||||
resolvedUserId, callerPackageName, callerPid, callerUid)) {
|
||||
throw new SecurityException("Missing permission to control media.");
|
||||
}
|
||||
return resolvedUserId;
|
||||
}
|
||||
|
||||
// For MediaSession2
|
||||
private boolean hasMediaControlPermission(int resolvedUserId, String packageName,
|
||||
int pid, int uid) throws RemoteException {
|
||||
// Allow API calls from the System UI
|
||||
if (isCurrentVolumeController(pid, uid)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check if it's system server or has MEDIA_CONTENT_CONTROL.
|
||||
// Note that system server doesn't have MEDIA_CONTENT_CONTROL, so we need extra
|
||||
// check here.
|
||||
if (uid == Process.SYSTEM_UID || getContext().checkPermission(
|
||||
android.Manifest.permission.MEDIA_CONTENT_CONTROL, pid, uid)
|
||||
== PackageManager.PERMISSION_GRANTED) {
|
||||
return true;
|
||||
} else if (DEBUG) {
|
||||
Log.d(TAG, packageName + " (uid=" + uid + ") hasn't granted MEDIA_CONTENT_CONTROL");
|
||||
}
|
||||
|
||||
// You may not access another user's content as an enabled listener.
|
||||
final int userId = UserHandle.getUserId(uid);
|
||||
if (resolvedUserId != userId) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO(jaewan): (Post-P) Propose NotificationManager#hasEnabledNotificationListener(
|
||||
// String pkgName) to notification team for optimization
|
||||
final List<ComponentName> enabledNotificationListeners =
|
||||
mNotificationManager.getEnabledNotificationListeners(userId);
|
||||
if (enabledNotificationListeners != null) {
|
||||
for (int i = 0; i < enabledNotificationListeners.size(); i++) {
|
||||
if (TextUtils.equals(packageName,
|
||||
enabledNotificationListeners.get(i).getPackageName())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, packageName + " (uid=" + uid + ") doesn't have an enabled "
|
||||
+ "notification listener");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void dispatchAdjustVolumeLocked(int suggestedStream, int direction, int flags) {
|
||||
MediaSessionRecord session = isGlobalPriorityActiveLocked() ? mGlobalPrioritySession
|
||||
: mCurrentFullUserRecord.mPriorityStack.getDefaultVolumeSession();
|
||||
|
||||
Reference in New Issue
Block a user