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

Commit 0d9ba1c0 authored by Naomi Musgrave's avatar Naomi Musgrave
Browse files

[Partial Screenshare] Respond to task changes

Destroying the MediaProjection notifies the client app through the
MediaProjection callbacks that the app should handle the session
ending prematurely.

This may happen if setting up task recording failed, or if the
user exited the task that is being recorded.

Registering a listener to the task for configuration changes
also enables handling of the task entering/exiting split
screen.

Fixes: 219761722
Fixes: 237526949
Fixes: 236971595
Test: atest WmTests:ContentRecordingControllerTests
Test: atest WmTests:ContentRecorderTests
Change-Id: I415fb70433a031889f0486e99bb7d3b664333414
parent 65ca888c
Loading
Loading
Loading
Loading
+12 −0
Original line number Diff line number Diff line
@@ -1105,6 +1105,12 @@
      "group": "WM_DEBUG_TASKS",
      "at": "com\/android\/server\/wm\/RootWindowContainer.java"
    },
    "-1018968224": {
      "message": "Recorded task is removed, so stop recording on display %d",
      "level": "VERBOSE",
      "group": "WM_DEBUG_CONTENT_RECORDING",
      "at": "com\/android\/server\/wm\/ContentRecorder.java"
    },
    "-1016578046": {
      "message": "Moving to %s Relaunching %s callers=%s",
      "level": "INFO",
@@ -2251,6 +2257,12 @@
      "group": "WM_DEBUG_FOCUS",
      "at": "com\/android\/server\/wm\/WindowManagerService.java"
    },
    "96494268": {
      "message": "Stop MediaProjection on virtual display %d",
      "level": "VERBOSE",
      "group": "WM_DEBUG_CONTENT_RECORDING",
      "at": "com\/android\/server\/wm\/ContentRecorder.java"
    },
    "100936473": {
      "message": "Wallpaper animation!",
      "level": "VERBOSE",
+0 −2
Original line number Diff line number Diff line
@@ -70,8 +70,6 @@ import android.hardware.display.DisplayManagerGlobal;
import android.hardware.display.DisplayManagerInternal;
import android.hardware.display.DisplayManagerInternal.DisplayGroupListener;
import android.hardware.display.DisplayManagerInternal.DisplayTransactionListener;
import android.hardware.display.DisplayManagerInternal.RefreshRateLimitation;
import android.hardware.display.DisplayManagerInternal.RefreshRateRange;
import android.hardware.display.DisplayViewport;
import android.hardware.display.DisplayedContentSample;
import android.hardware.display.DisplayedContentSamplingAttributes;
+9 −4
Original line number Diff line number Diff line
@@ -335,7 +335,7 @@ public final class MediaProjectionManagerService extends SystemService

        @Override // Binder call
        public void stopActiveProjection() {
            if (mContext.checkCallingPermission(Manifest.permission.MANAGE_MEDIA_PROJECTION)
            if (mContext.checkCallingOrSelfPermission(Manifest.permission.MANAGE_MEDIA_PROJECTION)
                    != PackageManager.PERMISSION_GRANTED) {
                throw new SecurityException("Requires MANAGE_MEDIA_PROJECTION in order to add "
                        + "projection callbacks");
@@ -393,9 +393,14 @@ public final class MediaProjectionManagerService extends SystemService
                    if (!isValidMediaProjection(projection)) {
                        throw new SecurityException("Invalid media projection");
                    }
                    LocalServices.getService(
                    if (!LocalServices.getService(
                            WindowManagerInternal.class).setContentRecordingSession(
                            incomingSession);
                            incomingSession)) {
                        // Unable to start mirroring, so tear down this projection.
                        if (mProjectionGrant != null) {
                            mProjectionGrant.stop();
                        }
                    }
                }
            } finally {
                Binder.restoreCallingIdentity(origId);
+81 −22
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@

package com.android.server.wm;

import static android.content.res.Configuration.ORIENTATION_UNDEFINED;
import static android.view.ContentRecordingSession.RECORD_CONTENT_DISPLAY;
import static android.view.ContentRecordingSession.RECORD_CONTENT_TASK;

@@ -26,6 +27,7 @@ import android.annotation.Nullable;
import android.content.res.Configuration;
import android.graphics.Point;
import android.graphics.Rect;
import android.media.projection.MediaProjectionManager;
import android.os.IBinder;
import android.provider.DeviceConfig;
import android.view.ContentRecordingSession;
@@ -38,7 +40,7 @@ import com.android.internal.protolog.common.ProtoLog;
/**
 * Manages content recording for a particular {@link DisplayContent}.
 */
final class ContentRecorder {
final class ContentRecorder implements WindowContainerListener {

    /**
     * The key for accessing the device config that controls if task recording is supported.
@@ -51,6 +53,8 @@ final class ContentRecorder {
    @NonNull
    private final DisplayContent mDisplayContent;

    @Nullable private final MediaProjectionManagerWrapper mMediaProjectionManager;

    /**
     * The session for content recording, or null if this DisplayContent is not being used for
     * recording.
@@ -73,8 +77,26 @@ final class ContentRecorder {
     */
    @Nullable private Rect mLastRecordedBounds = null;

    /**
     * The last configuration orientation.
     */
    private int mLastOrientation = ORIENTATION_UNDEFINED;

    ContentRecorder(@NonNull DisplayContent displayContent) {
        this(displayContent, () -> {
            MediaProjectionManager mpm = displayContent.mWmService.mContext.getSystemService(
                    MediaProjectionManager.class);
            if (mpm != null) {
                mpm.stopActiveProjection();
            }
        });
    }

    @VisibleForTesting
    ContentRecorder(@NonNull DisplayContent displayContent,
            @NonNull MediaProjectionManagerWrapper mediaProjectionManager) {
        mDisplayContent = displayContent;
        mMediaProjectionManager = mediaProjectionManager;
    }

    /**
@@ -95,7 +117,7 @@ final class ContentRecorder {
    }

    /**
     * Start recording if this DisplayContent no longer has content. Stop recording if it now
     * Start recording if this DisplayContent no longer has content. Pause recording if it now
     * has content or the display is not on.
     */
    @VisibleForTesting void updateRecording() {
@@ -187,7 +209,7 @@ final class ContentRecorder {
    /**
     * Stops recording on this DisplayContent, and updates the session details.
     */
    void remove() {
    void stopRecording() {
        if (mRecordedSurface != null) {
            // Do not wait for the mirrored surface to be garbage collected, but clean up
            // immediately.
@@ -195,7 +217,20 @@ final class ContentRecorder {
            mRecordedSurface = null;
            clearContentRecordingSession();
            // Do not need to force remove the VirtualDisplay; this is handled by the media
            // projection service.
            // projection service when the display is removed.
        }
    }


    /**
     * Ensure recording does not fall back to the display stack; ensure the recording is stopped
     * and the client notified by tearing down the virtual display.
     */
    void stopMediaProjection() {
        ProtoLog.v(WM_DEBUG_CONTENT_RECORDING,
                "Stop MediaProjection on virtual display %d", mDisplayContent.getDisplayId());
        if (mMediaProjectionManager != null) {
            mMediaProjectionManager.stopActiveProjection();
        }
    }

@@ -326,6 +361,8 @@ final class ContentRecorder {
                    ProtoLog.v(WM_DEBUG_CONTENT_RECORDING,
                            "Unable to retrieve task to start recording for "
                                    + "display %d", mDisplayContent.getDisplayId());
                } else {
                    taskToRecord.registerWindowContainerListener(this);
                }
                return taskToRecord;
            default:
@@ -342,9 +379,9 @@ final class ContentRecorder {
    /**
     * Exit this recording session.
     * <p>
     * If this is a task session, tear down the recording entirely. Do not fall back
     * to recording the entire display on the display stack; this would surprise the user
     * given they selected task capture.
     * If this is a task session, stop the recording entirely, including the MediaProjection.
     * Do not fall back to recording the entire display on the display stack; this would surprise
     * the user given they selected task capture.
     * </p><p>
     * If this is a display session, just stop recording by layer mirroring. Fall back to recording
     * from the display stack.
@@ -353,25 +390,14 @@ final class ContentRecorder {
    private void handleStartRecordingFailed() {
        final boolean shouldExitTaskRecording = mContentRecordingSession != null
                && mContentRecordingSession.getContentToRecord() == RECORD_CONTENT_TASK;
        if (shouldExitTaskRecording) {
            // Clean up the cached session first, since tearing down the display will generate
            // display
            // events which will trickle back to here.
            clearContentRecordingSession();
            tearDownVirtualDisplay();
        } else {
        clearContentRecordingSession();
        if (shouldExitTaskRecording) {
            // Clean up the cached session first to ensure recording doesn't re-start, since
            // tearing down the display will generate display events which will trickle back here.
            stopMediaProjection();
        }
    }

    /**
     * Ensure recording does not fall back to the display stack; ensure the recording is stopped
     * and the client notified by tearing down the virtual display.
     */
    private void tearDownVirtualDisplay() {
        // TODO(b/219761722) Clean up the VirtualDisplay if task mirroring fails
    }

    /**
     * Apply transformations to the mirrored surface to ensure the captured contents are scaled to
     * fit and centred in the output surface.
@@ -442,4 +468,37 @@ final class ContentRecorder {
        }
        return surfaceSize;
    }

    // WindowContainerListener
    @Override
    public void onRemoved() {
        ProtoLog.v(WM_DEBUG_CONTENT_RECORDING,
                "Recorded task is removed, so stop recording on display %d",
                mDisplayContent.getDisplayId());
        Task recordedTask = mRecordedWindowContainer.asTask();
        if (recordedTask == null
                || mContentRecordingSession.getContentToRecord() != RECORD_CONTENT_TASK) {
            return;
        }
        recordedTask.unregisterWindowContainerListener(this);
        // Stop mirroring and teardown.
        clearContentRecordingSession();
        // Clean up the cached session first to ensure recording doesn't re-start, since
        // tearing down the display will generate display events which will trickle back here.
        stopMediaProjection();
    }

    // WindowContainerListener
    @Override
    public void onMergedOverrideConfigurationChanged(
            Configuration mergedOverrideConfiguration) {
        WindowContainerListener.super.onMergedOverrideConfigurationChanged(
                mergedOverrideConfiguration);
        onConfigurationChanged(mLastOrientation);
        mLastOrientation = mergedOverrideConfiguration.orientation;
    }

    @VisibleForTesting interface MediaProjectionManagerWrapper {
        void stopActiveProjection();
    }
}
+4 −6
Original line number Diff line number Diff line
@@ -56,14 +56,13 @@ final class ContentRecordingController {
     * Updates the current recording session. If a new display is taking over recording, then
     * stops the prior display from recording.
     *
     * @param incomingSession the new recording session. Should either be {@code null}, to stop
     *                        the current session, or a session on a new/different display than the
     *                        current session.
     * @param incomingSession the new recording session. Should either have a {@code null} token, to
     *                        stop the current session, or a session on a new/different display
     *                        than the current session.
     * @param wmService       the window manager service
     */
    void setContentRecordingSessionLocked(@Nullable ContentRecordingSession incomingSession,
            @NonNull WindowManagerService wmService) {
        // TODO(b/219761722) handle a null session arriving due to task setup failing
        if (incomingSession != null && (!ContentRecordingSession.isValid(incomingSession)
                || ContentRecordingSession.isSameDisplay(mSession, incomingSession))) {
            // Ignore an invalid session, or a session for the same display as currently recording.
@@ -82,8 +81,7 @@ final class ContentRecordingController {
        }
        if (mSession != null) {
            // Update the pre-existing display about the new session.
            ProtoLog.v(WM_DEBUG_CONTENT_RECORDING,
                    "Pause the recording session on display %s",
            ProtoLog.v(WM_DEBUG_CONTENT_RECORDING, "Pause the recording session on display %s",
                    mDisplayContent.getDisplayId());
            mDisplayContent.pauseRecording();
            mDisplayContent.setContentRecordingSession(null);
Loading