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

Commit 1d535bc3 authored by Naomi Musgrave's avatar Naomi Musgrave Committed by Android (Google) Code Review
Browse files

Merge "(2/N)[MediaProjection] Show dialog when token is reused" into udc-dev

parents 0a66fbb6 1398b3d0
Loading
Loading
Loading
Loading
+6 −0
Original line number Original line Diff line number Diff line
@@ -1495,6 +1495,12 @@
      "group": "WM_DEBUG_CONFIGURATION",
      "group": "WM_DEBUG_CONFIGURATION",
      "at": "com\/android\/server\/wm\/ActivityRecord.java"
      "at": "com\/android\/server\/wm\/ActivityRecord.java"
    },
    },
    "-741766551": {
      "message": "Content Recording: Ignoring session on invalid virtual display",
      "level": "VERBOSE",
      "group": "WM_DEBUG_CONTENT_RECORDING",
      "at": "com\/android\/server\/wm\/ContentRecordingController.java"
    },
    "-732715767": {
    "-732715767": {
      "message": "Unable to retrieve window container to start recording for display %d",
      "message": "Unable to retrieve window container to start recording for display %d",
      "level": "VERBOSE",
      "level": "VERBOSE",
+51 −1
Original line number Original line Diff line number Diff line
@@ -20,11 +20,23 @@ import android.media.projection.IMediaProjection;
import android.media.projection.IMediaProjectionCallback;
import android.media.projection.IMediaProjectionCallback;
import android.media.projection.IMediaProjectionWatcherCallback;
import android.media.projection.IMediaProjectionWatcherCallback;
import android.media.projection.MediaProjectionInfo;
import android.media.projection.MediaProjectionInfo;
import android.media.projection.ReviewGrantedConsentResult;
import android.os.IBinder;
import android.os.IBinder;
import android.view.ContentRecordingSession;
import android.view.ContentRecordingSession;


/** {@hide} */
/** {@hide} */
interface IMediaProjectionManager {
interface IMediaProjectionManager {
    /**
     * Intent extra indicating if user must review access to the consent token already granted.
     */
    const String EXTRA_USER_REVIEW_GRANTED_CONSENT = "extra_media_projection_user_consent_required";

    /**
     * Intent extra indicating the package attempting to re-use granted consent.
     */
    const String EXTRA_PACKAGE_REUSING_GRANTED_CONSENT =
            "extra_media_projection_package_reusing_consent";

    @UnsupportedAppUsage
    @UnsupportedAppUsage
    boolean hasProjectionPermission(int uid, String packageName);
    boolean hasProjectionPermission(int uid, String packageName);


@@ -36,6 +48,21 @@ interface IMediaProjectionManager {
    IMediaProjection createProjection(int uid, String packageName, int type,
    IMediaProjection createProjection(int uid, String packageName, int type,
            boolean permanentGrant);
            boolean permanentGrant);


    /**
     * Returns the current {@link IMediaProjection} instance associated with the given
     * package, or {@code null} if it is not possible to re-use the current projection.
     *
     * <p>Should only be invoked when the user has reviewed consent for a re-used projection token.
     * Requires that there is a prior session waiting for the user to review consent, and the given
     * package details match those on the current projection.
     *
     * @see {@link #isCurrentProjection}
     */
    @EnforcePermission("android.Manifest.permission.MANAGE_MEDIA_PROJECTION")
    @JavaPassthrough(annotation = "@android.annotation.RequiresPermission(android.Manifest"
            + ".permission.MANAGE_MEDIA_PROJECTION)")
    IMediaProjection getProjection(int uid, String packageName);

    /**
    /**
     * Returns {@code true} if the given {@link IMediaProjection} corresponds to the current
     * Returns {@code true} if the given {@link IMediaProjection} corresponds to the current
     * projection, or {@code false} otherwise.
     * projection, or {@code false} otherwise.
@@ -58,7 +85,7 @@ interface IMediaProjectionManager {
     */
     */
    @JavaPassthrough(annotation = "@android.annotation.RequiresPermission(android.Manifest"
    @JavaPassthrough(annotation = "@android.annotation.RequiresPermission(android.Manifest"
            + ".permission.MANAGE_MEDIA_PROJECTION)")
            + ".permission.MANAGE_MEDIA_PROJECTION)")
    void requestConsentForInvalidProjection(IMediaProjection projection);
    void requestConsentForInvalidProjection(in IMediaProjection projection);


    @JavaPassthrough(annotation = "@android.annotation.RequiresPermission(android.Manifest"
    @JavaPassthrough(annotation = "@android.annotation.RequiresPermission(android.Manifest"
            + ".permission.MANAGE_MEDIA_PROJECTION)")
            + ".permission.MANAGE_MEDIA_PROJECTION)")
@@ -94,9 +121,32 @@ interface IMediaProjectionManager {
     *
     *
     * @param incomingSession the nullable incoming content recording session
     * @param incomingSession the nullable incoming content recording session
     * @param projection      the non-null projection the session describes
     * @param projection      the non-null projection the session describes
     * @throws SecurityException If the provided projection is not current.
     */
     */
  @JavaPassthrough(annotation = "@android.annotation.RequiresPermission(android.Manifest"
  @JavaPassthrough(annotation = "@android.annotation.RequiresPermission(android.Manifest"
            + ".permission.MANAGE_MEDIA_PROJECTION)")
            + ".permission.MANAGE_MEDIA_PROJECTION)")
    boolean setContentRecordingSession(in ContentRecordingSession incomingSession,
    boolean setContentRecordingSession(in ContentRecordingSession incomingSession,
            in IMediaProjection projection);
            in IMediaProjection projection);

    /**
     * Sets the result of the user reviewing the recording permission, when the host app is re-using
     * the consent token.
     *
     * <p>Ignores the provided result if the given projection is not the current projection.
     *
     * <p>Based on the given result:
     * <ul>
     *   <li>If UNKNOWN or RECORD_CANCEL, then tear down the recording.</li>
     *   <li>If RECORD_CONTENT_DISPLAY, then record the default display.</li>
     *   <li>If RECORD_CONTENT_TASK, record the task indicated by
     *     {@link IMediaProjection#getLaunchCookie}.</li>
     * </ul>
     * @param projection The projection associated with the consent result. Must be the current
     * projection instance, unless the given result is RECORD_CANCEL.
     */
    @EnforcePermission("android.Manifest.permission.MANAGE_MEDIA_PROJECTION")
    @JavaPassthrough(annotation = "@android.annotation.RequiresPermission(android.Manifest"
            + ".permission.MANAGE_MEDIA_PROJECTION)")
    void setUserReviewGrantedConsentResult(ReviewGrantedConsentResult consentResult,
            in @nullable IMediaProjection projection);
}
}
+31 −0
Original line number Original line Diff line number Diff line
/*
 * Copyright (C) 2023 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;

/**
 * Indicates result of user interacting with consent dialog, when their review is required due to
 * app re-using the token.

 * @hide
 */
@Backing(type="int")
enum ReviewGrantedConsentResult {
    UNKNOWN = -1,
    RECORD_CANCEL = 0,
    RECORD_CONTENT_DISPLAY = 1,
    RECORD_CONTENT_TASK = 2,
}
+201 −12
Original line number Original line Diff line number Diff line
@@ -19,9 +19,19 @@ package com.android.server.media.projection;
import static android.Manifest.permission.MANAGE_MEDIA_PROJECTION;
import static android.Manifest.permission.MANAGE_MEDIA_PROJECTION;
import static android.app.ActivityManagerInternal.MEDIA_PROJECTION_TOKEN_EVENT_CREATED;
import static android.app.ActivityManagerInternal.MEDIA_PROJECTION_TOKEN_EVENT_CREATED;
import static android.app.ActivityManagerInternal.MEDIA_PROJECTION_TOKEN_EVENT_DESTROYED;
import static android.app.ActivityManagerInternal.MEDIA_PROJECTION_TOKEN_EVENT_DESTROYED;
import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
import static android.media.projection.IMediaProjectionManager.EXTRA_PACKAGE_REUSING_GRANTED_CONSENT;
import static android.media.projection.IMediaProjectionManager.EXTRA_USER_REVIEW_GRANTED_CONSENT;
import static android.media.projection.ReviewGrantedConsentResult.RECORD_CANCEL;
import static android.media.projection.ReviewGrantedConsentResult.RECORD_CONTENT_DISPLAY;
import static android.media.projection.ReviewGrantedConsentResult.RECORD_CONTENT_TASK;
import static android.media.projection.ReviewGrantedConsentResult.UNKNOWN;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.Display.INVALID_DISPLAY;
import static android.view.Display.INVALID_DISPLAY;


import android.Manifest;
import android.Manifest;
import android.annotation.EnforcePermission;
import android.annotation.NonNull;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.Nullable;
import android.app.ActivityManagerInternal;
import android.app.ActivityManagerInternal;
@@ -30,7 +40,9 @@ import android.app.IProcessObserver;
import android.app.compat.CompatChanges;
import android.app.compat.CompatChanges;
import android.compat.annotation.ChangeId;
import android.compat.annotation.ChangeId;
import android.compat.annotation.EnabledSince;
import android.compat.annotation.EnabledSince;
import android.content.ComponentName;
import android.content.Context;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager;
@@ -45,11 +57,13 @@ import android.media.projection.IMediaProjectionManager;
import android.media.projection.IMediaProjectionWatcherCallback;
import android.media.projection.IMediaProjectionWatcherCallback;
import android.media.projection.MediaProjectionInfo;
import android.media.projection.MediaProjectionInfo;
import android.media.projection.MediaProjectionManager;
import android.media.projection.MediaProjectionManager;
import android.media.projection.ReviewGrantedConsentResult;
import android.os.Binder;
import android.os.Binder;
import android.os.Build;
import android.os.Build;
import android.os.Handler;
import android.os.Handler;
import android.os.IBinder;
import android.os.IBinder;
import android.os.Looper;
import android.os.Looper;
import android.os.PermissionEnforcer;
import android.os.RemoteException;
import android.os.RemoteException;
import android.os.SystemClock;
import android.os.SystemClock;
import android.os.UserHandle;
import android.os.UserHandle;
@@ -57,6 +71,7 @@ import android.util.ArrayMap;
import android.util.Slog;
import android.util.Slog;
import android.view.ContentRecordingSession;
import android.view.ContentRecordingSession;


import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.DumpUtils;
import com.android.internal.util.DumpUtils;
@@ -69,6 +84,7 @@ import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.io.PrintWriter;
import java.time.Duration;
import java.time.Duration;
import java.util.Map;
import java.util.Map;
import java.util.Objects;


/**
/**
 * Manages MediaProjection sessions.
 * Manages MediaProjection sessions.
@@ -161,10 +177,9 @@ public final class MediaProjectionManagerService extends SystemService
        }
        }
    }
    }



    @Override
    @Override
    public void onStart() {
    public void onStart() {
        publishBinderService(Context.MEDIA_PROJECTION_SERVICE, new BinderService(),
        publishBinderService(Context.MEDIA_PROJECTION_SERVICE, new BinderService(mContext),
                false /*allowIsolated*/);
                false /*allowIsolated*/);
        mMediaRouter.addCallback(MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY, mMediaRouterCallback,
        mMediaRouter.addCallback(MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY, mMediaRouterCallback,
                MediaRouter.CALLBACK_FLAG_PASSIVE_DISCOVERY);
                MediaRouter.CALLBACK_FLAG_PASSIVE_DISCOVERY);
@@ -305,6 +320,10 @@ public final class MediaProjectionManagerService extends SystemService
                }
                }
                return false;
                return false;
            }
            }
            if (mProjectionGrant != null) {
                // Cache the session details.
                mProjectionGrant.mSession = incomingSession;
            }
            return true;
            return true;
        }
        }
    }
    }
@@ -323,9 +342,8 @@ public final class MediaProjectionManagerService extends SystemService
        }
        }
    }
    }



    /**
    /**
     * Reshows the permisison dialog for the user to review consent they've already granted in
     * Re-shows the permission dialog for the user to review consent they've already granted in
     * the given projection instance.
     * the given projection instance.
     *
     *
     * <p>Preconditions:
     * <p>Preconditions:
@@ -337,18 +355,111 @@ public final class MediaProjectionManagerService extends SystemService
     * <p>Returns immediately but waits to start recording until user has reviewed their consent.
     * <p>Returns immediately but waits to start recording until user has reviewed their consent.
     */
     */
    @VisibleForTesting
    @VisibleForTesting
    void requestConsentForInvalidProjection(IMediaProjection projection) {
    void requestConsentForInvalidProjection() {
        synchronized (mLock) {
        synchronized (mLock) {
            Slog.v(TAG, "Reusing token: Reshow dialog for due to invalid projection.");
            Slog.v(TAG, "Reusing token: Reshow dialog for due to invalid projection.");
            // TODO(b/274790702): Trigger the permission dialog again in SysUI.
            // Trigger the permission dialog again in SysUI
            // Do not handle the result; SysUI will update us when the user has consented.
            mContext.startActivityAsUser(buildReviewGrantedConsentIntent(),
                    UserHandle.getUserHandleForUid(mProjectionGrant.uid));
        }
    }

    /**
     * Returns an intent to re-show the consent dialog in SysUI. Should only be used for the
     * scenario where the host app has re-used the consent token.
     *
     * <p>Consent dialog result handled in
     * {@link BinderService#setUserReviewGrantedConsentResult(int)}.
     */
    private Intent buildReviewGrantedConsentIntent() {
        final String permissionDialogString = mContext.getResources().getString(
                R.string.config_mediaProjectionPermissionDialogComponent);
        final ComponentName mediaProjectionPermissionDialogComponent =
                ComponentName.unflattenFromString(permissionDialogString);
        // We can use mProjectionGrant since we already checked that it matches the given token.
        return new Intent().setComponent(mediaProjectionPermissionDialogComponent)
                .putExtra(EXTRA_USER_REVIEW_GRANTED_CONSENT, true)
                .putExtra(EXTRA_PACKAGE_REUSING_GRANTED_CONSENT, mProjectionGrant.packageName)
                .setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
    }

    /**
     * Handles result of dialog shown from {@link BinderService#buildReviewGrantedConsentIntent()}.
     *
     * <p>Tears down session if user did not consent, or starts mirroring if user did consent.
     */
    @VisibleForTesting
    void setUserReviewGrantedConsentResult(@ReviewGrantedConsentResult int consentResult,
            @Nullable IMediaProjection projection) {
        synchronized (mLock) {
            final boolean consentGranted =
                    consentResult == RECORD_CONTENT_DISPLAY || consentResult == RECORD_CONTENT_TASK;
            if (consentGranted && projection == null || !isCurrentProjection(
                    projection.asBinder())) {
                Slog.v(TAG, "Reusing token: Ignore consent result of " + consentResult + " for a "
                        + "token that isn't current");
                return;
            }
            if (mProjectionGrant == null) {
                Slog.w(TAG, "Reusing token: Can't review consent with no ongoing projection.");
                return;
            }
            if (mProjectionGrant.mSession == null
                    || !mProjectionGrant.mSession.isWaitingToRecord()) {
                Slog.w(TAG, "Reusing token: Ignore consent result " + consentResult
                        + " if not waiting for the result.");
                return;
            }
            Slog.v(TAG, "Reusing token: Handling user consent result " + consentResult);
            switch (consentResult) {
                case UNKNOWN:
                case RECORD_CANCEL:
                    // Pass in null to stop mirroring.
                    setReviewedConsentSessionLocked(/* session= */ null);
                    // The grant may now be null if setting the session failed.
                    if (mProjectionGrant != null) {
                        // Always stop the projection.
                        mProjectionGrant.stop();
                    }
                    break;
                case RECORD_CONTENT_DISPLAY:
                    // TODO(270118861) The app may have specified a particular id in the virtual
                    //  display config. However - below will always return INVALID since it checks
                    //  that window manager mirroring is not enabled (it is always enabled for MP).
                    setReviewedConsentSessionLocked(ContentRecordingSession.createDisplaySession(
                            DEFAULT_DISPLAY));
                    break;
                case RECORD_CONTENT_TASK:
                    setReviewedConsentSessionLocked(ContentRecordingSession.createTaskSession(
                            mProjectionGrant.getLaunchCookie()));
                    break;
            }
        }
    }

    /**
     * Updates the session after the user has reviewed consent. There must be a current session.
     *
     * @param session The new session details, or {@code null} to stop recording.
     */
    private void setReviewedConsentSessionLocked(@Nullable ContentRecordingSession session) {
        if (session != null) {
            session.setWaitingToRecord(false);
            session.setVirtualDisplayId(mProjectionGrant.mVirtualDisplayId);
        }

        Slog.v(TAG, "Reusing token: Processed consent so set the session " + session);
        if (!setContentRecordingSession(session)) {
            Slog.e(TAG, "Reusing token: Failed to set session for reused consent, so stop");
            // Do not need to invoke stop; updating the session does it for us.
        }
        }
    }
    }


    // TODO(b/261563516): Remove internal method and test aidl directly, here and elsewhere.
    // TODO(b/261563516): Remove internal method and test aidl directly, here and elsewhere.
    @VisibleForTesting
    @VisibleForTesting
    MediaProjection createProjectionInternal(int uid, String packageName, int type,
    MediaProjection createProjectionInternal(int uid, String packageName, int type,
            boolean isPermanentGrant, UserHandle callingUser,
            boolean isPermanentGrant, UserHandle callingUser) {
            boolean packageAttemptedReusingGrantedConsent) {
        MediaProjection projection;
        MediaProjection projection;
        ApplicationInfo ai;
        ApplicationInfo ai;
        try {
        try {
@@ -371,6 +482,34 @@ public final class MediaProjectionManagerService extends SystemService
        return projection;
        return projection;
    }
    }


    // TODO(b/261563516): Remove internal method and test aidl directly, here and elsewhere.
    @VisibleForTesting
    MediaProjection getProjectionInternal(int uid, String packageName) {
        final long callingToken = Binder.clearCallingIdentity();
        try {
            // Supposedly the package has re-used the user's consent; confirm the provided details
            // against the current projection token before re-using the current projection.
            if (mProjectionGrant == null || mProjectionGrant.mSession == null
                    || !mProjectionGrant.mSession.isWaitingToRecord()) {
                Slog.e(TAG, "Reusing token: Not possible to reuse the current projection "
                        + "instance");
                return null;
            }
                // The package matches, go ahead and re-use the token for this request.
            if (mProjectionGrant.uid == uid
                    && Objects.equals(mProjectionGrant.packageName, packageName)) {
                Slog.v(TAG, "Reusing token: getProjection can reuse the current projection");
                return mProjectionGrant;
            } else {
                Slog.e(TAG, "Reusing token: Not possible to reuse the current projection "
                        + "instance due to package details mismatching");
                return null;
            }
        } finally {
            Binder.restoreCallingIdentity(callingToken);
        }
    }

    @VisibleForTesting
    @VisibleForTesting
    MediaProjectionInfo getActiveProjectionInfo() {
    MediaProjectionInfo getActiveProjectionInfo() {
        synchronized (mLock) {
        synchronized (mLock) {
@@ -395,6 +534,10 @@ public final class MediaProjectionManagerService extends SystemService


    private final class BinderService extends IMediaProjectionManager.Stub {
    private final class BinderService extends IMediaProjectionManager.Stub {


        BinderService(Context context) {
            super(PermissionEnforcer.fromContext(context));
        }

        @Override // Binder call
        @Override // Binder call
        public boolean hasProjectionPermission(int uid, String packageName) {
        public boolean hasProjectionPermission(int uid, String packageName) {
            final long token = Binder.clearCallingIdentity();
            final long token = Binder.clearCallingIdentity();
@@ -424,7 +567,25 @@ public final class MediaProjectionManagerService extends SystemService
            }
            }
            final UserHandle callingUser = Binder.getCallingUserHandle();
            final UserHandle callingUser = Binder.getCallingUserHandle();
            return createProjectionInternal(uid, packageName, type, isPermanentGrant,
            return createProjectionInternal(uid, packageName, type, isPermanentGrant,
                    callingUser, false);
                    callingUser);
        }

        @Override // Binder call
        @EnforcePermission(MANAGE_MEDIA_PROJECTION)
        public IMediaProjection getProjection(int uid, String packageName) {
            getProjection_enforcePermission();
            if (packageName == null || packageName.isEmpty()) {
                throw new IllegalArgumentException("package name must not be empty");
            }

            MediaProjection projection;
            final long callingToken = Binder.clearCallingIdentity();
            try {
                projection = getProjectionInternal(uid, packageName);
            } finally {
                Binder.restoreCallingIdentity(callingToken);
            }
            return projection;
        }
        }


        @Override // Binder call
        @Override // Binder call
@@ -562,7 +723,7 @@ public final class MediaProjectionManagerService extends SystemService
        }
        }


        @Override
        @Override
        public void requestConsentForInvalidProjection(IMediaProjection projection) {
        public void requestConsentForInvalidProjection(@NonNull IMediaProjection projection) {
            if (mContext.checkCallingOrSelfPermission(Manifest.permission.MANAGE_MEDIA_PROJECTION)
            if (mContext.checkCallingOrSelfPermission(Manifest.permission.MANAGE_MEDIA_PROJECTION)
                    != PackageManager.PERMISSION_GRANTED) {
                    != PackageManager.PERMISSION_GRANTED) {
                throw new SecurityException("Requires MANAGE_MEDIA_PROJECTION to check if the given"
                throw new SecurityException("Requires MANAGE_MEDIA_PROJECTION to check if the given"
@@ -577,7 +738,22 @@ public final class MediaProjectionManagerService extends SystemService
            // Remove calling app identity before performing any privileged operations.
            // Remove calling app identity before performing any privileged operations.
            final long token = Binder.clearCallingIdentity();
            final long token = Binder.clearCallingIdentity();
            try {
            try {
                MediaProjectionManagerService.this.requestConsentForInvalidProjection(projection);
                MediaProjectionManagerService.this.requestConsentForInvalidProjection();
            } finally {
                Binder.restoreCallingIdentity(token);
            }
        }

        @Override // Binder call
        @EnforcePermission(MANAGE_MEDIA_PROJECTION)
        public void setUserReviewGrantedConsentResult(@ReviewGrantedConsentResult int consentResult,
                @Nullable IMediaProjection projection) {
            setUserReviewGrantedConsentResult_enforcePermission();
            // Remove calling app identity before performing any privileged operations.
            final long token = Binder.clearCallingIdentity();
            try {
                MediaProjectionManagerService.this.setUserReviewGrantedConsentResult(consentResult,
                        projection);
            } finally {
            } finally {
                Binder.restoreCallingIdentity(token);
                Binder.restoreCallingIdentity(token);
            }
            }
@@ -594,7 +770,6 @@ public final class MediaProjectionManagerService extends SystemService
            }
            }
        }
        }



        private boolean checkPermission(String packageName, String permission) {
        private boolean checkPermission(String packageName, String permission) {
            return mContext.getPackageManager().checkPermission(permission, packageName)
            return mContext.getPackageManager().checkPermission(permission, packageName)
                    == PackageManager.PERMISSION_GRANTED;
                    == PackageManager.PERMISSION_GRANTED;
@@ -630,6 +805,8 @@ public final class MediaProjectionManagerService extends SystemService
        // Set if MediaProjection#createVirtualDisplay has been invoked previously (it
        // Set if MediaProjection#createVirtualDisplay has been invoked previously (it
        // should only be called once).
        // should only be called once).
        private int mVirtualDisplayId = INVALID_DISPLAY;
        private int mVirtualDisplayId = INVALID_DISPLAY;
        // The associated session details already sent to WindowManager.
        private ContentRecordingSession mSession;


        MediaProjection(int type, int uid, String packageName, int targetSdkVersion,
        MediaProjection(int type, int uid, String packageName, int targetSdkVersion,
                boolean isPrivileged) {
                boolean isPrivileged) {
@@ -883,6 +1060,18 @@ public final class MediaProjectionManagerService extends SystemService
            }
            }
            synchronized (mLock) {
            synchronized (mLock) {
                mVirtualDisplayId = displayId;
                mVirtualDisplayId = displayId;

                // If prior session was does not have a valid display id, then update the display
                // so recording can start.
                if (mSession != null && mSession.getVirtualDisplayId() == INVALID_DISPLAY) {
                    Slog.v(TAG, "Virtual display now created, so update session with the virtual "
                            + "display id");
                    mSession.setVirtualDisplayId(mVirtualDisplayId);
                    if (!setContentRecordingSession(mSession)) {
                        Slog.e(TAG, "Failed to set session for virtual display id");
                        // Do not need to invoke stop; updating the session does it for us.
                    }
                }
            }
            }
        }
        }


+2 −2
Original line number Original line Diff line number Diff line
@@ -80,7 +80,7 @@ final class ContentRecordingController {
        }
        }
        // Invalid scenario: ignore identical incoming session.
        // Invalid scenario: ignore identical incoming session.
        if (ContentRecordingSession.isProjectionOnSameDisplay(mSession, incomingSession)) {
        if (ContentRecordingSession.isProjectionOnSameDisplay(mSession, incomingSession)) {
            // TODO(242833866) if incoming session is no longer waiting to record, allow
            // TODO(242833866): if incoming session is no longer waiting to record, allow
            //  the update through.
            //  the update through.


            ProtoLog.v(WM_DEBUG_CONTENT_RECORDING,
            ProtoLog.v(WM_DEBUG_CONTENT_RECORDING,
@@ -99,7 +99,7 @@ final class ContentRecordingController {
            incomingDisplayContent = wmService.mRoot.getDisplayContentOrCreate(
            incomingDisplayContent = wmService.mRoot.getDisplayContentOrCreate(
                    incomingSession.getVirtualDisplayId());
                    incomingSession.getVirtualDisplayId());
            incomingDisplayContent.setContentRecordingSession(incomingSession);
            incomingDisplayContent.setContentRecordingSession(incomingSession);
            // TODO(b/270118861) When user grants consent to re-use, explicitly ask ContentRecorder
            // TODO(b/270118861): When user grants consent to re-use, explicitly ask ContentRecorder
            //  to update, since no config/display change arrives. Mark recording as black.
            //  to update, since no config/display change arrives. Mark recording as black.
        }
        }
        // Takeover and stopping scenario: stop recording on the pre-existing session.
        // Takeover and stopping scenario: stop recording on the pre-existing session.
Loading