Merge "[Partial Screenshare] Respond to task changes" into tm-qpr-dev am: f5a7752bf2

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/17570090

Change-Id: If3ba9ede80fc52a1a5d85604d947b05bb4e8f4d7
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
Naomi Musgrave
2022-08-12 11:00:56 +00:00
committed by Automerger Merge Worker
10 changed files with 172 additions and 55 deletions

View File

@@ -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",

View File

@@ -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;

View File

@@ -335,8 +335,8 @@ public final class MediaProjectionManagerService extends SystemService
@Override // Binder call
public void stopActiveProjection() {
if (mContext.checkCallingPermission(Manifest.permission.MANAGE_MEDIA_PROJECTION)
!= PackageManager.PERMISSION_GRANTED) {
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);

View File

@@ -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;
clearContentRecordingSession();
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();
// 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();
}
}

View File

@@ -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);

View File

@@ -6071,7 +6071,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
mRemoved = true;
if (mContentRecorder != null) {
mContentRecorder.remove();
mContentRecorder.stopRecording();
}
// Only update focus/visibility for the last one because there may be many root tasks are
@@ -6351,6 +6351,15 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
}
}
/**
* The MediaProjection instance is torn down.
*/
@VisibleForTesting void stopMediaProjection() {
if (mContentRecorder != null) {
mContentRecorder.stopMediaProjection();
}
}
/**
* Sets the incoming recording session. Should only be used when starting to record on
* this display; stopping recording is handled separately when the display is destroyed.

View File

@@ -880,6 +880,8 @@ public abstract class WindowManagerInternal {
* Must be invoked for a valid MediaProjection session.
*
* @param incomingSession the nullable incoming content recording session
* @return {@code true} if successfully set the session, or {@code false} if the session
* could not be prepared and the session needs to be torn down.
*/
public abstract void setContentRecordingSession(ContentRecordingSession incomingSession);
public abstract boolean setContentRecordingSession(ContentRecordingSession incomingSession);
}

View File

@@ -8291,14 +8291,15 @@ public class WindowManagerService extends IWindowManager.Stub
}
@Override
public void setContentRecordingSession(@Nullable ContentRecordingSession incomingSession) {
public boolean setContentRecordingSession(
@Nullable ContentRecordingSession incomingSession) {
synchronized (mGlobalLock) {
// Allow the controller to handle teardown or a non-task session.
// Allow the controller to handle teardown of a non-task session.
if (incomingSession == null
|| incomingSession.getContentToRecord() != RECORD_CONTENT_TASK) {
mContentRecordingController.setContentRecordingSessionLocked(incomingSession,
WindowManagerService.this);
return;
return true;
}
// For a task session, find the activity identified by the launch cookie.
final WindowContainerToken wct = getTaskWindowContainerTokenForLaunchCookie(
@@ -8306,15 +8307,14 @@ public class WindowManagerService extends IWindowManager.Stub
if (wct == null) {
Slog.w(TAG, "Handling a new recording session; unable to find the "
+ "WindowContainerToken");
mContentRecordingController.setContentRecordingSessionLocked(null,
WindowManagerService.this);
return;
return false;
}
// Replace the launch cookie in the session details with the task's
// WindowContainerToken.
incomingSession.setTokenToRecord(wct.asBinder());
mContentRecordingController.setContentRecordingSessionLocked(incomingSession,
WindowManagerService.this);
return true;
}
}
}

View File

@@ -31,9 +31,11 @@ import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.anyFloat;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.atLeast;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.never;
import android.content.res.Configuration;
import android.graphics.Point;
import android.graphics.Rect;
import android.hardware.display.VirtualDisplay;
@@ -53,6 +55,8 @@ import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import java.util.concurrent.CountDownLatch;
@@ -68,17 +72,21 @@ import java.util.concurrent.CountDownLatch;
public class ContentRecorderTests extends WindowTestsBase {
private static final IBinder TEST_TOKEN = new RecordingTestToken();
private static IBinder sTaskWindowContainerToken;
private Task mTask;
private final ContentRecordingSession mDisplaySession =
ContentRecordingSession.createDisplaySession(TEST_TOKEN);
private ContentRecordingSession mTaskSession;
private static Point sSurfaceSize;
private ContentRecorder mContentRecorder;
@Mock private ContentRecorder.MediaProjectionManagerWrapper mMediaProjectionManagerWrapper;
private SurfaceControl mRecordedSurface;
// Handle feature flag.
private ConfigListener mConfigListener;
private CountDownLatch mLatch;
@Before public void setUp() {
MockitoAnnotations.initMocks(this);
// GIVEN SurfaceControl can successfully mirror the provided surface.
sSurfaceSize = new Point(
mDefaultDisplay.getDefaultTaskDisplayArea().getBounds().width(),
@@ -92,7 +100,8 @@ public class ContentRecorderTests extends WindowTestsBase {
final int displayId = virtualDisplay.getDisplay().getDisplayId();
mWm.mRoot.onDisplayAdded(displayId);
final DisplayContent virtualDisplayContent = mWm.mRoot.getDisplayContent(displayId);
mContentRecorder = new ContentRecorder(virtualDisplayContent);
mContentRecorder = new ContentRecorder(virtualDisplayContent,
mMediaProjectionManagerWrapper);
spyOn(virtualDisplayContent);
// GIVEN MediaProjection has already initialized the WindowToken of the DisplayArea to
@@ -179,7 +188,7 @@ public class ContentRecorderTests extends WindowTestsBase {
mContentRecorder.setContentRecordingSession(session);
mContentRecorder.updateRecording();
assertThat(mContentRecorder.isCurrentlyRecording()).isFalse();
// TODO(b/219761722) validate VirtualDisplay is torn down when can't set up task recording.
verify(mMediaProjectionManagerWrapper).stopActiveProjection();
}
@Test
@@ -190,7 +199,7 @@ public class ContentRecorderTests extends WindowTestsBase {
mContentRecorder.setContentRecordingSession(invalidTaskSession);
mContentRecorder.updateRecording();
assertThat(mContentRecorder.isCurrentlyRecording()).isFalse();
// TODO(b/219761722) validate VirtualDisplay is torn down when can't set up task recording.
verify(mMediaProjectionManagerWrapper).stopActiveProjection();
}
@Test
@@ -218,9 +227,24 @@ public class ContentRecorderTests extends WindowTestsBase {
mContentRecorder.updateRecording();
mContentRecorder.onConfigurationChanged(ORIENTATION_PORTRAIT);
verify(mTransaction, atLeastOnce()).setPosition(eq(mRecordedSurface), anyFloat(),
verify(mTransaction, atLeast(2)).setPosition(eq(mRecordedSurface), anyFloat(),
anyFloat());
verify(mTransaction, atLeastOnce()).setMatrix(eq(mRecordedSurface), anyFloat(), anyFloat(),
verify(mTransaction, atLeast(2)).setMatrix(eq(mRecordedSurface), anyFloat(), anyFloat(),
anyFloat(), anyFloat());
}
@Test
public void testOnTaskConfigurationChanged_resizesSurface() {
mContentRecorder.setContentRecordingSession(mTaskSession);
mContentRecorder.updateRecording();
Configuration config = mTask.getConfiguration();
config.orientation = ORIENTATION_PORTRAIT;
mTask.onConfigurationChanged(config);
verify(mTransaction, atLeast(2)).setPosition(eq(mRecordedSurface), anyFloat(),
anyFloat());
verify(mTransaction, atLeast(2)).setMatrix(eq(mRecordedSurface), anyFloat(), anyFloat(),
anyFloat(), anyFloat());
}
@@ -240,20 +264,30 @@ public class ContentRecorderTests extends WindowTestsBase {
}
@Test
public void testRemove_stopsRecording() {
public void testStopRecording_stopsRecording() {
mContentRecorder.setContentRecordingSession(mDisplaySession);
mContentRecorder.updateRecording();
mContentRecorder.remove();
mContentRecorder.stopRecording();
assertThat(mContentRecorder.isCurrentlyRecording()).isFalse();
}
@Test
public void testRemove_neverRecording() {
mContentRecorder.remove();
public void testStopRecording_neverRecording() {
mContentRecorder.stopRecording();
assertThat(mContentRecorder.isCurrentlyRecording()).isFalse();
}
@Test
public void testRemoveTask_stopsRecording() {
mContentRecorder.setContentRecordingSession(mTaskSession);
mContentRecorder.updateRecording();
mTask.removeImmediately();
verify(mMediaProjectionManagerWrapper).stopActiveProjection();
}
@Test
public void testUpdateMirroredSurface_capturedAreaResized() {
mContentRecorder.setContentRecordingSession(mDisplaySession);
@@ -297,10 +331,10 @@ public class ContentRecorderTests extends WindowTestsBase {
*/
private IBinder setUpTaskWindowContainerToken(DisplayContent displayContent) {
final Task rootTask = createTask(displayContent);
final Task task = createTaskInRootTask(rootTask, 0 /* userId */);
mTask = createTaskInRootTask(rootTask, 0 /* userId */);
// Ensure the task is not empty.
createActivityRecord(displayContent, task);
return task.getTaskInfo().token.asBinder();
createActivityRecord(displayContent, mTask);
return mTask.getTaskInfo().token.asBinder();
}
/**

View File

@@ -87,7 +87,7 @@ public class ContentRecordingControllerTests extends WindowTestsBase {
@Test
public void testSetContentRecordingSessionLocked_invalidToken_notAccepted() {
ContentRecordingController controller = new ContentRecordingController();
// GIVEN an invalid display session (null token).
// GIVEN a session with a null token.
ContentRecordingSession session = ContentRecordingSession.createDisplaySession(null);
session.setDisplayId(DEFAULT_DISPLAY);
// WHEN updating the session.