Merge "Refactor content recording out of DisplayContent" into tm-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
032eae98fe
@@ -295,6 +295,12 @@
|
||||
"group": "WM_DEBUG_STARTING_WINDOW",
|
||||
"at": "com\/android\/server\/wm\/ActivityRecord.java"
|
||||
},
|
||||
"-1781861035": {
|
||||
"message": "Display %d has content (%b) so pause recording",
|
||||
"level": "VERBOSE",
|
||||
"group": "WM_DEBUG_CONTENT_RECORDING",
|
||||
"at": "com\/android\/server\/wm\/ContentRecorder.java"
|
||||
},
|
||||
"-1777196134": {
|
||||
"message": "goodToGo(): No apps to animate, mPendingAnimations=%d",
|
||||
"level": "DEBUG",
|
||||
@@ -343,12 +349,6 @@
|
||||
"group": "WM_DEBUG_STATES",
|
||||
"at": "com\/android\/server\/wm\/ActivityTaskSupervisor.java"
|
||||
},
|
||||
"-1734445525": {
|
||||
"message": "Display %d has content (%b) so pause recording",
|
||||
"level": "VERBOSE",
|
||||
"group": "WM_DEBUG_CONTENT_RECORDING",
|
||||
"at": "com\/android\/server\/wm\/DisplayContent.java"
|
||||
},
|
||||
"-1730156332": {
|
||||
"message": "Display id=%d rotation changed to %d from %d, lastOrientation=%d",
|
||||
"level": "VERBOSE",
|
||||
@@ -445,6 +445,12 @@
|
||||
"group": "WM_DEBUG_LOCKTASK",
|
||||
"at": "com\/android\/server\/wm\/LockTaskController.java"
|
||||
},
|
||||
"-1605829532": {
|
||||
"message": "Unable to start recording due to null token for display %d",
|
||||
"level": "VERBOSE",
|
||||
"group": "WM_DEBUG_CONTENT_RECORDING",
|
||||
"at": "com\/android\/server\/wm\/ContentRecorder.java"
|
||||
},
|
||||
"-1598452494": {
|
||||
"message": "activityDestroyedLocked: r=%s",
|
||||
"level": "DEBUG",
|
||||
@@ -511,12 +517,6 @@
|
||||
"group": "WM_ERROR",
|
||||
"at": "com\/android\/server\/wm\/WindowManagerService.java"
|
||||
},
|
||||
"-1542296596": {
|
||||
"message": "Going ahead with updating recording for display %d to new bounds %s and\/or orientation %d.",
|
||||
"level": "VERBOSE",
|
||||
"group": "WM_DEBUG_CONTENT_RECORDING",
|
||||
"at": "com\/android\/server\/wm\/DisplayContent.java"
|
||||
},
|
||||
"-1539974875": {
|
||||
"message": "removeAppToken: %s delayed=%b Callers=%s",
|
||||
"level": "VERBOSE",
|
||||
@@ -715,6 +715,12 @@
|
||||
"group": "WM_DEBUG_TASKS",
|
||||
"at": "com\/android\/server\/wm\/RootWindowContainer.java"
|
||||
},
|
||||
"-1373875178": {
|
||||
"message": "Going ahead with updating recording for display %d to new bounds %s and\/or orientation %d.",
|
||||
"level": "VERBOSE",
|
||||
"group": "WM_DEBUG_CONTENT_RECORDING",
|
||||
"at": "com\/android\/server\/wm\/ContentRecorder.java"
|
||||
},
|
||||
"-1364754753": {
|
||||
"message": "Task vanished taskId=%d",
|
||||
"level": "VERBOSE",
|
||||
@@ -739,11 +745,11 @@
|
||||
"group": "WM_DEBUG_STARTING_WINDOW",
|
||||
"at": "com\/android\/server\/wm\/ActivityRecord.java"
|
||||
},
|
||||
"-1323003327": {
|
||||
"message": "Unexpectedly null window container; unable to update recording for display %d",
|
||||
"-1326876381": {
|
||||
"message": "Provided surface for recording on display %d is not present, so do not update the surface",
|
||||
"level": "VERBOSE",
|
||||
"group": "WM_DEBUG_CONTENT_RECORDING",
|
||||
"at": "com\/android\/server\/wm\/DisplayContent.java"
|
||||
"at": "com\/android\/server\/wm\/ContentRecorder.java"
|
||||
},
|
||||
"-1311436264": {
|
||||
"message": "Unregister task fragment organizer=%s uid=%d pid=%d",
|
||||
@@ -859,12 +865,6 @@
|
||||
"group": "WM_DEBUG_WINDOW_INSETS",
|
||||
"at": "com\/android\/server\/wm\/InsetsSourceProvider.java"
|
||||
},
|
||||
"-1179559337": {
|
||||
"message": "Unable to start recording due to invalid region for display %d",
|
||||
"level": "VERBOSE",
|
||||
"group": "WM_DEBUG_CONTENT_RECORDING",
|
||||
"at": "com\/android\/server\/wm\/DisplayContent.java"
|
||||
},
|
||||
"-1176488860": {
|
||||
"message": "SURFACE isSecure=%b: %s",
|
||||
"level": "INFO",
|
||||
@@ -1045,12 +1045,6 @@
|
||||
"group": "WM_DEBUG_DRAW",
|
||||
"at": "com\/android\/server\/wm\/WindowStateAnimator.java"
|
||||
},
|
||||
"-992111757": {
|
||||
"message": "Unable to retrieve window container to start recording for display %d",
|
||||
"level": "VERBOSE",
|
||||
"group": "WM_DEBUG_CONTENT_RECORDING",
|
||||
"at": "com\/android\/server\/wm\/DisplayContent.java"
|
||||
},
|
||||
"-986746907": {
|
||||
"message": "Starting window removed %s",
|
||||
"level": "DEBUG",
|
||||
@@ -1111,12 +1105,6 @@
|
||||
"group": "WM_DEBUG_STATES",
|
||||
"at": "com\/android\/server\/wm\/ActivityRecord.java"
|
||||
},
|
||||
"-922507769": {
|
||||
"message": "Display %d has no content and is on, so start recording for state %d",
|
||||
"level": "VERBOSE",
|
||||
"group": "WM_DEBUG_CONTENT_RECORDING",
|
||||
"at": "com\/android\/server\/wm\/DisplayContent.java"
|
||||
},
|
||||
"-917215012": {
|
||||
"message": "%s: caller %d is using old GET_TASKS but privileged; allowing",
|
||||
"level": "WARN",
|
||||
@@ -1291,6 +1279,12 @@
|
||||
"group": "WM_DEBUG_SCREEN_ON",
|
||||
"at": "com\/android\/server\/wm\/WindowManagerService.java"
|
||||
},
|
||||
"-751255162": {
|
||||
"message": "Unable to update recording for display %d to new bounds %s and\/or orientation %d, since the surface is not available.",
|
||||
"level": "VERBOSE",
|
||||
"group": "WM_DEBUG_CONTENT_RECORDING",
|
||||
"at": "com\/android\/server\/wm\/ContentRecorder.java"
|
||||
},
|
||||
"-743856570": {
|
||||
"message": "shouldWaitAnimatingExit: isAnimating: %s",
|
||||
"level": "DEBUG",
|
||||
@@ -1303,6 +1297,12 @@
|
||||
"group": "WM_DEBUG_CONFIGURATION",
|
||||
"at": "com\/android\/server\/wm\/ActivityRecord.java"
|
||||
},
|
||||
"-732715767": {
|
||||
"message": "Unable to retrieve window container to start recording for display %d",
|
||||
"level": "VERBOSE",
|
||||
"group": "WM_DEBUG_CONTENT_RECORDING",
|
||||
"at": "com\/android\/server\/wm\/ContentRecorder.java"
|
||||
},
|
||||
"-729530161": {
|
||||
"message": "Moving to DESTROYED: %s (no app)",
|
||||
"level": "VERBOSE",
|
||||
@@ -1549,18 +1549,6 @@
|
||||
"group": "WM_DEBUG_KEEP_SCREEN_ON",
|
||||
"at": "com\/android\/server\/wm\/RootWindowContainer.java"
|
||||
},
|
||||
"-479990726": {
|
||||
"message": "Unable to start recording due to null token for display %d",
|
||||
"level": "VERBOSE",
|
||||
"group": "WM_DEBUG_CONTENT_RECORDING",
|
||||
"at": "com\/android\/server\/wm\/DisplayContent.java"
|
||||
},
|
||||
"-473911359": {
|
||||
"message": "Display %d was already recording, so apply transformations if necessary",
|
||||
"level": "VERBOSE",
|
||||
"group": "WM_DEBUG_CONTENT_RECORDING",
|
||||
"at": "com\/android\/server\/wm\/DisplayContent.java"
|
||||
},
|
||||
"-463348344": {
|
||||
"message": "Removing and adding activity %s to root task at top callers=%s",
|
||||
"level": "INFO",
|
||||
@@ -1669,12 +1657,6 @@
|
||||
"group": "WM_DEBUG_TASKS",
|
||||
"at": "com\/android\/server\/wm\/RootWindowContainer.java"
|
||||
},
|
||||
"-370641936": {
|
||||
"message": "Unable to update recording for display %d to new bounds %s and\/or orientation %d, since the surface is not available.",
|
||||
"level": "VERBOSE",
|
||||
"group": "WM_DEBUG_CONTENT_RECORDING",
|
||||
"at": "com\/android\/server\/wm\/DisplayContent.java"
|
||||
},
|
||||
"-360208282": {
|
||||
"message": "Animating wallpapers: old: %s hidden=%b new: %s hidden=%b",
|
||||
"level": "VERBOSE",
|
||||
@@ -1747,6 +1729,12 @@
|
||||
"group": "WM_DEBUG_RECENTS_ANIMATIONS",
|
||||
"at": "com\/android\/server\/wm\/RecentsAnimation.java"
|
||||
},
|
||||
"-302468137": {
|
||||
"message": "Display %d was already recording, so apply transformations if necessary",
|
||||
"level": "VERBOSE",
|
||||
"group": "WM_DEBUG_CONTENT_RECORDING",
|
||||
"at": "com\/android\/server\/wm\/ContentRecorder.java"
|
||||
},
|
||||
"-292790591": {
|
||||
"message": "Attempted to set IME policy to a display that does not exist: %d",
|
||||
"level": "WARN",
|
||||
@@ -1873,6 +1861,12 @@
|
||||
"group": "WM_DEBUG_STATES",
|
||||
"at": "com\/android\/server\/wm\/Task.java"
|
||||
},
|
||||
"-142844021": {
|
||||
"message": "Unable to start recording for display %d since the surface is not available.",
|
||||
"level": "VERBOSE",
|
||||
"group": "WM_DEBUG_CONTENT_RECORDING",
|
||||
"at": "com\/android\/server\/wm\/ContentRecorder.java"
|
||||
},
|
||||
"-134091882": {
|
||||
"message": "Screenshotting Activity %s",
|
||||
"level": "VERBOSE",
|
||||
@@ -2605,6 +2599,12 @@
|
||||
"group": "WM_SHOW_TRANSACTIONS",
|
||||
"at": "com\/android\/server\/wm\/Session.java"
|
||||
},
|
||||
"609880497": {
|
||||
"message": "Display %d has no content and is on, so start recording for state %d",
|
||||
"level": "VERBOSE",
|
||||
"group": "WM_DEBUG_CONTENT_RECORDING",
|
||||
"at": "com\/android\/server\/wm\/ContentRecorder.java"
|
||||
},
|
||||
"620368427": {
|
||||
"message": "******* TELLING SURFACE FLINGER WE ARE BOOTED!",
|
||||
"level": "INFO",
|
||||
@@ -3319,6 +3319,12 @@
|
||||
"group": "WM_ERROR",
|
||||
"at": "com\/android\/server\/wm\/WindowManagerService.java"
|
||||
},
|
||||
"1444064727": {
|
||||
"message": "Unexpectedly null window container; unable to update recording for display %d",
|
||||
"level": "VERBOSE",
|
||||
"group": "WM_DEBUG_CONTENT_RECORDING",
|
||||
"at": "com\/android\/server\/wm\/ContentRecorder.java"
|
||||
},
|
||||
"1448683958": {
|
||||
"message": "Override pending remote transitionSet=%b adapter=%s",
|
||||
"level": "INFO",
|
||||
@@ -3445,6 +3451,12 @@
|
||||
"group": "WM_DEBUG_APP_TRANSITIONS_ANIM",
|
||||
"at": "com\/android\/server\/wm\/AppTransition.java"
|
||||
},
|
||||
"1608402305": {
|
||||
"message": "Unable to start recording due to invalid region for display %d",
|
||||
"level": "VERBOSE",
|
||||
"group": "WM_DEBUG_CONTENT_RECORDING",
|
||||
"at": "com\/android\/server\/wm\/ContentRecorder.java"
|
||||
},
|
||||
"1610646518": {
|
||||
"message": "Enqueueing pending finish: %s",
|
||||
"level": "VERBOSE",
|
||||
@@ -3547,12 +3559,6 @@
|
||||
"group": "WM_DEBUG_WINDOW_ORGANIZER",
|
||||
"at": "com\/android\/server\/wm\/DisplayAreaOrganizerController.java"
|
||||
},
|
||||
"1707558369": {
|
||||
"message": "Unable to start recording for display %d since the surface is not available.",
|
||||
"level": "VERBOSE",
|
||||
"group": "WM_DEBUG_CONTENT_RECORDING",
|
||||
"at": "com\/android\/server\/wm\/DisplayContent.java"
|
||||
},
|
||||
"1720229827": {
|
||||
"message": "Creating animation bounds layer",
|
||||
"level": "INFO",
|
||||
@@ -3685,12 +3691,6 @@
|
||||
"group": "WM_DEBUG_STARTING_WINDOW",
|
||||
"at": "com\/android\/server\/wm\/ActivityRecord.java"
|
||||
},
|
||||
"1854279309": {
|
||||
"message": "Provided surface for recording on display %d is not present, so do not update the surface",
|
||||
"level": "VERBOSE",
|
||||
"group": "WM_DEBUG_CONTENT_RECORDING",
|
||||
"at": "com\/android\/server\/wm\/DisplayContent.java"
|
||||
},
|
||||
"1856211951": {
|
||||
"message": "moveFocusableActivityToTop: already on top, activity=%s",
|
||||
"level": "DEBUG",
|
||||
|
||||
368
services/core/java/com/android/server/wm/ContentRecorder.java
Normal file
368
services/core/java/com/android/server/wm/ContentRecorder.java
Normal file
@@ -0,0 +1,368 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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 com.android.server.wm;
|
||||
|
||||
import static android.view.ContentRecordingSession.RECORD_CONTENT_DISPLAY;
|
||||
|
||||
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_CONTENT_RECORDING;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.content.res.Configuration;
|
||||
import android.graphics.Point;
|
||||
import android.graphics.Rect;
|
||||
import android.os.IBinder;
|
||||
import android.view.ContentRecordingSession;
|
||||
import android.view.Display;
|
||||
import android.view.SurfaceControl;
|
||||
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
import com.android.internal.protolog.common.ProtoLog;
|
||||
|
||||
/**
|
||||
* Manages content recording for a particular {@link DisplayContent}.
|
||||
*/
|
||||
final class ContentRecorder {
|
||||
|
||||
/**
|
||||
* The display content this class is handling recording for.
|
||||
*/
|
||||
@NonNull
|
||||
private final DisplayContent mDisplayContent;
|
||||
|
||||
/**
|
||||
* The session for content recording, or null if this DisplayContent is not being used for
|
||||
* recording.
|
||||
*/
|
||||
@VisibleForTesting private ContentRecordingSession mContentRecordingSession = null;
|
||||
|
||||
/**
|
||||
* The WindowContainer for the level of the hierarchy to record.
|
||||
*/
|
||||
@Nullable private WindowContainer mRecordedWindowContainer = null;
|
||||
|
||||
/**
|
||||
* The surface for recording the contents of this hierarchy, or null if content recording is
|
||||
* temporarily disabled.
|
||||
*/
|
||||
@Nullable private SurfaceControl mRecordedSurface = null;
|
||||
|
||||
/**
|
||||
* The last bounds of the region to record.
|
||||
*/
|
||||
@Nullable private Rect mLastRecordedBounds = null;
|
||||
|
||||
ContentRecorder(@NonNull DisplayContent displayContent) {
|
||||
mDisplayContent = displayContent;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @param session the new session indicating recording will begin on this display.
|
||||
*/
|
||||
void setContentRecordingSession(@Nullable ContentRecordingSession session) {
|
||||
mContentRecordingSession = session;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@code true} if this DisplayContent is currently recording.
|
||||
*/
|
||||
boolean isCurrentlyRecording() {
|
||||
return mContentRecordingSession != null && mRecordedSurface != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Start recording if this DisplayContent no longer has content. Stop recording if it now
|
||||
* has content or the display is not on.
|
||||
*/
|
||||
@VisibleForTesting void updateRecording() {
|
||||
if (isCurrentlyRecording() && (mDisplayContent.getLastHasContent()
|
||||
|| mDisplayContent.getDisplay().getState() == Display.STATE_OFF)) {
|
||||
pauseRecording();
|
||||
} else {
|
||||
// Display no longer has content, or now has a surface to write to, so try to start
|
||||
// recording.
|
||||
startRecordingIfNeeded();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle a configuration change on the display content, and resize recording if needed.
|
||||
* @param lastOrientation the prior orientation of the configuration
|
||||
*/
|
||||
void onConfigurationChanged(@Configuration.Orientation int lastOrientation) {
|
||||
// Update surface for MediaProjection, if this DisplayContent is being used for recording.
|
||||
if (isCurrentlyRecording() && mLastRecordedBounds != null) {
|
||||
// Recording has already begun, but update recording since the display is now on.
|
||||
if (mRecordedWindowContainer == null) {
|
||||
ProtoLog.v(WM_DEBUG_CONTENT_RECORDING,
|
||||
"Unexpectedly null window container; unable to update recording for "
|
||||
+ "display %d",
|
||||
mDisplayContent.getDisplayId());
|
||||
return;
|
||||
}
|
||||
|
||||
ProtoLog.v(WM_DEBUG_CONTENT_RECORDING,
|
||||
"Display %d was already recording, so apply transformations if necessary",
|
||||
mDisplayContent.getDisplayId());
|
||||
// Retrieve the size of the region to record, and continue with the update
|
||||
// if the bounds or orientation has changed.
|
||||
final Rect recordedContentBounds = mRecordedWindowContainer.getBounds();
|
||||
int recordedContentOrientation = mRecordedWindowContainer.getOrientation();
|
||||
if (!mLastRecordedBounds.equals(recordedContentBounds)
|
||||
|| lastOrientation != recordedContentOrientation) {
|
||||
Point surfaceSize = fetchSurfaceSizeIfPresent();
|
||||
if (surfaceSize != null) {
|
||||
ProtoLog.v(WM_DEBUG_CONTENT_RECORDING,
|
||||
"Going ahead with updating recording for display %d to new "
|
||||
+ "bounds %s and/or orientation %d.",
|
||||
mDisplayContent.getDisplayId(), recordedContentBounds,
|
||||
recordedContentOrientation);
|
||||
updateMirroredSurface(mDisplayContent.mWmService.mTransactionFactory.get(),
|
||||
recordedContentBounds, surfaceSize);
|
||||
} else {
|
||||
// If the surface removed, do nothing. We will handle this via onDisplayChanged
|
||||
// (the display will be off if the surface is removed).
|
||||
ProtoLog.v(WM_DEBUG_CONTENT_RECORDING,
|
||||
"Unable to update recording for display %d to new bounds %s"
|
||||
+ " and/or orientation %d, since the surface is not available.",
|
||||
mDisplayContent.getDisplayId(), recordedContentBounds,
|
||||
recordedContentOrientation);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Pauses recording on this display content. Note the session does not need to be updated,
|
||||
* since recording can be resumed still.
|
||||
*/
|
||||
void pauseRecording() {
|
||||
if (mRecordedSurface == null) {
|
||||
return;
|
||||
}
|
||||
ProtoLog.v(WM_DEBUG_CONTENT_RECORDING,
|
||||
"Display %d has content (%b) so pause recording", mDisplayContent.getDisplayId(),
|
||||
mDisplayContent.getLastHasContent());
|
||||
// If the display is not on and it is a virtual display, then it no longer has an
|
||||
// associated surface to write output to.
|
||||
// If the display now has content, stop mirroring to it.
|
||||
mDisplayContent.mWmService.mTransactionFactory.get()
|
||||
// Remove the reference to mMirroredSurface, to clean up associated memory.
|
||||
.remove(mRecordedSurface)
|
||||
// Reparent the SurfaceControl of this DisplayContent back to mSurfaceControl,
|
||||
// to allow content to be added to it. This allows this DisplayContent to stop
|
||||
// mirroring and show content normally.
|
||||
.reparent(mDisplayContent.getWindowingLayer(), mDisplayContent.getSurfaceControl())
|
||||
.reparent(mDisplayContent.getOverlayLayer(), mDisplayContent.getSurfaceControl())
|
||||
.apply();
|
||||
// Pause mirroring by destroying the reference to the mirrored layer.
|
||||
mRecordedSurface = null;
|
||||
// Do not un-set the token, in case content is removed and recording should begin again.
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops recording on this DisplayContent, and updates the session details.
|
||||
*/
|
||||
void remove() {
|
||||
if (mRecordedSurface != null) {
|
||||
// Do not wait for the mirrored surface to be garbage collected, but clean up
|
||||
// immediately.
|
||||
mDisplayContent.mWmService.mTransactionFactory.get().remove(mRecordedSurface).apply();
|
||||
mRecordedSurface = null;
|
||||
clearContentRecordingSession();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes both the local cache and WM Service view of the current session, to stop the session
|
||||
* on this display.
|
||||
*/
|
||||
private void clearContentRecordingSession() {
|
||||
// Update the cached session state first, since updating the service will result in always
|
||||
// returning to this instance to update recording state.
|
||||
mContentRecordingSession = null;
|
||||
mDisplayContent.mWmService.setContentRecordingSession(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Start recording to this DisplayContent if it does not have its own content. Captures the
|
||||
* content of a WindowContainer indicated by a WindowToken. If unable to start recording, falls
|
||||
* back to original MediaProjection approach.
|
||||
*/
|
||||
private void startRecordingIfNeeded() {
|
||||
// Only record if this display does not have its own content, is not recording already,
|
||||
// and if this display is on (it has a surface to write output to).
|
||||
if (mDisplayContent.getLastHasContent() || isCurrentlyRecording()
|
||||
|| mDisplayContent.getDisplay().getState() == Display.STATE_OFF
|
||||
|| mContentRecordingSession == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final int contentToRecord = mContentRecordingSession.getContentToRecord();
|
||||
if (contentToRecord != RECORD_CONTENT_DISPLAY) {
|
||||
// TODO(b/216625226) handle task-based recording
|
||||
// Not a valid region, or recording is disabled, so fall back to prior MediaProjection
|
||||
// approach.
|
||||
clearContentRecordingSession();
|
||||
ProtoLog.v(WM_DEBUG_CONTENT_RECORDING,
|
||||
"Unable to start recording due to invalid region for display %d",
|
||||
mDisplayContent.getDisplayId());
|
||||
return;
|
||||
}
|
||||
// Given the WindowToken of the DisplayArea to record, retrieve the associated
|
||||
// SurfaceControl.
|
||||
IBinder tokenToRecord = mContentRecordingSession.getTokenToRecord();
|
||||
if (tokenToRecord == null) {
|
||||
// Unexpectedly missing token. Fall back to prior MediaProjection approach.
|
||||
clearContentRecordingSession();
|
||||
ProtoLog.v(WM_DEBUG_CONTENT_RECORDING,
|
||||
"Unable to start recording due to null token for display %d",
|
||||
mDisplayContent.getDisplayId());
|
||||
return;
|
||||
}
|
||||
|
||||
final WindowContainer wc =
|
||||
mDisplayContent.mWmService.mWindowContextListenerController.getContainer(
|
||||
tokenToRecord);
|
||||
if (wc == null) {
|
||||
// Un-set the window token to record for this VirtualDisplay. Fall back to the
|
||||
// original MediaProjection approach.
|
||||
mDisplayContent.mWmService.mDisplayManagerInternal.setWindowManagerMirroring(
|
||||
mDisplayContent.getDisplayId(), false);
|
||||
clearContentRecordingSession();
|
||||
ProtoLog.v(WM_DEBUG_CONTENT_RECORDING,
|
||||
"Unable to retrieve window container to start recording for "
|
||||
+ "display %d",
|
||||
mDisplayContent.getDisplayId());
|
||||
return;
|
||||
}
|
||||
// TODO(206461622) Migrate to using the RootDisplayArea
|
||||
mRecordedWindowContainer = wc.getDisplayContent();
|
||||
|
||||
final Point surfaceSize = fetchSurfaceSizeIfPresent();
|
||||
if (surfaceSize == null) {
|
||||
ProtoLog.v(WM_DEBUG_CONTENT_RECORDING,
|
||||
"Unable to start recording for display %d since the surface is not "
|
||||
+ "available.",
|
||||
mDisplayContent.getDisplayId());
|
||||
return;
|
||||
}
|
||||
ProtoLog.v(WM_DEBUG_CONTENT_RECORDING,
|
||||
"Display %d has no content and is on, so start recording for state %d",
|
||||
mDisplayContent.getDisplayId(), mDisplayContent.getDisplay().getState());
|
||||
|
||||
// Create a mirrored hierarchy for the SurfaceControl of the DisplayArea to capture.
|
||||
mRecordedSurface = SurfaceControl.mirrorSurface(
|
||||
mRecordedWindowContainer.getSurfaceControl());
|
||||
SurfaceControl.Transaction transaction =
|
||||
mDisplayContent.mWmService.mTransactionFactory.get()
|
||||
// Set the mMirroredSurface's parent to the root SurfaceControl for this
|
||||
// DisplayContent. This brings the new mirrored hierarchy under this
|
||||
// DisplayContent,
|
||||
// so SurfaceControl will write the layers of this hierarchy to the
|
||||
// output surface
|
||||
// provided by the app.
|
||||
.reparent(mRecordedSurface, mDisplayContent.getSurfaceControl())
|
||||
// Reparent the SurfaceControl of this DisplayContent to null, to prevent
|
||||
// content
|
||||
// being added to it. This ensures that no app launched explicitly on the
|
||||
// VirtualDisplay will show up as part of the mirrored content.
|
||||
.reparent(mDisplayContent.getWindowingLayer(), null)
|
||||
.reparent(mDisplayContent.getOverlayLayer(), null);
|
||||
// Retrieve the size of the DisplayArea to mirror.
|
||||
updateMirroredSurface(transaction, mRecordedWindowContainer.getBounds(), surfaceSize);
|
||||
|
||||
// No need to clean up. In SurfaceFlinger, parents hold references to their children. The
|
||||
// mirrored SurfaceControl is alive since the parent DisplayContent SurfaceControl is
|
||||
// holding a reference to it. Therefore, the mirrored SurfaceControl will be cleaned up
|
||||
// when the VirtualDisplay is destroyed - which will clean up this DisplayContent.
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply transformations to the mirrored surface to ensure the captured contents are scaled to
|
||||
* fit and centred in the output surface.
|
||||
*
|
||||
* @param transaction the transaction to include transformations of mMirroredSurface
|
||||
* to. Transaction is not applied before returning.
|
||||
* @param recordedContentBounds bounds of the content to record to the surface provided by
|
||||
* the app.
|
||||
* @param surfaceSize the default size of the surface to write the display area
|
||||
* content to
|
||||
*/
|
||||
@VisibleForTesting void updateMirroredSurface(SurfaceControl.Transaction transaction,
|
||||
Rect recordedContentBounds, Point surfaceSize) {
|
||||
// Calculate the scale to apply to the root mirror SurfaceControl to fit the size of the
|
||||
// output surface.
|
||||
float scaleX = surfaceSize.x / (float) recordedContentBounds.width();
|
||||
float scaleY = surfaceSize.y / (float) recordedContentBounds.height();
|
||||
float scale = Math.min(scaleX, scaleY);
|
||||
int scaledWidth = Math.round(scale * (float) recordedContentBounds.width());
|
||||
int scaledHeight = Math.round(scale * (float) recordedContentBounds.height());
|
||||
|
||||
// Calculate the shift to apply to the root mirror SurfaceControl to centre the mirrored
|
||||
// contents in the output surface.
|
||||
int shiftedX = 0;
|
||||
if (scaledWidth != surfaceSize.x) {
|
||||
shiftedX = (surfaceSize.x - scaledWidth) / 2;
|
||||
}
|
||||
int shiftedY = 0;
|
||||
if (scaledHeight != surfaceSize.y) {
|
||||
shiftedY = (surfaceSize.y - scaledHeight) / 2;
|
||||
}
|
||||
|
||||
transaction
|
||||
// Crop the area to capture to exclude the 'extra' wallpaper that is used
|
||||
// for parallax (b/189930234).
|
||||
.setWindowCrop(mRecordedSurface, recordedContentBounds.width(),
|
||||
recordedContentBounds.height())
|
||||
// Scale the root mirror SurfaceControl, based upon the size difference between the
|
||||
// source (DisplayArea to capture) and output (surface the app reads images from).
|
||||
.setMatrix(mRecordedSurface, scale, 0 /* dtdx */, 0 /* dtdy */, scale)
|
||||
// Position needs to be updated when the mirrored DisplayArea has changed, since
|
||||
// the content will no longer be centered in the output surface.
|
||||
.setPosition(mRecordedSurface, shiftedX /* x */, shiftedY /* y */)
|
||||
.apply();
|
||||
mLastRecordedBounds = new Rect(recordedContentBounds);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a non-null {@link Point} if the surface is present, or null otherwise
|
||||
*/
|
||||
private Point fetchSurfaceSizeIfPresent() {
|
||||
// Retrieve the default size of the surface the app provided to
|
||||
// MediaProjection#createVirtualDisplay. Note the app is the consumer of the surface,
|
||||
// since it reads out buffers from the surface, and SurfaceFlinger is the producer since
|
||||
// it writes the mirrored layers to the buffers.
|
||||
Point surfaceSize =
|
||||
mDisplayContent.mWmService.mDisplayManagerInternal.getDisplaySurfaceDefaultSize(
|
||||
mDisplayContent.getDisplayId());
|
||||
if (surfaceSize == null) {
|
||||
// Layer mirroring started with a null surface, so do not apply any transformations yet.
|
||||
// State of virtual display will change to 'ON' when the surface is set.
|
||||
// will get event DISPLAY_DEVICE_EVENT_CHANGED
|
||||
ProtoLog.v(WM_DEBUG_CONTENT_RECORDING,
|
||||
"Provided surface for recording on display %d is not present, so do not"
|
||||
+ " update the surface",
|
||||
mDisplayContent.getDisplayId());
|
||||
return null;
|
||||
}
|
||||
return surfaceSize;
|
||||
}
|
||||
}
|
||||
@@ -37,7 +37,6 @@ import static android.os.Process.SYSTEM_UID;
|
||||
import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
|
||||
import static android.util.DisplayMetrics.DENSITY_DEFAULT;
|
||||
import static android.util.RotationUtils.deltaRotation;
|
||||
import static android.view.ContentRecordingSession.RECORD_CONTENT_DISPLAY;
|
||||
import static android.view.Display.DEFAULT_DISPLAY;
|
||||
import static android.view.Display.FLAG_CAN_SHOW_WITH_INSECURE_KEYGUARD;
|
||||
import static android.view.Display.FLAG_PRIVATE;
|
||||
@@ -306,28 +305,12 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
|
||||
*/
|
||||
private SurfaceControl mWindowingLayer;
|
||||
|
||||
// TODO(216756854) move all recording fields to the controller
|
||||
/**
|
||||
* The session for content recording, or null if this DisplayContent is not being used for
|
||||
* recording.
|
||||
* Delegate for handling all logic around content recording; decides if this DisplayContent is
|
||||
* recording, and if so, applies necessary updates to SurfaceFlinger.
|
||||
*/
|
||||
@VisibleForTesting private ContentRecordingSession mContentRecordingSession = null;
|
||||
|
||||
/**
|
||||
* The WindowContainer for the level of the hierarchy to record.
|
||||
*/
|
||||
@Nullable private DisplayContent mRecordedWindowContainer = null;
|
||||
|
||||
/**
|
||||
* The surface for recording the contents of this hierarchy, or null if content recording is
|
||||
* temporarily disabled.
|
||||
*/
|
||||
@Nullable private SurfaceControl mRecordedSurface = null;
|
||||
|
||||
/**
|
||||
* The last bounds of the region to record.
|
||||
*/
|
||||
@Nullable private Rect mLastRecordedBounds = null;
|
||||
@Nullable
|
||||
private ContentRecorder mContentRecorder;
|
||||
|
||||
/**
|
||||
* The default per Display minimal size of tasks. Calculated at construction.
|
||||
@@ -2544,42 +2527,8 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
|
||||
updateImeParent();
|
||||
|
||||
// Update surface for MediaProjection, if this DisplayContent is being used for recording.
|
||||
if (isCurrentlyRecording() && mLastRecordedBounds != null) {
|
||||
// Recording has already begun, but update recording since the display is now on.
|
||||
if (mRecordedWindowContainer == null) {
|
||||
ProtoLog.v(WM_DEBUG_CONTENT_RECORDING,
|
||||
"Unexpectedly null window container; unable to update recording for "
|
||||
+ "display %d",
|
||||
mDisplayId);
|
||||
return;
|
||||
}
|
||||
|
||||
ProtoLog.v(WM_DEBUG_CONTENT_RECORDING,
|
||||
"Display %d was already recording, so apply transformations if necessary",
|
||||
mDisplayId);
|
||||
// Retrieve the size of the region to record, and continue with the update
|
||||
// if the bounds or orientation has changed.
|
||||
final Rect recordedContentBounds = mRecordedWindowContainer.getBounds();
|
||||
int recordedContentOrientation = mRecordedWindowContainer.getOrientation();
|
||||
if (!mLastRecordedBounds.equals(recordedContentBounds)
|
||||
|| lastOrientation != recordedContentOrientation) {
|
||||
Point surfaceSize = fetchSurfaceSizeIfPresent();
|
||||
if (surfaceSize != null) {
|
||||
ProtoLog.v(WM_DEBUG_CONTENT_RECORDING,
|
||||
"Going ahead with updating recording for display %d to new "
|
||||
+ "bounds %s and/or orientation %d.",
|
||||
mDisplayId, recordedContentBounds, recordedContentOrientation);
|
||||
updateMirroredSurface(mWmService.mTransactionFactory.get(),
|
||||
recordedContentBounds, surfaceSize);
|
||||
} else {
|
||||
// If the surface removed, do nothing. We will handle this via onDisplayChanged
|
||||
// (the display will be off if the surface is removed).
|
||||
ProtoLog.v(WM_DEBUG_CONTENT_RECORDING,
|
||||
"Unable to update recording for display %d to new bounds %s"
|
||||
+ " and/or orientation %d, since the surface is not available.",
|
||||
mDisplayId, recordedContentBounds, recordedContentOrientation);
|
||||
}
|
||||
}
|
||||
if (mContentRecorder != null) {
|
||||
mContentRecorder.onConfigurationChanged(lastOrientation);
|
||||
}
|
||||
|
||||
if (lastOrientation != getConfiguration().orientation) {
|
||||
@@ -5872,12 +5821,8 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
|
||||
}
|
||||
mRemoved = true;
|
||||
|
||||
if (mRecordedSurface != null) {
|
||||
// Do not wait for the mirrored surface to be garbage collected, but clean up
|
||||
// immediately.
|
||||
mWmService.mTransactionFactory.get().remove(mRecordedSurface).apply();
|
||||
mRecordedSurface = null;
|
||||
clearContentRecordingSession();
|
||||
if (mContentRecorder != null) {
|
||||
mContentRecorder.remove();
|
||||
}
|
||||
|
||||
// Only update focus/visibility for the last one because there may be many root tasks are
|
||||
@@ -6117,137 +6062,30 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
|
||||
return mSandboxDisplayApis;
|
||||
}
|
||||
|
||||
/**
|
||||
* Start recording to this DisplayContent if it does not have its own content. Captures the
|
||||
* content of a WindowContainer indicated by a WindowToken. If unable to start recording, falls
|
||||
* back to original MediaProjection approach.
|
||||
*/
|
||||
private void startRecordingIfNeeded() {
|
||||
// Only record if this display does not have its own content, is not recording already,
|
||||
// and if this display is on (it has a surface to write output to).
|
||||
if (mLastHasContent || isCurrentlyRecording() || mDisplay.getState() == Display.STATE_OFF
|
||||
|| mContentRecordingSession == null) {
|
||||
return;
|
||||
private ContentRecorder getContentRecorder() {
|
||||
if (mContentRecorder == null) {
|
||||
mContentRecorder = new ContentRecorder(this);
|
||||
}
|
||||
|
||||
final int contentToRecord = mContentRecordingSession.getContentToRecord();
|
||||
if (contentToRecord != RECORD_CONTENT_DISPLAY) {
|
||||
// TODO(b/216625226) handle task-based recording
|
||||
// Not a valid region, or recording is disabled, so fall back to prior MediaProjection
|
||||
// approach.
|
||||
clearContentRecordingSession();
|
||||
ProtoLog.v(WM_DEBUG_CONTENT_RECORDING,
|
||||
"Unable to start recording due to invalid region for display %d",
|
||||
mDisplayId);
|
||||
return;
|
||||
}
|
||||
// Given the WindowToken of the DisplayArea to record, retrieve the associated
|
||||
// SurfaceControl.
|
||||
IBinder tokenToRecord = mContentRecordingSession.getTokenToRecord();
|
||||
if (tokenToRecord == null) {
|
||||
// Unexpectedly missing token. Fall back to prior MediaProjection approach.
|
||||
clearContentRecordingSession();
|
||||
ProtoLog.v(WM_DEBUG_CONTENT_RECORDING,
|
||||
"Unable to start recording due to null token for display %d", mDisplayId);
|
||||
return;
|
||||
}
|
||||
|
||||
final WindowContainer wc = mWmService.mWindowContextListenerController.getContainer(
|
||||
tokenToRecord);
|
||||
if (wc == null) {
|
||||
// Un-set the window token to record for this VirtualDisplay. Fall back to the
|
||||
// original MediaProjection approach.
|
||||
mWmService.mDisplayManagerInternal.setWindowManagerMirroring(mDisplayId, false);
|
||||
clearContentRecordingSession();
|
||||
ProtoLog.v(WM_DEBUG_CONTENT_RECORDING,
|
||||
"Unable to retrieve window container to start recording for "
|
||||
+ "display %d",
|
||||
mDisplayId);
|
||||
return;
|
||||
}
|
||||
// TODO(206461622) Migrate to the RootDisplayArea
|
||||
mRecordedWindowContainer = wc.getDisplayContent();
|
||||
|
||||
final Point surfaceSize = fetchSurfaceSizeIfPresent();
|
||||
if (surfaceSize == null) {
|
||||
ProtoLog.v(WM_DEBUG_CONTENT_RECORDING,
|
||||
"Unable to start recording for display %d since the surface is not "
|
||||
+ "available.",
|
||||
mDisplayId);
|
||||
return;
|
||||
}
|
||||
ProtoLog.v(WM_DEBUG_CONTENT_RECORDING,
|
||||
"Display %d has no content and is on, so start recording for state %d",
|
||||
mDisplayId, mDisplay.getState());
|
||||
|
||||
// Create a mirrored hierarchy for the SurfaceControl of the DisplayArea to capture.
|
||||
mRecordedSurface = SurfaceControl.mirrorSurface(
|
||||
mRecordedWindowContainer.getSurfaceControl());
|
||||
SurfaceControl.Transaction transaction = mWmService.mTransactionFactory.get()
|
||||
// Set the mMirroredSurface's parent to the root SurfaceControl for this
|
||||
// DisplayContent. This brings the new mirrored hierarchy under this DisplayContent,
|
||||
// so SurfaceControl will write the layers of this hierarchy to the output surface
|
||||
// provided by the app.
|
||||
.reparent(mRecordedSurface, mSurfaceControl)
|
||||
// Reparent the SurfaceControl of this DisplayContent to null, to prevent content
|
||||
// being added to it. This ensures that no app launched explicitly on the
|
||||
// VirtualDisplay will show up as part of the mirrored content.
|
||||
.reparent(mWindowingLayer, null)
|
||||
.reparent(mOverlayLayer, null);
|
||||
// Retrieve the size of the DisplayArea to mirror.
|
||||
updateMirroredSurface(transaction, mRecordedWindowContainer.getBounds(), surfaceSize);
|
||||
|
||||
// No need to clean up. In SurfaceFlinger, parents hold references to their children. The
|
||||
// mirrored SurfaceControl is alive since the parent DisplayContent SurfaceControl is
|
||||
// holding a reference to it. Therefore, the mirrored SurfaceControl will be cleaned up
|
||||
// when the VirtualDisplay is destroyed - which will clean up this DisplayContent.
|
||||
return mContentRecorder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pause the recording session.
|
||||
*/
|
||||
public void pauseRecording() {
|
||||
if (mRecordedSurface == null) {
|
||||
return;
|
||||
@VisibleForTesting void pauseRecording() {
|
||||
if (mContentRecorder != null) {
|
||||
mContentRecorder.pauseRecording();
|
||||
}
|
||||
ProtoLog.v(WM_DEBUG_CONTENT_RECORDING,
|
||||
"Display %d has content (%b) so pause recording", mDisplayId,
|
||||
mLastHasContent);
|
||||
// If the display is not on and it is a virtual display, then it no longer has an
|
||||
// associated surface to write output to.
|
||||
// If the display now has content, stop mirroring to it.
|
||||
mWmService.mTransactionFactory.get()
|
||||
// Remove the reference to mMirroredSurface, to clean up associated memory.
|
||||
.remove(mRecordedSurface)
|
||||
// Reparent the SurfaceControl of this DisplayContent back to mSurfaceControl,
|
||||
// to allow content to be added to it. This allows this DisplayContent to stop
|
||||
// mirroring and show content normally.
|
||||
.reparent(mWindowingLayer, mSurfaceControl)
|
||||
.reparent(mOverlayLayer, mSurfaceControl)
|
||||
.apply();
|
||||
// Pause mirroring by destroying the reference to the mirrored layer.
|
||||
mRecordedSurface = null;
|
||||
// Do not un-set the token, in case content is removed and recording should begin again.
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @param session the new session indicating recording will begin on this display.
|
||||
*/
|
||||
public void setContentRecordingSession(@Nullable ContentRecordingSession session) {
|
||||
mContentRecordingSession = session;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes both the local cache and WM Service view of the current session, to stop the session
|
||||
* on this display.
|
||||
*/
|
||||
private void clearContentRecordingSession() {
|
||||
// Update the cached session state first, since updating the service will result in always
|
||||
// returning to this instance to update recording state.
|
||||
mContentRecordingSession = null;
|
||||
mWmService.setContentRecordingSession(null);
|
||||
void setContentRecordingSession(@Nullable ContentRecordingSession session) {
|
||||
getContentRecorder().setContentRecordingSession(session);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -6255,97 +6093,14 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
|
||||
* has content or the display is not on.
|
||||
*/
|
||||
@VisibleForTesting void updateRecording() {
|
||||
if (isCurrentlyRecording() && (mLastHasContent
|
||||
|| mDisplay.getState() == Display.STATE_OFF)) {
|
||||
pauseRecording();
|
||||
} else {
|
||||
// Display no longer has content, or now has a surface to write to, so try to start
|
||||
// recording.
|
||||
startRecordingIfNeeded();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply transformations to the mirrored surface to ensure the captured contents are scaled to
|
||||
* fit and centred in the output surface.
|
||||
*
|
||||
* @param transaction the transaction to include transformations of mMirroredSurface
|
||||
* to. Transaction is not applied before returning.
|
||||
* @param recordedContentBounds bounds of the content to record to the surface provided by
|
||||
* the app.
|
||||
* @param surfaceSize the default size of the surface to write the display area
|
||||
* content to
|
||||
*/
|
||||
@VisibleForTesting
|
||||
void updateMirroredSurface(SurfaceControl.Transaction transaction,
|
||||
Rect recordedContentBounds, Point surfaceSize) {
|
||||
// Calculate the scale to apply to the root mirror SurfaceControl to fit the size of the
|
||||
// output surface.
|
||||
float scaleX = surfaceSize.x / (float) recordedContentBounds.width();
|
||||
float scaleY = surfaceSize.y / (float) recordedContentBounds.height();
|
||||
float scale = Math.min(scaleX, scaleY);
|
||||
int scaledWidth = Math.round(scale * (float) recordedContentBounds.width());
|
||||
int scaledHeight = Math.round(scale * (float) recordedContentBounds.height());
|
||||
|
||||
// Calculate the shift to apply to the root mirror SurfaceControl to centre the mirrored
|
||||
// contents in the output surface.
|
||||
int shiftedX = 0;
|
||||
if (scaledWidth != surfaceSize.x) {
|
||||
shiftedX = (surfaceSize.x - scaledWidth) / 2;
|
||||
}
|
||||
int shiftedY = 0;
|
||||
if (scaledHeight != surfaceSize.y) {
|
||||
shiftedY = (surfaceSize.y - scaledHeight) / 2;
|
||||
}
|
||||
|
||||
transaction
|
||||
// Crop the area to capture to exclude the 'extra' wallpaper that is used
|
||||
// for parallax (b/189930234).
|
||||
.setWindowCrop(mRecordedSurface, recordedContentBounds.width(),
|
||||
recordedContentBounds.height())
|
||||
// Scale the root mirror SurfaceControl, based upon the size difference between the
|
||||
// source (DisplayArea to capture) and output (surface the app reads images from).
|
||||
.setMatrix(mRecordedSurface, scale, 0 /* dtdx */, 0 /* dtdy */, scale)
|
||||
// Position needs to be updated when the mirrored DisplayArea has changed, since
|
||||
// the content will no longer be centered in the output surface.
|
||||
.setPosition(mRecordedSurface, shiftedX /* x */, shiftedY /* y */)
|
||||
.apply();
|
||||
mLastRecordedBounds = new Rect(recordedContentBounds);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a non-null {@link Point} if the surface is present, or null otherwise
|
||||
*/
|
||||
Point fetchSurfaceSizeIfPresent() {
|
||||
// Retrieve the default size of the surface the app provided to
|
||||
// MediaProjection#createVirtualDisplay. Note the app is the consumer of the surface,
|
||||
// since it reads out buffers from the surface, and SurfaceFlinger is the producer since
|
||||
// it writes the mirrored layers to the buffers.
|
||||
Point surfaceSize = mWmService.mDisplayManagerInternal.getDisplaySurfaceDefaultSize(
|
||||
mDisplayId);
|
||||
if (surfaceSize == null) {
|
||||
// Layer mirroring started with a null surface, so do not apply any transformations yet.
|
||||
// State of virtual display will change to 'ON' when the surface is set.
|
||||
// will get event DISPLAY_DEVICE_EVENT_CHANGED
|
||||
ProtoLog.v(WM_DEBUG_CONTENT_RECORDING,
|
||||
"Provided surface for recording on display %d is not present, so do not"
|
||||
+ " update the surface",
|
||||
mDisplayId);
|
||||
return null;
|
||||
}
|
||||
return surfaceSize;
|
||||
getContentRecorder().updateRecording();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@code true} if this DisplayContent is currently recording.
|
||||
*/
|
||||
boolean isCurrentlyRecording() {
|
||||
return mContentRecordingSession != null && mRecordedSurface != null;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
@Nullable ContentRecordingSession getContentRecordingSession() {
|
||||
return mContentRecordingSession;
|
||||
return mContentRecorder != null && mContentRecorder.isCurrentlyRecording();
|
||||
}
|
||||
|
||||
/** The entry for proceeding to handle {@link #mFixedRotationLaunchingApp}. */
|
||||
|
||||
@@ -0,0 +1,239 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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 com.android.server.wm;
|
||||
|
||||
|
||||
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
|
||||
import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR;
|
||||
import static android.view.ContentRecordingSession.RECORD_CONTENT_TASK;
|
||||
|
||||
import static com.android.dx.mockito.inline.extended.ExtendedMockito.any;
|
||||
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
|
||||
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
|
||||
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
|
||||
|
||||
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.atLeastOnce;
|
||||
import static org.mockito.Mockito.never;
|
||||
|
||||
import android.graphics.Point;
|
||||
import android.graphics.Rect;
|
||||
import android.hardware.display.VirtualDisplay;
|
||||
import android.os.Binder;
|
||||
import android.os.IBinder;
|
||||
import android.platform.test.annotations.Presubmit;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.view.ContentRecordingSession;
|
||||
import android.view.Surface;
|
||||
import android.view.SurfaceControl;
|
||||
|
||||
import androidx.test.filters.SmallTest;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
/**
|
||||
* Tests for the {@link ContentRecorder} class.
|
||||
*
|
||||
* Build/Install/Run:
|
||||
* atest WmTests:ContentRecorderTests
|
||||
*/
|
||||
@SmallTest
|
||||
@Presubmit
|
||||
@RunWith(WindowTestRunner.class)
|
||||
public class ContentRecorderTests extends WindowTestsBase {
|
||||
private static final IBinder TEST_TOKEN = new RecordingTestToken();
|
||||
private final ContentRecordingSession mDefaultSession =
|
||||
ContentRecordingSession.createDisplaySession(TEST_TOKEN);
|
||||
private static Point sSurfaceSize;
|
||||
private ContentRecorder mContentRecorder;
|
||||
private SurfaceControl mRecordedSurface;
|
||||
|
||||
@Before public void setUp() {
|
||||
// GIVEN MediaProjection has already initialized the WindowToken of the DisplayArea to
|
||||
// mirror.
|
||||
setUpDefaultTaskDisplayAreaWindowToken();
|
||||
|
||||
// GIVEN SurfaceControl can successfully mirror the provided surface.
|
||||
sSurfaceSize = new Point(
|
||||
mDefaultDisplay.getDefaultTaskDisplayArea().getBounds().width(),
|
||||
mDefaultDisplay.getDefaultTaskDisplayArea().getBounds().height());
|
||||
mRecordedSurface = surfaceControlMirrors(sSurfaceSize);
|
||||
|
||||
// GIVEN the VirtualDisplay associated with the session (so the display has state ON).
|
||||
VirtualDisplay virtualDisplay = mWm.mDisplayManager.createVirtualDisplay("VirtualDisplay",
|
||||
sSurfaceSize.x, sSurfaceSize.y,
|
||||
DisplayMetrics.DENSITY_140, new Surface(), VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR);
|
||||
final int displayId = virtualDisplay.getDisplay().getDisplayId();
|
||||
mDefaultSession.setDisplayId(displayId);
|
||||
|
||||
mWm.mRoot.onDisplayAdded(displayId);
|
||||
final DisplayContent mVirtualDisplayContent = mWm.mRoot.getDisplayContent(displayId);
|
||||
mContentRecorder = new ContentRecorder(mVirtualDisplayContent);
|
||||
spyOn(mVirtualDisplayContent);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsCurrentlyRecording() {
|
||||
assertThat(mContentRecorder.isCurrentlyRecording()).isFalse();
|
||||
|
||||
mContentRecorder.updateRecording();
|
||||
assertThat(mContentRecorder.isCurrentlyRecording()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateRecording_display() {
|
||||
mContentRecorder.setContentRecordingSession(mDefaultSession);
|
||||
mContentRecorder.updateRecording();
|
||||
assertThat(mContentRecorder.isCurrentlyRecording()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateRecording_task() {
|
||||
mDefaultSession.setContentToRecord(RECORD_CONTENT_TASK);
|
||||
mContentRecorder.setContentRecordingSession(mDefaultSession);
|
||||
mContentRecorder.updateRecording();
|
||||
assertThat(mContentRecorder.isCurrentlyRecording()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateRecording_wasPaused() {
|
||||
mContentRecorder.setContentRecordingSession(mDefaultSession);
|
||||
mContentRecorder.updateRecording();
|
||||
|
||||
mContentRecorder.pauseRecording();
|
||||
mContentRecorder.updateRecording();
|
||||
assertThat(mContentRecorder.isCurrentlyRecording()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateRecording_wasStopped() {
|
||||
mContentRecorder.setContentRecordingSession(mDefaultSession);
|
||||
mContentRecorder.updateRecording();
|
||||
|
||||
mContentRecorder.remove();
|
||||
mContentRecorder.updateRecording();
|
||||
assertThat(mContentRecorder.isCurrentlyRecording()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnConfigurationChanged_neverRecording() {
|
||||
mContentRecorder.onConfigurationChanged(ORIENTATION_PORTRAIT);
|
||||
|
||||
verify(mTransaction, never()).setPosition(eq(mRecordedSurface), anyFloat(), anyFloat());
|
||||
verify(mTransaction, never()).setMatrix(eq(mRecordedSurface), anyFloat(), anyFloat(),
|
||||
anyFloat(), anyFloat());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnConfigurationChanged_resizesSurface() {
|
||||
mContentRecorder.setContentRecordingSession(mDefaultSession);
|
||||
mContentRecorder.updateRecording();
|
||||
mContentRecorder.onConfigurationChanged(ORIENTATION_PORTRAIT);
|
||||
|
||||
verify(mTransaction, atLeastOnce()).setPosition(eq(mRecordedSurface), anyFloat(),
|
||||
anyFloat());
|
||||
verify(mTransaction, atLeastOnce()).setMatrix(eq(mRecordedSurface), anyFloat(), anyFloat(),
|
||||
anyFloat(), anyFloat());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPauseRecording_pausesRecording() {
|
||||
mContentRecorder.setContentRecordingSession(mDefaultSession);
|
||||
mContentRecorder.updateRecording();
|
||||
|
||||
mContentRecorder.pauseRecording();
|
||||
assertThat(mContentRecorder.isCurrentlyRecording()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPauseRecording_neverRecording() {
|
||||
mContentRecorder.pauseRecording();
|
||||
assertThat(mContentRecorder.isCurrentlyRecording()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRemove_stopsRecording() {
|
||||
mContentRecorder.setContentRecordingSession(mDefaultSession);
|
||||
mContentRecorder.updateRecording();
|
||||
|
||||
mContentRecorder.remove();
|
||||
assertThat(mContentRecorder.isCurrentlyRecording()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRemove_neverRecording() {
|
||||
mContentRecorder.remove();
|
||||
assertThat(mContentRecorder.isCurrentlyRecording()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateMirroredSurface_capturedAreaResized() {
|
||||
mContentRecorder.setContentRecordingSession(mDefaultSession);
|
||||
mContentRecorder.updateRecording();
|
||||
|
||||
// WHEN attempting to mirror on the virtual display, and the captured content is resized.
|
||||
float xScale = 0.7f;
|
||||
float yScale = 2f;
|
||||
Rect displayAreaBounds = new Rect(0, 0, Math.round(sSurfaceSize.x * xScale),
|
||||
Math.round(sSurfaceSize.y * yScale));
|
||||
mContentRecorder.updateMirroredSurface(mTransaction, displayAreaBounds, sSurfaceSize);
|
||||
|
||||
// THEN content in the captured DisplayArea is scaled to fit the surface size.
|
||||
verify(mTransaction, atLeastOnce()).setMatrix(mRecordedSurface, 1.0f / yScale, 0, 0,
|
||||
1.0f / yScale);
|
||||
// THEN captured content is positioned in the centre of the output surface.
|
||||
float scaledWidth = displayAreaBounds.width() / xScale;
|
||||
float xInset = (sSurfaceSize.x - scaledWidth) / 2;
|
||||
verify(mTransaction, atLeastOnce()).setPosition(mRecordedSurface, xInset, 0);
|
||||
}
|
||||
|
||||
private static class RecordingTestToken extends Binder {
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a WindowToken associated with the default task DisplayArea, in order for that
|
||||
* DisplayArea to be mirrored.
|
||||
*/
|
||||
private void setUpDefaultTaskDisplayAreaWindowToken() {
|
||||
// GIVEN the default task display area is represented by the WindowToken.
|
||||
spyOn(mWm.mWindowContextListenerController);
|
||||
doReturn(mDefaultDisplay.getDefaultTaskDisplayArea()).when(
|
||||
mWm.mWindowContextListenerController).getContainer(any());
|
||||
}
|
||||
|
||||
/**
|
||||
* SurfaceControl successfully creates a mirrored surface of the given size.
|
||||
*/
|
||||
private SurfaceControl surfaceControlMirrors(Point surfaceSize) {
|
||||
// Do not set the parent, since the mirrored surface is the root of a new surface hierarchy.
|
||||
SurfaceControl mirroredSurface = new SurfaceControl.Builder()
|
||||
.setName("mirroredSurface")
|
||||
.setBufferSize(surfaceSize.x, surfaceSize.y)
|
||||
.setCallsite("mirrorSurface")
|
||||
.build();
|
||||
doReturn(mirroredSurface).when(() -> SurfaceControl.mirrorSurface(any()));
|
||||
doReturn(surfaceSize).when(mWm.mDisplayManagerInternal).getDisplaySurfaceDefaultSize(
|
||||
anyInt());
|
||||
return mirroredSurface;
|
||||
}
|
||||
}
|
||||
@@ -68,7 +68,6 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyBoolean;
|
||||
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
|
||||
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
|
||||
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
|
||||
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
|
||||
import static com.android.dx.mockito.inline.extended.ExtendedMockito.never;
|
||||
import static com.android.dx.mockito.inline.extended.ExtendedMockito.reset;
|
||||
import static com.android.dx.mockito.inline.extended.ExtendedMockito.same;
|
||||
@@ -97,7 +96,6 @@ import static org.junit.Assert.assertTrue;
|
||||
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.clearInvocations;
|
||||
import static org.mockito.Mockito.doAnswer;
|
||||
import static org.mockito.Mockito.doCallRealMethod;
|
||||
@@ -150,8 +148,6 @@ import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Mockito;
|
||||
import org.mockito.MockitoSession;
|
||||
import org.mockito.quality.Strictness;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
@@ -2280,99 +2276,8 @@ public class DisplayContentTests extends WindowTestsBase {
|
||||
assertNotEquals(imeMenuDialog, mDisplayContent.findFocusedWindow());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testVirtualDisplayContent() {
|
||||
MockitoSession mockSession = mockitoSession()
|
||||
.initMocks(this)
|
||||
.spyStatic(SurfaceControl.class)
|
||||
.strictness(Strictness.LENIENT)
|
||||
.startMocking();
|
||||
|
||||
// GIVEN MediaProjection has already initialized the WindowToken of the DisplayArea to
|
||||
// mirror.
|
||||
final IBinder tokenToMirror = setUpDefaultTaskDisplayAreaWindowToken();
|
||||
|
||||
// GIVEN SurfaceControl can successfully mirror the provided surface.
|
||||
Point surfaceSize = new Point(
|
||||
mDefaultDisplay.getDefaultTaskDisplayArea().getBounds().width(),
|
||||
mDefaultDisplay.getDefaultTaskDisplayArea().getBounds().height());
|
||||
surfaceControlMirrors(surfaceSize);
|
||||
|
||||
// WHEN creating the DisplayContent for a new virtual display.
|
||||
final DisplayContent virtualDisplay = new TestDisplayContent.Builder(mAtm,
|
||||
mDisplayInfo).build();
|
||||
|
||||
// GIVEN a session is set up to capture a DisplayContent.
|
||||
ContentRecordingSession session = ContentRecordingSession.createDisplaySession(
|
||||
tokenToMirror);
|
||||
session.setDisplayId(virtualDisplay.getDisplayId());
|
||||
mWm.mContentRecordingController.setContentRecordingSessionLocked(session, mWm);
|
||||
|
||||
// WHEN attempting to mirror on the virtual display.
|
||||
virtualDisplay.updateRecording();
|
||||
|
||||
// THEN mirroring is initiated for the default display's DisplayArea.
|
||||
assertThat(virtualDisplay.isCurrentlyRecording()).isTrue();
|
||||
assertThat(virtualDisplay.getContentRecordingSession()).isEqualTo(session);
|
||||
|
||||
mockSession.finishMocking();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testVirtualDisplayContent_capturedAreaResized() {
|
||||
MockitoSession mockSession = mockitoSession()
|
||||
.initMocks(this)
|
||||
.spyStatic(SurfaceControl.class)
|
||||
.strictness(Strictness.LENIENT)
|
||||
.startMocking();
|
||||
|
||||
// GIVEN MediaProjection has already initialized the WindowToken of the DisplayArea to
|
||||
// mirror.
|
||||
final IBinder tokenToMirror = setUpDefaultTaskDisplayAreaWindowToken();
|
||||
|
||||
// GIVEN SurfaceControl can successfully mirror the provided surface.
|
||||
Point surfaceSize = new Point(
|
||||
mDefaultDisplay.getDefaultTaskDisplayArea().getBounds().width(),
|
||||
mDefaultDisplay.getDefaultTaskDisplayArea().getBounds().height());
|
||||
SurfaceControl mirroredSurface = surfaceControlMirrors(surfaceSize);
|
||||
|
||||
// WHEN creating the DisplayContent for a new virtual display.
|
||||
final DisplayContent virtualDisplay = new TestDisplayContent.Builder(mAtm,
|
||||
mDisplayInfo).build();
|
||||
|
||||
// GIVEN a session is set up to capture a DisplayContent.
|
||||
ContentRecordingSession session = ContentRecordingSession.createDisplaySession(
|
||||
tokenToMirror);
|
||||
session.setDisplayId(virtualDisplay.getDisplayId());
|
||||
mWm.mContentRecordingController.setContentRecordingSessionLocked(session, mWm);
|
||||
|
||||
// WHEN attempting to mirror on the virtual display, and the captured content is resized.
|
||||
virtualDisplay.updateRecording();
|
||||
float xScale = 0.7f;
|
||||
float yScale = 2f;
|
||||
Rect displayAreaBounds = new Rect(0, 0, Math.round(surfaceSize.x * xScale),
|
||||
Math.round(surfaceSize.y * yScale));
|
||||
virtualDisplay.updateMirroredSurface(mTransaction, displayAreaBounds, surfaceSize);
|
||||
|
||||
// THEN content in the captured DisplayArea is scaled to fit the surface size.
|
||||
verify(mTransaction, atLeastOnce()).setMatrix(mirroredSurface, 1.0f / yScale, 0, 0,
|
||||
1.0f / yScale);
|
||||
// THEN captured content is positioned in the centre of the output surface.
|
||||
float scaledWidth = displayAreaBounds.width() / xScale;
|
||||
float xInset = (surfaceSize.x - scaledWidth) / 2;
|
||||
verify(mTransaction, atLeastOnce()).setPosition(mirroredSurface, xInset, 0);
|
||||
|
||||
mockSession.finishMocking();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testVirtualDisplayContent_withoutSurface() {
|
||||
MockitoSession mockSession = mockitoSession()
|
||||
.initMocks(this)
|
||||
.spyStatic(SurfaceControl.class)
|
||||
.strictness(Strictness.LENIENT)
|
||||
.startMocking();
|
||||
|
||||
// GIVEN MediaProjection has already initialized the WindowToken of the DisplayArea to
|
||||
// mirror.
|
||||
final IBinder tokenToMirror = setUpDefaultTaskDisplayAreaWindowToken();
|
||||
@@ -2392,25 +2297,17 @@ public class DisplayContentTests extends WindowTestsBase {
|
||||
ContentRecordingSession session = ContentRecordingSession.createDisplaySession(
|
||||
tokenToMirror);
|
||||
session.setDisplayId(displayId);
|
||||
mWm.mContentRecordingController.setContentRecordingSessionLocked(session, mWm);
|
||||
mWm.setContentRecordingSession(session);
|
||||
actualDC.updateRecording();
|
||||
|
||||
// THEN mirroring is not started, since a null surface indicates the VirtualDisplay is off.
|
||||
assertThat(actualDC.isCurrentlyRecording()).isFalse();
|
||||
assertThat(actualDC.getContentRecordingSession()).isEqualTo(session);
|
||||
|
||||
display.release();
|
||||
mockSession.finishMocking();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testVirtualDisplayContent_withSurface() {
|
||||
MockitoSession mockSession = mockitoSession()
|
||||
.initMocks(this)
|
||||
.spyStatic(SurfaceControl.class)
|
||||
.strictness(Strictness.LENIENT)
|
||||
.startMocking();
|
||||
|
||||
// GIVEN MediaProjection has already initialized the WindowToken of the DisplayArea to
|
||||
// mirror.
|
||||
final IBinder tokenToMirror = setUpDefaultTaskDisplayAreaWindowToken();
|
||||
@@ -2438,10 +2335,8 @@ public class DisplayContentTests extends WindowTestsBase {
|
||||
|
||||
// THEN mirroring is initiated for the default display's DisplayArea.
|
||||
assertThat(actualDC.isCurrentlyRecording()).isTrue();
|
||||
assertThat(actualDC.getContentRecordingSession()).isEqualTo(session);
|
||||
|
||||
display.release();
|
||||
mockSession.finishMocking();
|
||||
}
|
||||
|
||||
private static class MirroringTestToken extends Binder {
|
||||
|
||||
@@ -148,6 +148,7 @@ public class SystemServicesTestRule implements TestRule {
|
||||
private void setUp() {
|
||||
mMockitoSession = mockitoSession()
|
||||
.spyStatic(LocalServices.class)
|
||||
.spyStatic(SurfaceControl.class)
|
||||
.mockStatic(LockGuard.class)
|
||||
.mockStatic(Watchdog.class)
|
||||
.strictness(Strictness.LENIENT)
|
||||
|
||||
Reference in New Issue
Block a user