diff options
author | Michael Wright <michaelwr@google.com> | 2014-07-08 18:07:36 -0700 |
---|---|---|
committer | Michael Wright <michaelwr@google.com> | 2014-07-17 14:30:19 -0700 |
commit | c39d47a8e7c74bd539104b0efab898ef6fc43ddf (patch) | |
tree | 98b0c601c9d3b0249e4bd2324aa66ad138e29ad9 /services/core/java/com/android/server/media/projection | |
parent | 6ccb5f894e11a0ee59c0632643f265427731536e (diff) | |
download | frameworks_base-c39d47a8e7c74bd539104b0efab898ef6fc43ddf.zip frameworks_base-c39d47a8e7c74bd539104b0efab898ef6fc43ddf.tar.gz frameworks_base-c39d47a8e7c74bd539104b0efab898ef6fc43ddf.tar.bz2 |
Add MediaProjection APIs.
The new MediaProjection infrastructure allows the system to hand out
tokens granting the ability to capture the screen's contents, audio,
etc. at a granular level. It's intended to be used both for screen
casting, via the cast APIs, as well as screen sharing via third party
applications.
The screen sharing case is implemented, but all of audio capturing
is still forthcoming.
Change-Id: I4b24669bed7083e11413c10ed8d6b025f5375316
Diffstat (limited to 'services/core/java/com/android/server/media/projection')
-rw-r--r-- | services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java | 329 |
1 files changed, 329 insertions, 0 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 new file mode 100644 index 0000000..7b28699 --- /dev/null +++ b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java @@ -0,0 +1,329 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.media.projection; + +import com.android.server.Watchdog; + +import android.Manifest; +import android.app.AppOpsManager; +import android.content.Context; +import android.content.pm.PackageManager; +import android.hardware.display.DisplayManager; +import android.media.projection.IMediaProjectionManager; +import android.media.projection.IMediaProjection; +import android.media.projection.IMediaProjectionCallback; +import android.media.projection.MediaProjectionManager; +import android.os.Binder; +import android.os.Handler; +import android.os.IBinder; +import android.os.IBinder.DeathRecipient; +import android.os.Looper; +import android.os.Message; +import android.os.RemoteException; +import android.util.ArrayMap; +import android.util.Slog; + +import com.android.server.SystemService; + +import java.io.FileDescriptor; +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Map; + +/** + * Manages MediaProjection sessions. + * + * The {@link MediaProjectionManagerService} manages the creation and lifetime of MediaProjections, + * as well as the capabilities they grant. Any service using MediaProjection tokens as permission + * grants <b>must</b> validate the token before use by calling {@link + * IMediaProjectionService#isValidMediaProjection}. + */ +public final class MediaProjectionManagerService extends SystemService + implements Watchdog.Monitor { + 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 Context mContext; + private final AppOpsManager mAppOps; + + public MediaProjectionManagerService(Context context) { + super(context); + mContext = context; + mProjectionGrants = new ArrayMap<IBinder, MediaProjection>(); + mAppOps = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE); + Watchdog.getInstance().addMonitor(this); + } + + @Override + public void onStart() { + publishBinderService(Context.MEDIA_PROJECTION_SERVICE, new BinderService(), + false /*allowIsolated*/); + } + + @Override + public void monitor() { + synchronized (mLock) { /* check for deadlock */ } + } + + 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, " "); + } + } + } + + private final class BinderService extends IMediaProjectionManager.Stub { + + @Override // Binder call + public boolean hasProjectionPermission(int uid, String packageName) { + long token = Binder.clearCallingIdentity(); + boolean hasPermission = false; + try { + hasPermission |= checkPermission(packageName, + android.Manifest.permission.CAPTURE_VIDEO_OUTPUT) + || mAppOps.checkOpNoThrow( + AppOpsManager.OP_PROJECT_MEDIA, uid, packageName) + == AppOpsManager.MODE_ALLOWED; + } finally { + Binder.restoreCallingIdentity(token); + } + return hasPermission; + } + + @Override // Binder call + public IMediaProjection createProjection(int uid, String packageName, int type, + boolean isPermanentGrant) { + if (mContext.checkCallingPermission(Manifest.permission.CREATE_MEDIA_PROJECTION) + != PackageManager.PERMISSION_GRANTED) { + throw new SecurityException("Requires CREATE_MEDIA_PROJECTION in order to grant " + + "projection permission"); + } + long callingToken = Binder.clearCallingIdentity(); + MediaProjection projection; + try { + projection = new MediaProjection(type, uid, packageName); + if (isPermanentGrant) { + mAppOps.setMode(AppOpsManager.OP_PROJECT_MEDIA, + projection.uid, projection.packageName, AppOpsManager.MODE_ALLOWED); + } + } finally { + Binder.restoreCallingIdentity(callingToken); + } + return projection; + } + + @Override // Binder call + public boolean isValidMediaProjection(IMediaProjection projection) { + return mProjectionGrants.containsKey(projection.asBinder()); + } + + @Override // Binder call + public void dump(FileDescriptor fd, final PrintWriter pw, String[] args) { + if (mContext == null + || mContext.checkCallingOrSelfPermission(Manifest.permission.DUMP) + != PackageManager.PERMISSION_GRANTED) { + pw.println("Permission Denial: can't dump MediaProjectionManager from from pid=" + + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid()); + return; + } + + final long token = Binder.clearCallingIdentity(); + try { + dump(pw); + } finally { + Binder.restoreCallingIdentity(token); + } + } + + 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 IBinder mToken; + 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(); + } + + @Override // Binder call + public boolean canProjectVideo() { + return mType == MediaProjectionManager.TYPE_MIRRORING || + mType == MediaProjectionManager.TYPE_SCREEN_CAPTURE; + } + + @Override // Binder call + public boolean canProjectSecureVideo() { + return false; + } + + @Override // Binder call + public boolean canProjectAudio() { + return mType == MediaProjectionManager.TYPE_MIRRORING || + mType == MediaProjectionManager.TYPE_PRESENTATION; + } + + @Override // Binder call + public int getVirtualDisplayFlags() { + switch (mType) { + case MediaProjectionManager.TYPE_SCREEN_CAPTURE: + return DisplayManager.VIRTUAL_DISPLAY_FLAG_SCREEN_SHARE; + case MediaProjectionManager.TYPE_MIRRORING: + return DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC | + DisplayManager.VIRTUAL_DISPLAY_FLAG_PRESENTATION; + case MediaProjectionManager.TYPE_PRESENTATION: + return DisplayManager.VIRTUAL_DISPLAY_FLAG_PRESENTATION | + DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY; + } + throw new RuntimeException("Unknown MediaProjection type"); + } + + @Override // Binder call + public void start(IMediaProjectionCallback callback) { + if (callback == null) { + throw new IllegalArgumentException("callback must not be null"); + } + synchronized (mLock) { + if (mProjectionGrants.containsKey(asBinder())) { + throw new IllegalStateException( + "Cannot start already started MediaProjection"); + } + addCallback(callback); + try { + mToken = callback.asBinder(); + mToken.linkToDeath(this, 0); + } catch (RemoteException e) { + Slog.w(TAG, + "MediaProjectionCallbacks must be valid, aborting MediaProjection", e); + return; + } + mProjectionGrants.put(asBinder(), this); + } + } + + @Override // Binder call + public void stop() { + synchronized (mLock) { + if (!mProjectionGrants.containsKey(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()); + } + } + + @Override + public void binderDied() { + stop(); + } + + @Override + public void addCallback(IMediaProjectionCallback callback) { + if (callback == null) { + throw new IllegalArgumentException("callback must not be null"); + } + mCallbackDelegate.add(callback); + } + + @Override + public void removeCallback(IMediaProjectionCallback callback) { + if (callback == null) { + throw new IllegalArgumentException("callback must not be null"); + } + mCallbackDelegate.remove(callback); + } + + public void dump(PrintWriter pw, String prefix) { + pw.println(prefix + "(" + packageName + ", uid=" + uid + "): " + typeToString(mType)); + } + } + + private static class CallbackDelegate { + private static final int MSG_ON_STOP = 0; + private List<IMediaProjectionCallback> mCallbacks; + private Handler mHandler; + private Object mLock = new Object(); + + public CallbackDelegate() { + mHandler = new Handler(Looper.getMainLooper(), null, true /*async*/); + mCallbacks = new ArrayList<IMediaProjectionCallback>(); + } + + public void add(IMediaProjectionCallback callback) { + synchronized (mLock) { + mCallbacks.add(callback); + } + } + + public void remove(IMediaProjectionCallback callback) { + synchronized (mLock) { + mCallbacks.remove(callback); + } + } + + public void dispatchStop() { + 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); + } + } + }); + } + } + } + } + + private static String typeToString(int type) { + switch (type) { + case MediaProjectionManager.TYPE_SCREEN_CAPTURE: + return "TYPE_SCREEN_CAPTURE"; + case MediaProjectionManager.TYPE_MIRRORING: + return "TYPE_MIRRORING"; + case MediaProjectionManager.TYPE_PRESENTATION: + return "TYPE_PRESENTATION"; + } + return Integer.toString(type); + } +} |