summaryrefslogtreecommitdiffstats
path: root/services
diff options
context:
space:
mode:
authorRoboErik <epastern@google.com>2014-04-30 12:48:25 -0700
committerRoboErik <epastern@google.com>2014-05-06 14:06:29 -0700
commite7880d8eb1903d42e4e2a90c99b58e2240e01e82 (patch)
tree5741d2b360b17a5be6c26d804dde98f55705b01e /services
parentcedde01622a6798f5c4526ef1227bd906b6e59ef (diff)
downloadframeworks_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.java37
-rw-r--r--services/core/java/com/android/server/media/MediaSessionService.java124
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++) {