summaryrefslogtreecommitdiffstats
path: root/services/core/java/com/android/server/media
diff options
context:
space:
mode:
authorMichael Wright <michaelwr@google.com>2014-08-12 19:27:54 -0700
committerMichael Wright <michaelwr@google.com>2014-08-17 16:06:56 -0700
commitd86ecd26d78c7bcd7f820b9ef5e3f4c3eb037510 (patch)
tree977c5062c44726daebdd6d2803f09f8c6f77b1ba /services/core/java/com/android/server/media
parent804322564b1b4e4ccff0b1c8cd312bac75ec5bb0 (diff)
downloadframeworks_base-d86ecd26d78c7bcd7f820b9ef5e3f4c3eb037510.zip
frameworks_base-d86ecd26d78c7bcd7f820b9ef5e3f4c3eb037510.tar.gz
frameworks_base-d86ecd26d78c7bcd7f820b9ef5e3f4c3eb037510.tar.bz2
Add callbacks for MediaProjection start / stop.
Also, enforce that there is only one valid MediaProjection at a time. Bug: 16488053 Change-Id: Id05445d798c98cb208bc4dab186296392e15d30b
Diffstat (limited to 'services/core/java/com/android/server/media')
-rw-r--r--services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java345
1 files changed, 299 insertions, 46 deletions
diff --git a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
index 289b5aa..69d1dc9 100644
--- a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
+++ b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
@@ -26,6 +26,8 @@ import android.hardware.display.DisplayManager;
import android.media.projection.IMediaProjectionManager;
import android.media.projection.IMediaProjection;
import android.media.projection.IMediaProjectionCallback;
+import android.media.projection.IMediaProjectionWatcherCallback;
+import android.media.projection.MediaProjectionInfo;
import android.media.projection.MediaProjectionManager;
import android.os.Binder;
import android.os.Handler;
@@ -34,6 +36,7 @@ import android.os.IBinder.DeathRecipient;
import android.os.Looper;
import android.os.Message;
import android.os.RemoteException;
+import android.os.UserHandle;
import android.util.ArrayMap;
import android.util.Slog;
@@ -59,15 +62,20 @@ public final class MediaProjectionManagerService extends SystemService
private static final String TAG = "MediaProjectionManagerService";
private final Object mLock = new Object(); // Protects the list of media projections
- private final Map<IBinder, MediaProjection> mProjectionGrants;
+ private final Map<IBinder, IBinder.DeathRecipient> mDeathEaters;
+ private final CallbackDelegate mCallbackDelegate;
private final Context mContext;
private final AppOpsManager mAppOps;
+ private IBinder mProjectionToken;
+ private MediaProjection mProjectionGrant;
+
public MediaProjectionManagerService(Context context) {
super(context);
mContext = context;
- mProjectionGrants = new ArrayMap<IBinder, MediaProjection>();
+ mDeathEaters = new ArrayMap<IBinder, IBinder.DeathRecipient>();
+ mCallbackDelegate = new CallbackDelegate();
mAppOps = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE);
Watchdog.getInstance().addMonitor(this);
}
@@ -83,13 +91,97 @@ public final class MediaProjectionManagerService extends SystemService
synchronized (mLock) { /* check for deadlock */ }
}
+ private void startProjectionLocked(final MediaProjection projection) {
+ if (mProjectionGrant != null) {
+ mProjectionGrant.stop();
+ }
+ mProjectionToken = projection.asBinder();
+ mProjectionGrant = projection;
+ dispatchStart(projection);
+ }
+
+ private void stopProjectionLocked(final MediaProjection projection) {
+ mProjectionToken = null;
+ mProjectionGrant = null;
+ dispatchStop(projection);
+ }
+
+ private void addCallback(final IMediaProjectionWatcherCallback callback) {
+ IBinder.DeathRecipient deathRecipient = new IBinder.DeathRecipient() {
+ @Override
+ public void binderDied() {
+ synchronized (mLock) {
+ unlinkDeathRecipientLocked(callback);
+ removeCallback(callback);
+ }
+ }
+ };
+ synchronized (mLock) {
+ mCallbackDelegate.add(callback);
+ linkDeathRecipientLocked(callback, deathRecipient);
+ }
+ }
+
+ private void removeCallback(IMediaProjectionWatcherCallback callback) {
+ synchronized (mLock) {
+ unlinkDeathRecipientLocked(callback);
+ removeCallback(callback);
+ }
+ }
+
+ private void linkDeathRecipientLocked(IMediaProjectionWatcherCallback callback,
+ IBinder.DeathRecipient deathRecipient) {
+ try {
+ final IBinder token = callback.asBinder();
+ token.linkToDeath(deathRecipient, 0);
+ mDeathEaters.put(token, deathRecipient);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Unable to link to death for media projection monitoring callback", e);
+ }
+ }
+
+ private void unlinkDeathRecipientLocked(IMediaProjectionWatcherCallback callback) {
+ final IBinder token = callback.asBinder();
+ IBinder.DeathRecipient deathRecipient = mDeathEaters.remove(token);
+ if (deathRecipient != null) {
+ token.unlinkToDeath(deathRecipient, 0);
+ }
+ }
+
+ private void dispatchStart(MediaProjection projection) {
+ mCallbackDelegate.dispatchStart(projection);
+ }
+
+ private void dispatchStop(MediaProjection projection) {
+ mCallbackDelegate.dispatchStop(projection);
+ }
+
+ private boolean isValidMediaProjection(IBinder token) {
+ synchronized (mLock) {
+ if (mProjectionToken != null) {
+ return mProjectionToken.equals(token);
+ }
+ return false;
+ }
+ }
+
+ private MediaProjectionInfo getActiveProjectionInfo() {
+ synchronized (mLock) {
+ if (mProjectionGrant == null) {
+ return null;
+ }
+ return mProjectionGrant.getProjectionInfo();
+ }
+ }
+
private void dump(final PrintWriter pw) {
pw.println("MEDIA PROJECTION MANAGER (dumpsys media_projection)");
synchronized (mLock) {
- Collection<MediaProjection> projections = mProjectionGrants.values();
- pw.println("Media Projections: size=" + projections.size());
- for (MediaProjection mp : projections) {
- mp.dump(pw, " ");
+ pw.println("Media Projection: ");
+ if (mProjectionGrant != null ) {
+ mProjectionGrant.dump(pw);
+ } else {
+ pw.println("null");
}
}
}
@@ -115,11 +207,14 @@ public final class MediaProjectionManagerService extends SystemService
@Override // Binder call
public IMediaProjection createProjection(int uid, String packageName, int type,
boolean isPermanentGrant) {
- if (mContext.checkCallingPermission(Manifest.permission.CREATE_MEDIA_PROJECTION)
+ if (mContext.checkCallingPermission(Manifest.permission.MANAGE_MEDIA_PROJECTION)
!= PackageManager.PERMISSION_GRANTED) {
- throw new SecurityException("Requires CREATE_MEDIA_PROJECTION in order to grant "
+ throw new SecurityException("Requires MANAGE_MEDIA_PROJECTION in order to grant "
+ "projection permission");
}
+ if (packageName == null || packageName.isEmpty()) {
+ throw new IllegalArgumentException("package name must not be empty");
+ }
long callingToken = Binder.clearCallingIdentity();
MediaProjection projection;
try {
@@ -136,7 +231,71 @@ public final class MediaProjectionManagerService extends SystemService
@Override // Binder call
public boolean isValidMediaProjection(IMediaProjection projection) {
- return mProjectionGrants.containsKey(projection.asBinder());
+ return MediaProjectionManagerService.this.isValidMediaProjection(
+ projection.asBinder());
+ }
+
+ @Override // Binder call
+ public MediaProjectionInfo getActiveProjectionInfo() {
+ if (mContext.checkCallingPermission(Manifest.permission.MANAGE_MEDIA_PROJECTION)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("Requires MANAGE_MEDIA_PROJECTION in order to add "
+ + "projection callbacks");
+ }
+ final long token = Binder.clearCallingIdentity();
+ try {
+ return MediaProjectionManagerService.this.getActiveProjectionInfo();
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ @Override // Binder call
+ public void stopActiveProjection() {
+ if (mContext.checkCallingPermission(Manifest.permission.MANAGE_MEDIA_PROJECTION)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("Requires MANAGE_MEDIA_PROJECTION in order to add "
+ + "projection callbacks");
+ }
+ final long token = Binder.clearCallingIdentity();
+ try {
+ if (mProjectionGrant != null) {
+ mProjectionGrant.stop();
+ }
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+
+ }
+
+ @Override //Binder call
+ public void addCallback(final IMediaProjectionWatcherCallback callback) {
+ if (mContext.checkCallingPermission(Manifest.permission.MANAGE_MEDIA_PROJECTION)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("Requires MANAGE_MEDIA_PROJECTION in order to add "
+ + "projection callbacks");
+ }
+ final long token = Binder.clearCallingIdentity();
+ try {
+ MediaProjectionManagerService.this.addCallback(callback);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ @Override
+ public void removeCallback(IMediaProjectionWatcherCallback callback) {
+ if (mContext.checkCallingPermission(Manifest.permission.MANAGE_MEDIA_PROJECTION)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("Requires MANAGE_MEDIA_PROJECTION in order to remove "
+ + "projection callbacks");
+ }
+ final long token = Binder.clearCallingIdentity();
+ try {
+ MediaProjectionManagerService.this.removeCallback(callback);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
}
@Override // Binder call
@@ -157,25 +316,27 @@ public final class MediaProjectionManagerService extends SystemService
}
}
+
private boolean checkPermission(String packageName, String permission) {
return mContext.getPackageManager().checkPermission(permission, packageName)
== PackageManager.PERMISSION_GRANTED;
}
}
- private final class MediaProjection extends IMediaProjection.Stub implements DeathRecipient {
- public int uid;
- public String packageName;
+ private final class MediaProjection extends IMediaProjection.Stub {
+ public final int uid;
+ public final String packageName;
+ public final UserHandle userHandle;
private IBinder mToken;
+ private IBinder.DeathRecipient mDeathEater;
private int mType;
- private CallbackDelegate mCallbackDelegate;
public MediaProjection(int type, int uid, String packageName) {
mType = type;
this.uid = uid;
this.packageName = packageName;
- mCallbackDelegate = new CallbackDelegate();
+ userHandle = new UserHandle(UserHandle.getUserId(uid));
}
@Override // Binder call
@@ -220,49 +381,50 @@ public final class MediaProjectionManagerService extends SystemService
}
@Override // Binder call
- public void start(IMediaProjectionCallback callback) {
+ public void start(final IMediaProjectionCallback callback) {
if (callback == null) {
throw new IllegalArgumentException("callback must not be null");
}
synchronized (mLock) {
- if (mProjectionGrants.containsKey(asBinder())) {
+ if (isValidMediaProjection(asBinder())) {
throw new IllegalStateException(
"Cannot start already started MediaProjection");
}
addCallback(callback);
try {
mToken = callback.asBinder();
- mToken.linkToDeath(this, 0);
+ mDeathEater = new IBinder.DeathRecipient() {
+ @Override
+ public void binderDied() {
+ mCallbackDelegate.remove(callback);
+ stop();
+ }
+ };
+ mToken.linkToDeath(mDeathEater, 0);
} catch (RemoteException e) {
Slog.w(TAG,
"MediaProjectionCallbacks must be valid, aborting MediaProjection", e);
return;
}
- mProjectionGrants.put(asBinder(), this);
+ startProjectionLocked(this);
}
}
@Override // Binder call
public void stop() {
synchronized (mLock) {
- if (!mProjectionGrants.containsKey(asBinder())) {
+ if (!isValidMediaProjection(asBinder())) {
Slog.w(TAG, "Attempted to stop inactive MediaProjection "
+ "(uid=" + Binder.getCallingUid() + ", "
+ "pid=" + Binder.getCallingPid() + ")");
return;
}
- mToken.unlinkToDeath(this, 0);
- mCallbackDelegate.dispatchStop();
- mProjectionGrants.remove(asBinder());
+ mToken.unlinkToDeath(mDeathEater, 0);
+ stopProjectionLocked(this);
}
}
@Override
- public void binderDied() {
- stop();
- }
-
- @Override
public void addCallback(IMediaProjectionCallback callback) {
if (callback == null) {
throw new IllegalArgumentException("callback must not be null");
@@ -278,52 +440,143 @@ public final class MediaProjectionManagerService extends SystemService
mCallbackDelegate.remove(callback);
}
- public void dump(PrintWriter pw, String prefix) {
- pw.println(prefix + "(" + packageName + ", uid=" + uid + "): " + typeToString(mType));
+ public MediaProjectionInfo getProjectionInfo() {
+ return new MediaProjectionInfo(packageName, userHandle);
+ }
+
+ public void dump(PrintWriter pw) {
+ pw.println("(" + packageName + ", uid=" + uid + "): " + typeToString(mType));
}
}
+
private static class CallbackDelegate {
- private static final int MSG_ON_STOP = 0;
- private List<IMediaProjectionCallback> mCallbacks;
+ private Map<IBinder, IMediaProjectionCallback> mClientCallbacks;
+ private Map<IBinder, IMediaProjectionWatcherCallback> mWatcherCallbacks;
private Handler mHandler;
private Object mLock = new Object();
public CallbackDelegate() {
mHandler = new Handler(Looper.getMainLooper(), null, true /*async*/);
- mCallbacks = new ArrayList<IMediaProjectionCallback>();
+ mClientCallbacks = new ArrayMap<IBinder, IMediaProjectionCallback>();
+ mWatcherCallbacks = new ArrayMap<IBinder, IMediaProjectionWatcherCallback>();
}
public void add(IMediaProjectionCallback callback) {
synchronized (mLock) {
- mCallbacks.add(callback);
+ mClientCallbacks.put(callback.asBinder(), callback);
+ }
+ }
+
+ public void add(IMediaProjectionWatcherCallback callback) {
+ synchronized (mLock) {
+ mWatcherCallbacks.put(callback.asBinder(), callback);
}
}
public void remove(IMediaProjectionCallback callback) {
synchronized (mLock) {
- mCallbacks.remove(callback);
+ mClientCallbacks.remove(callback.asBinder());
}
}
- public void dispatchStop() {
+ public void remove(IMediaProjectionWatcherCallback callback) {
synchronized (mLock) {
- for (final IMediaProjectionCallback callback : mCallbacks) {
- mHandler.post(new Runnable() {
- @Override
- public void run() {
- try {
- callback.onStop();
- } catch (RemoteException e) {
- Slog.w(TAG, "Failed to notify media projection has stopped", e);
- }
- }
- });
+ mWatcherCallbacks.remove(callback.asBinder());
+ }
+ }
+
+ public void dispatchStart(MediaProjection projection) {
+ if (projection == null) {
+ Slog.e(TAG, "Tried to dispatch start notification for a null media projection."
+ + " Ignoring!");
+ return;
+ }
+ synchronized (mLock) {
+ for (IMediaProjectionWatcherCallback callback : mWatcherCallbacks.values()) {
+ MediaProjectionInfo info = projection.getProjectionInfo();
+ mHandler.post(new WatcherStartCallback(info, callback));
}
}
}
+
+ public void dispatchStop(MediaProjection projection) {
+ if (projection == null) {
+ Slog.e(TAG, "Tried to dispatch stop notification for a null media projection."
+ + " Ignoring!");
+ return;
+ }
+ synchronized (mLock) {
+ for (IMediaProjectionCallback callback : mClientCallbacks.values()) {
+ mHandler.post(new ClientStopCallback(callback));
+ }
+
+ for (IMediaProjectionWatcherCallback callback : mWatcherCallbacks.values()) {
+ MediaProjectionInfo info = projection.getProjectionInfo();
+ mHandler.post(new WatcherStopCallback(info, callback));
+ }
+ }
+ }
+ }
+
+ private static final class WatcherStartCallback implements Runnable {
+ private IMediaProjectionWatcherCallback mCallback;
+ private MediaProjectionInfo mInfo;
+
+ public WatcherStartCallback(MediaProjectionInfo info,
+ IMediaProjectionWatcherCallback callback) {
+ mInfo = info;
+ mCallback = callback;
+ }
+
+ @Override
+ public void run() {
+ try {
+ mCallback.onStart(mInfo);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Failed to notify media projection has stopped", e);
+ }
+ }
+ }
+
+ private static final class WatcherStopCallback implements Runnable {
+ private IMediaProjectionWatcherCallback mCallback;
+ private MediaProjectionInfo mInfo;
+
+ public WatcherStopCallback(MediaProjectionInfo info,
+ IMediaProjectionWatcherCallback callback) {
+ mInfo = info;
+ mCallback = callback;
+ }
+
+ @Override
+ public void run() {
+ try {
+ mCallback.onStop(mInfo);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Failed to notify media projection has stopped", e);
+ }
+ }
}
+ private static final class ClientStopCallback implements Runnable {
+ private IMediaProjectionCallback mCallback;
+
+ public ClientStopCallback(IMediaProjectionCallback callback) {
+ mCallback = callback;
+ }
+
+ @Override
+ public void run() {
+ try {
+ mCallback.onStop();
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Failed to notify media projection has stopped", e);
+ }
+ }
+ }
+
+
private static String typeToString(int type) {
switch (type) {
case MediaProjectionManager.TYPE_SCREEN_CAPTURE: