Loading media/java/android/media/projection/IMediaProjection.aidl +22 −0 Original line number Diff line number Diff line Loading @@ -51,4 +51,26 @@ interface IMediaProjection { @JavaPassthrough(annotation = "@android.annotation.RequiresPermission(android.Manifest" + ".permission.MANAGE_MEDIA_PROJECTION)") void setLaunchCookie(in IBinder launchCookie); /** * Returns {@code true} if this token is still valid. A token is valid as long as the token * hasn't timed out before it was used, and the token is only used once. * * <p>If the {@link IMediaProjection} is not valid, then either throws an exception if the * target SDK is at least {@code U}, or returns {@code false} for target SDK below {@code U}. * * @throws IllegalStateException If the caller's target SDK is at least {@code U} and the * projection is not valid. */ @JavaPassthrough(annotation = "@android.annotation.RequiresPermission(android.Manifest" + ".permission.MANAGE_MEDIA_PROJECTION)") boolean isValid(); /** * Sets that {@link MediaProjection#createVirtualDisplay} has been invoked with this token (it * should only be called once). */ @JavaPassthrough(annotation = "@android.annotation.RequiresPermission(android.Manifest" + ".permission.MANAGE_MEDIA_PROJECTION)") void notifyVirtualDisplayCreated(int displayId); } media/java/android/media/projection/IMediaProjectionManager.aidl +23 −4 Original line number Diff line number Diff line Loading @@ -44,6 +44,22 @@ interface IMediaProjectionManager { + ".permission.MANAGE_MEDIA_PROJECTION)") boolean isCurrentProjection(IMediaProjection projection); /** * Reshows the permisison dialog for the user to review consent they've already granted in * the given projection instance. * * <p>Preconditions: * <ul> * <li>{@link IMediaProjection#isValid} returned false, rather than throwing an exception</li> * <li>Given projection instance is the current projection instance.</li> * <ul> * * <p>Returns immediately but waits to start recording until user has reviewed their consent. */ @JavaPassthrough(annotation = "@android.annotation.RequiresPermission(android.Manifest" + ".permission.MANAGE_MEDIA_PROJECTION)") void requestConsentForInvalidProjection(IMediaProjection projection); @JavaPassthrough(annotation = "@android.annotation.RequiresPermission(android.Manifest" + ".permission.MANAGE_MEDIA_PROJECTION)") MediaProjectionInfo getActiveProjectionInfo(); Loading @@ -69,15 +85,18 @@ interface IMediaProjectionManager { void removeCallback(IMediaProjectionWatcherCallback callback); /** * Updates the content recording session. If a different session is already in progress, then * the pre-existing session is stopped, and the new incoming session takes over. Only updates * the session if the given projection is valid. * Returns {@code true} if it successfully updates the content recording session. Returns * {@code false} otherwise, and stops the current projection. * * <p>If a different session is already in progress, then the pre-existing session is stopped, * and the new incoming session takes over. Only updates the session if the given projection is * valid. * * @param incomingSession the nullable incoming content recording session * @param projection the non-null projection the session describes */ @JavaPassthrough(annotation = "@android.annotation.RequiresPermission(android.Manifest" + ".permission.MANAGE_MEDIA_PROJECTION)") void setContentRecordingSession(in ContentRecordingSession incomingSession, boolean setContentRecordingSession(in ContentRecordingSession incomingSession, in IMediaProjection projection); } media/java/android/media/projection/MediaProjection.java +20 −5 Original line number Diff line number Diff line Loading @@ -164,12 +164,21 @@ public final class MediaProjection { * @param handler The {@link android.os.Handler} on which the callback should be invoked, or * null if the callback should be invoked on the calling thread's main * {@link android.os.Looper}. * @throws IllegalStateException If the target SDK is * {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE U} and * up and no {@link Callback} * is registered. If the target SDK is less than * @throws IllegalStateException In the following scenarios, if the target SDK is {@link * android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE U} and up: * <ol> * <li>If no {@link Callback} is registered.</li> * <li>If {@link MediaProjectionManager#getMediaProjection} * was invoked more than once to get this * {@code MediaProjection} instance. * <li>If this instance has already taken a recording through * {@code #createVirtualDisplay}. * </ol> * However, if the target SDK is less than * {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE U}, no * exception is thrown. * exception is thrown. In case 1, recording begins even without * the callback. In case 2 & 3, recording doesn't begin * until the user re-grants consent in the dialog. * @throws SecurityException If attempting to create a new virtual display associated with this * MediaProjection instance after it has been stopped by invoking * {@link #stop()}. Loading Loading @@ -216,8 +225,13 @@ public final class MediaProjection { // Pass in the current session details, so they are guaranteed to only be set in // WindowManagerService AFTER a VirtualDisplay is constructed (assuming there are no // errors during set-up). // Do not introduce a separate aidl call here to prevent a race // condition between setting up the VirtualDisplay and checking token validity. virtualDisplayConfig.setWindowManagerMirroringEnabled(true); // Do not declare a display id to mirror; default to the default display. // DisplayManagerService will ask MediaProjectionManagerService to check if the app // is re-using consent. Always return the projection instance to keep this call // non-blocking; no content is sent to the app until the user re-grants consent. final VirtualDisplay virtualDisplay = mDisplayManager.createVirtualDisplay(this, virtualDisplayConfig.build(), callback, handler); if (virtualDisplay == null) { Loading Loading @@ -339,6 +353,7 @@ public final class MediaProjection { private final class MediaProjectionCallback extends IMediaProjectionCallback.Stub { @Override public void onStop() { Slog.v(TAG, "Dispatch stop to " + mCallbacks.size() + " callbacks."); for (CallbackRecord cbr : mCallbacks.values()) { cbr.onStop(); } Loading media/java/android/media/projection/MediaProjectionManager.java +2 −0 Original line number Diff line number Diff line Loading @@ -231,6 +231,8 @@ public final class MediaProjectionManager { if (projection == null) { return null; } // Don't do anything here if app is re-using the token; we check how often // IMediaProjection#start is invoked. Fail to the app when they start recording. return new MediaProjection(mContext, IMediaProjection.Stub.asInterface(projection)); } Loading media/tests/projection/src/android/media/projection/FakeIMediaProjection.java +10 −2 Original line number Diff line number Diff line Loading @@ -62,12 +62,10 @@ public final class FakeIMediaProjection extends IMediaProjection.Stub { @Override public void registerCallback(IMediaProjectionCallback callback) throws RemoteException { } @Override public void unregisterCallback(IMediaProjectionCallback callback) throws RemoteException { } @Override Loading @@ -79,4 +77,14 @@ public final class FakeIMediaProjection extends IMediaProjection.Stub { public void setLaunchCookie(IBinder launchCookie) throws RemoteException { mLaunchCookie = launchCookie; } @Override public boolean isValid() throws RemoteException { return true; } @Override public void notifyVirtualDisplayCreated(int displayId) throws RemoteException { } } Loading
media/java/android/media/projection/IMediaProjection.aidl +22 −0 Original line number Diff line number Diff line Loading @@ -51,4 +51,26 @@ interface IMediaProjection { @JavaPassthrough(annotation = "@android.annotation.RequiresPermission(android.Manifest" + ".permission.MANAGE_MEDIA_PROJECTION)") void setLaunchCookie(in IBinder launchCookie); /** * Returns {@code true} if this token is still valid. A token is valid as long as the token * hasn't timed out before it was used, and the token is only used once. * * <p>If the {@link IMediaProjection} is not valid, then either throws an exception if the * target SDK is at least {@code U}, or returns {@code false} for target SDK below {@code U}. * * @throws IllegalStateException If the caller's target SDK is at least {@code U} and the * projection is not valid. */ @JavaPassthrough(annotation = "@android.annotation.RequiresPermission(android.Manifest" + ".permission.MANAGE_MEDIA_PROJECTION)") boolean isValid(); /** * Sets that {@link MediaProjection#createVirtualDisplay} has been invoked with this token (it * should only be called once). */ @JavaPassthrough(annotation = "@android.annotation.RequiresPermission(android.Manifest" + ".permission.MANAGE_MEDIA_PROJECTION)") void notifyVirtualDisplayCreated(int displayId); }
media/java/android/media/projection/IMediaProjectionManager.aidl +23 −4 Original line number Diff line number Diff line Loading @@ -44,6 +44,22 @@ interface IMediaProjectionManager { + ".permission.MANAGE_MEDIA_PROJECTION)") boolean isCurrentProjection(IMediaProjection projection); /** * Reshows the permisison dialog for the user to review consent they've already granted in * the given projection instance. * * <p>Preconditions: * <ul> * <li>{@link IMediaProjection#isValid} returned false, rather than throwing an exception</li> * <li>Given projection instance is the current projection instance.</li> * <ul> * * <p>Returns immediately but waits to start recording until user has reviewed their consent. */ @JavaPassthrough(annotation = "@android.annotation.RequiresPermission(android.Manifest" + ".permission.MANAGE_MEDIA_PROJECTION)") void requestConsentForInvalidProjection(IMediaProjection projection); @JavaPassthrough(annotation = "@android.annotation.RequiresPermission(android.Manifest" + ".permission.MANAGE_MEDIA_PROJECTION)") MediaProjectionInfo getActiveProjectionInfo(); Loading @@ -69,15 +85,18 @@ interface IMediaProjectionManager { void removeCallback(IMediaProjectionWatcherCallback callback); /** * Updates the content recording session. If a different session is already in progress, then * the pre-existing session is stopped, and the new incoming session takes over. Only updates * the session if the given projection is valid. * Returns {@code true} if it successfully updates the content recording session. Returns * {@code false} otherwise, and stops the current projection. * * <p>If a different session is already in progress, then the pre-existing session is stopped, * and the new incoming session takes over. Only updates the session if the given projection is * valid. * * @param incomingSession the nullable incoming content recording session * @param projection the non-null projection the session describes */ @JavaPassthrough(annotation = "@android.annotation.RequiresPermission(android.Manifest" + ".permission.MANAGE_MEDIA_PROJECTION)") void setContentRecordingSession(in ContentRecordingSession incomingSession, boolean setContentRecordingSession(in ContentRecordingSession incomingSession, in IMediaProjection projection); }
media/java/android/media/projection/MediaProjection.java +20 −5 Original line number Diff line number Diff line Loading @@ -164,12 +164,21 @@ public final class MediaProjection { * @param handler The {@link android.os.Handler} on which the callback should be invoked, or * null if the callback should be invoked on the calling thread's main * {@link android.os.Looper}. * @throws IllegalStateException If the target SDK is * {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE U} and * up and no {@link Callback} * is registered. If the target SDK is less than * @throws IllegalStateException In the following scenarios, if the target SDK is {@link * android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE U} and up: * <ol> * <li>If no {@link Callback} is registered.</li> * <li>If {@link MediaProjectionManager#getMediaProjection} * was invoked more than once to get this * {@code MediaProjection} instance. * <li>If this instance has already taken a recording through * {@code #createVirtualDisplay}. * </ol> * However, if the target SDK is less than * {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE U}, no * exception is thrown. * exception is thrown. In case 1, recording begins even without * the callback. In case 2 & 3, recording doesn't begin * until the user re-grants consent in the dialog. * @throws SecurityException If attempting to create a new virtual display associated with this * MediaProjection instance after it has been stopped by invoking * {@link #stop()}. Loading Loading @@ -216,8 +225,13 @@ public final class MediaProjection { // Pass in the current session details, so they are guaranteed to only be set in // WindowManagerService AFTER a VirtualDisplay is constructed (assuming there are no // errors during set-up). // Do not introduce a separate aidl call here to prevent a race // condition between setting up the VirtualDisplay and checking token validity. virtualDisplayConfig.setWindowManagerMirroringEnabled(true); // Do not declare a display id to mirror; default to the default display. // DisplayManagerService will ask MediaProjectionManagerService to check if the app // is re-using consent. Always return the projection instance to keep this call // non-blocking; no content is sent to the app until the user re-grants consent. final VirtualDisplay virtualDisplay = mDisplayManager.createVirtualDisplay(this, virtualDisplayConfig.build(), callback, handler); if (virtualDisplay == null) { Loading Loading @@ -339,6 +353,7 @@ public final class MediaProjection { private final class MediaProjectionCallback extends IMediaProjectionCallback.Stub { @Override public void onStop() { Slog.v(TAG, "Dispatch stop to " + mCallbacks.size() + " callbacks."); for (CallbackRecord cbr : mCallbacks.values()) { cbr.onStop(); } Loading
media/java/android/media/projection/MediaProjectionManager.java +2 −0 Original line number Diff line number Diff line Loading @@ -231,6 +231,8 @@ public final class MediaProjectionManager { if (projection == null) { return null; } // Don't do anything here if app is re-using the token; we check how often // IMediaProjection#start is invoked. Fail to the app when they start recording. return new MediaProjection(mContext, IMediaProjection.Stub.asInterface(projection)); } Loading
media/tests/projection/src/android/media/projection/FakeIMediaProjection.java +10 −2 Original line number Diff line number Diff line Loading @@ -62,12 +62,10 @@ public final class FakeIMediaProjection extends IMediaProjection.Stub { @Override public void registerCallback(IMediaProjectionCallback callback) throws RemoteException { } @Override public void unregisterCallback(IMediaProjectionCallback callback) throws RemoteException { } @Override Loading @@ -79,4 +77,14 @@ public final class FakeIMediaProjection extends IMediaProjection.Stub { public void setLaunchCookie(IBinder launchCookie) throws RemoteException { mLaunchCookie = launchCookie; } @Override public boolean isValid() throws RemoteException { return true; } @Override public void notifyVirtualDisplayCreated(int displayId) throws RemoteException { } }