Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 89e8322f authored by Naomi Musgrave's avatar Naomi Musgrave
Browse files

(1/N)[MediaProjection] Throw exception if token re-used

Validate if the IMediaProjection token (representing
the user's consent) is used to get more than one
MediaProjection instance, or if client app is trying to
invoke MediaProjection#createVirtualDisplay more than
once.

Throw an exception for target SDK U+.

Follow-on CLs will:
* Re-show the permission dialog for re-used consent,
  when the target SDk is below U.
* Black out recording when waiting for consent

Bug: 242833866
Test: atest FrameworksServicesTests:MediaProjectionManagerServiceTest
Test: atest FrameworksServicesTests:DisplayManagerServiceTest

Change-Id: I1fc7c9afde63ea1fc849015932a66b9321879fd9
parent d9fc7a57
Loading
Loading
Loading
Loading
+22 −0
Original line number Diff line number Diff line
@@ -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);
}
+23 −4
Original line number Diff line number Diff line
@@ -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();
@@ -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);
}
+20 −5
Original line number Diff line number Diff line
@@ -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()}.
@@ -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) {
@@ -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();
            }
+2 −0
Original line number Diff line number Diff line
@@ -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));
    }

+10 −2
Original line number Diff line number Diff line
@@ -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
@@ -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