diff options
author | Michael Wright <michaelwr@google.com> | 2014-08-12 19:27:54 -0700 |
---|---|---|
committer | Michael Wright <michaelwr@google.com> | 2014-08-17 16:06:56 -0700 |
commit | d86ecd26d78c7bcd7f820b9ef5e3f4c3eb037510 (patch) | |
tree | 977c5062c44726daebdd6d2803f09f8c6f77b1ba /services/core/java/com/android/server/media/projection | |
parent | 804322564b1b4e4ccff0b1c8cd312bac75ec5bb0 (diff) | |
download | frameworks_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/projection')
-rw-r--r-- | services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java | 345 |
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: |