diff options
author | RoboErik <epastern@google.com> | 2014-04-30 12:48:25 -0700 |
---|---|---|
committer | RoboErik <epastern@google.com> | 2014-05-06 14:06:29 -0700 |
commit | e7880d8eb1903d42e4e2a90c99b58e2240e01e82 (patch) | |
tree | 5741d2b360b17a5be6c26d804dde98f55705b01e /services | |
parent | cedde01622a6798f5c4526ef1227bd906b6e59ef (diff) | |
download | frameworks_base-e7880d8eb1903d42e4e2a90c99b58e2240e01e82.zip frameworks_base-e7880d8eb1903d42e4e2a90c99b58e2240e01e82.tar.gz frameworks_base-e7880d8eb1903d42e4e2a90c99b58e2240e01e82.tar.bz2 |
Add APIs for creating a system priority session and getting controllers
This adds a hidden call to set flags and a flag for making a session an
exclusive high priority session. This will cause all media button events
to be sent to that session as long as it is stillr egistered. This
requires the MODIFY_PHONE_STATE permission like the old forCalls API.
This also adds a way to get controllers for all the ongoing sessions.
This is protected by the MEDIA_CONTENT_CONTROL permission like the
old RemoteController APIs.
Change-Id: I51540e8dcf3a7dbe02a0f8ee003821e40af653a3
Diffstat (limited to 'services')
-rw-r--r-- | services/core/java/com/android/server/media/MediaSessionRecord.java | 37 | ||||
-rw-r--r-- | services/core/java/com/android/server/media/MediaSessionService.java | 124 |
2 files changed, 158 insertions, 3 deletions
diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java index 3dc17fc..e4e5979 100644 --- a/services/core/java/com/android/server/media/MediaSessionRecord.java +++ b/services/core/java/com/android/server/media/MediaSessionRecord.java @@ -17,6 +17,7 @@ package com.android.server.media; import android.content.Intent; +import android.content.pm.PackageManager; import android.media.routeprovider.RouteRequest; import android.media.session.ISessionController; import android.media.session.ISessionControllerCallback; @@ -80,6 +81,7 @@ public class MediaSessionRecord implements IBinder.DeathRecipient { private RouteConnectionRecord mConnection; // TODO define a RouteState class with relevant info private int mRouteState; + private long mFlags; // TransportPerformer fields @@ -148,6 +150,25 @@ public class MediaSessionRecord implements IBinder.DeathRecipient { } /** + * Get this session's flags. + * + * @return The flags for this session. + */ + public long getFlags() { + return mFlags; + } + + /** + * Check if this session has system priorty and should receive media buttons + * before any other sessions. + * + * @return True if this is a system priority session, false otherwise + */ + public boolean isSystemPriority() { + return (mFlags & Session.FLAG_EXCLUSIVE_GLOBAL_PRIORITY) != 0; + } + + /** * Set the selected route. This does not connect to the route, just notifies * the app that a new route has been selected. * @@ -394,7 +415,8 @@ public class MediaSessionRecord implements IBinder.DeathRecipient { @Override public void publish() { - mIsPublished = true; // TODO push update to service + mIsPublished = true; + mService.publishSession(MediaSessionRecord.this); } @Override public void setTransportPerformerEnabled() { @@ -402,6 +424,19 @@ public class MediaSessionRecord implements IBinder.DeathRecipient { } @Override + public void setFlags(long flags) { + if ((flags & Session.FLAG_EXCLUSIVE_GLOBAL_PRIORITY) != 0) { + int pid = getCallingPid(); + int uid = getCallingUid(); + mService.enforcePhoneStatePermission(pid, uid); + } + if (mIsPublished) { + throw new IllegalStateException("Cannot set flags after publishing session."); + } + mFlags = flags; + } + + @Override public void setMetadata(MediaMetadata metadata) { mMetadata = metadata; mHandler.post(MessageHandler.MSG_UPDATE_METADATA); diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java index 107f6ad..3035521 100644 --- a/services/core/java/com/android/server/media/MediaSessionService.java +++ b/services/core/java/com/android/server/media/MediaSessionService.java @@ -17,17 +17,24 @@ package com.android.server.media; import android.Manifest; +import android.app.ActivityManager; +import android.content.ComponentName; import android.content.Context; import android.content.pm.PackageManager; import android.media.routeprovider.RouteRequest; import android.media.session.ISession; import android.media.session.ISessionCallback; +import android.media.session.ISessionController; import android.media.session.ISessionManager; import android.media.session.RouteInfo; import android.media.session.RouteOptions; import android.os.Binder; import android.os.Handler; +import android.os.IBinder; +import android.os.Process; import android.os.RemoteException; +import android.os.UserHandle; +import android.provider.Settings; import android.text.TextUtils; import android.util.Log; @@ -38,6 +45,7 @@ import com.android.server.Watchdog.Monitor; import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.ArrayList; +import java.util.List; /** * System implementation of MediaSessionManager @@ -57,6 +65,8 @@ public class MediaSessionService extends SystemService implements Monitor { // TODO do we want a separate thread for handling mediasession messages? private final Handler mHandler = new Handler(); + private MediaSessionRecord mPrioritySession; + // Used to keep track of the current request to show routes for a specific // session so we drop late callbacks properly. private int mShowRoutesRequestId = 0; @@ -121,6 +131,17 @@ public class MediaSessionService extends SystemService implements Monitor { } } + public void publishSession(MediaSessionRecord record) { + synchronized (mLock) { + if (record.isSystemPriority()) { + if (mPrioritySession != null) { + Log.w(TAG, "Replacing existing priority session with a new session"); + } + mPrioritySession = record; + } + } + } + @Override public void monitor() { synchronized (mLock) { @@ -142,6 +163,9 @@ public class MediaSessionService extends SystemService implements Monitor { private void destroySessionLocked(MediaSessionRecord session) { mSessions.remove(session); + if (session == mPrioritySession) { + mPrioritySession = null; + } } private void enforcePackageName(String packageName, int uid) { @@ -158,8 +182,64 @@ public class MediaSessionService extends SystemService implements Monitor { throw new IllegalArgumentException("packageName is not owned by the calling process"); } + protected void enforcePhoneStatePermission(int pid, int uid) { + if (getContext().checkPermission(android.Manifest.permission.MODIFY_PHONE_STATE, pid, uid) + != PackageManager.PERMISSION_GRANTED) { + throw new SecurityException("Must hold the MODIFY_PHONE_STATE permission."); + } + } + + /** + * Checks a caller's authorization to register an IRemoteControlDisplay. + * Authorization is granted if one of the following is true: + * <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> + * </ul> + */ + private void enforceMediaPermissions(ComponentName compName, int pid, int uid) { + if (getContext() + .checkPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL, pid, uid) + != PackageManager.PERMISSION_GRANTED + && !isEnabledNotificationListener(compName)) { + throw new SecurityException("Missing permission to control media."); + } + } + + private boolean isEnabledNotificationListener(ComponentName compName) { + if (compName != null) { + final int currentUser = ActivityManager.getCurrentUser(); + final String enabledNotifListeners = Settings.Secure.getStringForUser( + getContext().getContentResolver(), + Settings.Secure.ENABLED_NOTIFICATION_LISTENERS, + currentUser); + if (enabledNotifListeners != null) { + final String[] components = enabledNotifListeners.split(":"); + for (int i = 0; i < components.length; i++) { + final ComponentName component = + ComponentName.unflattenFromString(components[i]); + if (component != null) { + if (compName.equals(component)) { + if (DEBUG) { + Log.d(TAG, "ok to get sessions: " + component + + " is authorized notification listener"); + } + return true; + } + } + } + } + if (DEBUG) { + Log.d(TAG, "not ok to get sessions, " + compName + + " is not in list of ENABLED_NOTIFICATION_LISTENERS"); + } + } + return false; + } + private MediaSessionRecord createSessionInternal(int pid, String packageName, - ISessionCallback cb, String tag) { + ISessionCallback cb, String tag, boolean forCalls) { synchronized (mLock) { return createSessionLocked(pid, packageName, cb, tag); } @@ -201,6 +281,11 @@ public class MediaSessionService extends SystemService implements Monitor { return -1; } + private boolean isSessionDiscoverable(MediaSessionRecord record) { + // TODO probably want to check more than if it's published. + return record.isPublished(); + } + private MediaRouteProviderWatcher.Callback mProviderWatcherCallback = new MediaRouteProviderWatcher.Callback() { @Override @@ -266,7 +351,38 @@ public class MediaSessionService extends SystemService implements Monitor { if (cb == null) { throw new IllegalArgumentException("Controller callback cannot be null"); } - return createSessionInternal(pid, packageName, cb, tag).getSessionBinder(); + return createSessionInternal(pid, packageName, cb, tag, false).getSessionBinder(); + } finally { + Binder.restoreCallingIdentity(token); + } + } + + @Override + public List<IBinder> getSessions(ComponentName componentName) { + + final int pid = Binder.getCallingPid(); + final int uid = Binder.getCallingUid(); + final long token = Binder.clearCallingIdentity(); + + try { + if (componentName != null) { + // If they gave us a component name verify they own the + // package + enforcePackageName(componentName.getPackageName(), uid); + } + // Then check if they have the permissions or their component is + // allowed + enforceMediaPermissions(componentName, pid, uid); + ArrayList<IBinder> binders = new ArrayList<IBinder>(); + synchronized (mLock) { + for (int i = mSessions.size() - 1; i >= 0; i--) { + MediaSessionRecord record = mSessions.get(i); + if (isSessionDiscoverable(record)) { + binders.add(record.getControllerBinder().asBinder()); + } + } + } + return binders; } finally { Binder.restoreCallingIdentity(token); } @@ -286,6 +402,10 @@ public class MediaSessionService extends SystemService implements Monitor { pw.println(); synchronized (mLock) { + pw.println("Session for calls:" + mPrioritySession); + if (mPrioritySession != null) { + mPrioritySession.dump(pw, ""); + } int count = mSessions.size(); pw.println("Sessions - have " + count + " states:"); for (int i = 0; i < count; i++) { |