Add callbacks for MediaProjection start / stop.
Also, enforce that there is only one valid MediaProjection at a time. Bug: 16488053 Change-Id: Id05445d798c98cb208bc4dab186296392e15d30b
This commit is contained in:
@@ -329,6 +329,7 @@ LOCAL_SRC_FILES += \
|
||||
media/java/android/media/projection/IMediaProjection.aidl \
|
||||
media/java/android/media/projection/IMediaProjectionCallback.aidl \
|
||||
media/java/android/media/projection/IMediaProjectionManager.aidl \
|
||||
media/java/android/media/projection/IMediaProjectionWatcherCallback.aidl \
|
||||
media/java/android/media/routing/IMediaRouteService.aidl \
|
||||
media/java/android/media/routing/IMediaRouteClientCallback.aidl \
|
||||
media/java/android/media/routing/IMediaRouter.aidl \
|
||||
|
||||
@@ -2811,11 +2811,11 @@
|
||||
android:description="@string/permdesc_accessDrmCertificates"
|
||||
android:protectionLevel="signature|system" />
|
||||
|
||||
<!-- Api Allows an application to create media projection sessions.
|
||||
<!-- Api Allows an application to manage media projection sessions.
|
||||
@hide This is not a third-party API (intended for system apps). -->
|
||||
<permission android:name="android.permission.CREATE_MEDIA_PROJECTION"
|
||||
android:label="@string/permlab_createMediaProjection"
|
||||
android:description="@string/permdesc_createMediaProjection"
|
||||
<permission android:name="android.permission.MANAGE_MEDIA_PROJECTION"
|
||||
android:label="@string/permlab_manageMediaProjection"
|
||||
android:description="@string/permdesc_manageMediaProjection"
|
||||
android:protectionLevel="signature" />
|
||||
|
||||
<!-- @SystemApi Allows an application to read install sessions
|
||||
|
||||
@@ -3898,10 +3898,10 @@
|
||||
<!-- Description of an application permission that lets it control keyguard. -->
|
||||
<string name="permdesc_recovery">Allows an application to interact with the recovery system and system updates.</string>
|
||||
|
||||
<!-- Title of an application permission that lets it create media projection sessions. -->
|
||||
<string name="permlab_createMediaProjection">Create media projection sessions</string>
|
||||
<!-- Description of an application permission that lets it create media projection sessions. -->
|
||||
<string name="permdesc_createMediaProjection">Allows an application to create media projection sessions. These sessions can provide applications the ability to capture display and audio contents. Should never be needed by normal apps.</string>
|
||||
<!-- Title of an application permission that lets it manage media projection sessions. -->
|
||||
<string name="permlab_manageMediaProjection">Manage media projection sessions</string>
|
||||
<!-- Description of an application permission that lets it manage media projection sessions. -->
|
||||
<string name="permdesc_manageMediaProjection">Allows an application to manage media projection sessions. These sessions can provide applications the ability to capture display and audio contents. Should never be needed by normal apps.</string>
|
||||
|
||||
<!-- Title of an application permission that lets it read install sessions. -->
|
||||
<string name="permlab_readInstallSessions">Read install sessions</string>
|
||||
|
||||
@@ -17,6 +17,9 @@
|
||||
package android.media.projection;
|
||||
|
||||
import android.media.projection.IMediaProjection;
|
||||
import android.media.projection.IMediaProjectionCallback;
|
||||
import android.media.projection.IMediaProjectionWatcherCallback;
|
||||
import android.media.projection.MediaProjectionInfo;
|
||||
import android.os.IBinder;
|
||||
|
||||
/** {@hide} */
|
||||
@@ -25,4 +28,8 @@ interface IMediaProjectionManager {
|
||||
IMediaProjection createProjection(int uid, String packageName, int type,
|
||||
boolean permanentGrant);
|
||||
boolean isValidMediaProjection(IMediaProjection projection);
|
||||
MediaProjectionInfo getActiveProjectionInfo();
|
||||
void stopActiveProjection();
|
||||
void addCallback(IMediaProjectionWatcherCallback callback);
|
||||
void removeCallback(IMediaProjectionWatcherCallback callback);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* 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 android.media.projection;
|
||||
|
||||
import android.media.projection.MediaProjectionInfo;
|
||||
|
||||
/** {@hide} */
|
||||
oneway interface IMediaProjectionWatcherCallback {
|
||||
void onStart(in MediaProjectionInfo info);
|
||||
void onStop(in MediaProjectionInfo info);
|
||||
}
|
||||
19
media/java/android/media/projection/MediaProjectionInfo.aidl
Normal file
19
media/java/android/media/projection/MediaProjectionInfo.aidl
Normal file
@@ -0,0 +1,19 @@
|
||||
/**
|
||||
* 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 android.media.projection;
|
||||
|
||||
parcelable MediaProjectionInfo;
|
||||
76
media/java/android/media/projection/MediaProjectionInfo.java
Normal file
76
media/java/android/media/projection/MediaProjectionInfo.java
Normal file
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* 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 android.media.projection;
|
||||
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
import android.os.UserHandle;
|
||||
|
||||
/** @hide */
|
||||
public final class MediaProjectionInfo implements Parcelable {
|
||||
private final String mPackageName;
|
||||
private final UserHandle mUserHandle;
|
||||
|
||||
public MediaProjectionInfo(String packageName, UserHandle handle) {
|
||||
mPackageName = packageName;
|
||||
mUserHandle = handle;
|
||||
}
|
||||
|
||||
public MediaProjectionInfo(Parcel in) {
|
||||
mPackageName = in.readString();
|
||||
mUserHandle = UserHandle.readFromParcel(in);
|
||||
}
|
||||
|
||||
public String getPackageName() {
|
||||
return mPackageName;
|
||||
}
|
||||
|
||||
public UserHandle getUserHandle() {
|
||||
return mUserHandle;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "MediaProjectionInfo{mPackageName="
|
||||
+ mPackageName + ", mUserHandle="
|
||||
+ mUserHandle + "}";
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel out, int flags) {
|
||||
out.writeString(mPackageName);
|
||||
UserHandle.writeToParcel(mUserHandle, out);
|
||||
}
|
||||
|
||||
public static final Parcelable.Creator<MediaProjectionInfo> CREATOR =
|
||||
new Parcelable.Creator<MediaProjectionInfo>() {
|
||||
@Override
|
||||
public MediaProjectionInfo createFromParcel(Parcel in) {
|
||||
return new MediaProjectionInfo (in);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MediaProjectionInfo[] newArray(int size) {
|
||||
return new MediaProjectionInfo[size];
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -17,12 +17,20 @@
|
||||
package android.media.projection;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.media.projection.IMediaProjection;
|
||||
import android.os.Binder;
|
||||
import android.os.Handler;
|
||||
import android.os.IBinder;
|
||||
import android.os.RemoteException;
|
||||
import android.os.ServiceManager;
|
||||
import android.util.ArrayMap;
|
||||
import android.util.Log;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Manages the retrieval of certain types of {@link MediaProjection} tokens.
|
||||
@@ -35,6 +43,7 @@ import android.os.IBinder;
|
||||
* </p>
|
||||
*/
|
||||
public final class MediaProjectionManager {
|
||||
private static final String TAG = "MediaProjectionManager";
|
||||
/** @hide */
|
||||
public static final String EXTRA_APP_TOKEN = "android.media.projection.extra.EXTRA_APP_TOKEN";
|
||||
/** @hide */
|
||||
@@ -49,10 +58,15 @@ public final class MediaProjectionManager {
|
||||
public static final int TYPE_PRESENTATION = 2;
|
||||
|
||||
private Context mContext;
|
||||
private Map<Callback, CallbackDelegate> mCallbacks;
|
||||
private IMediaProjectionManager mService;
|
||||
|
||||
/** @hide */
|
||||
public MediaProjectionManager(Context context) {
|
||||
mContext = context;
|
||||
IBinder b = ServiceManager.getService(Context.MEDIA_PROJECTION_SERVICE);
|
||||
mService = IMediaProjectionManager.Stub.asInterface(b);
|
||||
mCallbacks = new ArrayMap<>();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -88,4 +102,105 @@ public final class MediaProjectionManager {
|
||||
}
|
||||
return new MediaProjection(mContext, IMediaProjection.Stub.asInterface(projection));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the {@link MediaProjectionInfo} for the active {@link MediaProjection}.
|
||||
* @hide
|
||||
*/
|
||||
public MediaProjectionInfo getActiveProjectionInfo() {
|
||||
try {
|
||||
return mService.getActiveProjectionInfo();
|
||||
} catch (RemoteException e) {
|
||||
Log.e(TAG, "Unable to get the active projection info", e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop the current projection if there is one.
|
||||
* @hide
|
||||
*/
|
||||
public void stopActiveProjection() {
|
||||
try {
|
||||
mService.stopActiveProjection();
|
||||
} catch (RemoteException e) {
|
||||
Log.e(TAG, "Unable to stop the currently active media projection", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a callback to monitor all of the {@link MediaProjection}s activity.
|
||||
* Not for use by regular applications, must have the MANAGE_MEDIA_PROJECTION permission.
|
||||
* @hide
|
||||
*/
|
||||
public void addCallback(@NonNull Callback callback, @Nullable Handler handler) {
|
||||
if (callback == null) {
|
||||
throw new IllegalArgumentException("callback must not be null");
|
||||
}
|
||||
CallbackDelegate delegate = new CallbackDelegate(callback, handler);
|
||||
mCallbacks.put(callback, delegate);
|
||||
try {
|
||||
mService.addCallback(delegate);
|
||||
} catch (RemoteException e) {
|
||||
Log.e(TAG, "Unable to add callbacks to MediaProjection service", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a MediaProjection monitoring callback.
|
||||
* @hide
|
||||
*/
|
||||
public void removeCallback(@NonNull Callback callback) {
|
||||
if (callback == null) {
|
||||
throw new IllegalArgumentException("callback must not be null");
|
||||
}
|
||||
CallbackDelegate delegate = mCallbacks.remove(callback);
|
||||
try {
|
||||
if (delegate != null) {
|
||||
mService.removeCallback(delegate);
|
||||
}
|
||||
} catch (RemoteException e) {
|
||||
Log.e(TAG, "Unable to add callbacks to MediaProjection service", e);
|
||||
}
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
public static abstract class Callback {
|
||||
public abstract void onStart(MediaProjectionInfo info);
|
||||
public abstract void onStop(MediaProjectionInfo info);
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
private final static class CallbackDelegate extends IMediaProjectionWatcherCallback.Stub {
|
||||
private Callback mCallback;
|
||||
private Handler mHandler;
|
||||
|
||||
public CallbackDelegate(Callback callback, Handler handler) {
|
||||
mCallback = callback;
|
||||
if (handler == null) {
|
||||
handler = new Handler();
|
||||
}
|
||||
mHandler = handler;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart(final MediaProjectionInfo info) {
|
||||
mHandler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
mCallback.onStart(info);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStop(final MediaProjectionInfo info) {
|
||||
mHandler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
mCallback.onStop(info);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -111,7 +111,7 @@
|
||||
<uses-permission android:name="android.permission.CAMERA" />
|
||||
|
||||
<!-- Screen Capturing -->
|
||||
<uses-permission android:name="android.permission.CREATE_MEDIA_PROJECTION" />
|
||||
<uses-permission android:name="android.permission.MANAGE_MEDIA_PROJECTION" />
|
||||
|
||||
<application
|
||||
android:name=".SystemUIApplication"
|
||||
|
||||
@@ -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,48 +381,49 @@ 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) {
|
||||
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user