Merge "MediaProjection sets content recording details in WM directly." into tm-dev

This commit is contained in:
Naomi Musgrave
2022-03-03 10:45:36 +00:00
committed by Android (Google) Code Review
21 changed files with 1305 additions and 322 deletions

View File

@@ -21,7 +21,6 @@ import android.annotation.Nullable;
import android.graphics.Point;
import android.hardware.SensorManager;
import android.os.Handler;
import android.os.IBinder;
import android.os.PowerManager;
import android.util.IntArray;
import android.util.Slog;
@@ -351,24 +350,17 @@ public abstract class DisplayManagerInternal {
public abstract List<RefreshRateLimitation> getRefreshRateLimitations(int displayId);
/**
* Returns the window token of the level of the WindowManager hierarchy to mirror. Returns null
* if layer mirroring by SurfaceFlinger should not be performed for the given displayId.
* For now, only used for mirroring started from MediaProjection.
*/
public abstract IBinder getWindowTokenClientToMirror(int displayId);
/**
* For the given displayId, updates the window token of the level of the WindowManager hierarchy
* to mirror. If windowToken is null, then SurfaceFlinger performs no layer mirroring to the
* For the given displayId, updates if WindowManager is responsible for mirroring on that
* display. If {@code false}, then SurfaceFlinger performs no layer mirroring to the
* given display.
* For now, only used for mirroring started from MediaProjection.
* Only used for mirroring started from MediaProjection.
*/
public abstract void setWindowTokenClientToMirror(int displayId, IBinder windowToken);
public abstract void setWindowManagerMirroring(int displayId, boolean isMirroring);
/**
* Returns the default size of the surface associated with the display, or null if the surface
* is not provided for layer mirroring by SurfaceFlinger.
* For now, only used for mirroring started from MediaProjection.
* Only used for mirroring started from MediaProjection.
*/
public abstract Point getDisplaySurfaceDefaultSize(int displayId);

View File

@@ -24,7 +24,6 @@ import android.annotation.Nullable;
import android.hardware.display.DisplayManager.VirtualDisplayFlag;
import android.media.projection.MediaProjection;
import android.os.Handler;
import android.os.IBinder;
import android.os.Parcel;
import android.os.Parcelable;
import android.view.Surface;
@@ -95,11 +94,10 @@ public final class VirtualDisplayConfig implements Parcelable {
private int mDisplayIdToMirror = DEFAULT_DISPLAY;
/**
* The window token of the level of the WindowManager hierarchy to mirror, or null if mirroring
* should not be performed.
* Indicates if WindowManager is responsible for mirroring content to this VirtualDisplay, or
* if DisplayManager should record contents instead.
*/
@Nullable
private IBinder mWindowTokenClientToMirror = null;
private boolean mWindowManagerMirroring = false;
@@ -126,7 +124,7 @@ public final class VirtualDisplayConfig implements Parcelable {
@Nullable Surface surface,
@Nullable String uniqueId,
int displayIdToMirror,
@Nullable IBinder windowTokenClientToMirror) {
boolean windowManagerMirroring) {
this.mName = name;
com.android.internal.util.AnnotationValidations.validate(
NonNull.class, null, mName);
@@ -148,7 +146,7 @@ public final class VirtualDisplayConfig implements Parcelable {
this.mSurface = surface;
this.mUniqueId = uniqueId;
this.mDisplayIdToMirror = displayIdToMirror;
this.mWindowTokenClientToMirror = windowTokenClientToMirror;
this.mWindowManagerMirroring = windowManagerMirroring;
// onConstructed(); // You can define this method to get a callback
}
@@ -227,12 +225,12 @@ public final class VirtualDisplayConfig implements Parcelable {
}
/**
* The window token of the level of the WindowManager hierarchy to mirror, or null if mirroring
* should not be performed.
* Indicates if WindowManager is responsible for mirroring content to this VirtualDisplay, or
* if DisplayManager should record contents instead.
*/
@DataClass.Generated.Member
public @Nullable IBinder getWindowTokenClientToMirror() {
return mWindowTokenClientToMirror;
public boolean isWindowManagerMirroring() {
return mWindowManagerMirroring;
}
@Override
@@ -242,9 +240,9 @@ public final class VirtualDisplayConfig implements Parcelable {
// void parcelFieldName(Parcel dest, int flags) { ... }
int flg = 0;
if (mWindowManagerMirroring) flg |= 0x100;
if (mSurface != null) flg |= 0x20;
if (mUniqueId != null) flg |= 0x40;
if (mWindowTokenClientToMirror != null) flg |= 0x100;
dest.writeInt(flg);
dest.writeString(mName);
dest.writeInt(mWidth);
@@ -254,7 +252,6 @@ public final class VirtualDisplayConfig implements Parcelable {
if (mSurface != null) dest.writeTypedObject(mSurface, flags);
if (mUniqueId != null) dest.writeString(mUniqueId);
dest.writeInt(mDisplayIdToMirror);
if (mWindowTokenClientToMirror != null) dest.writeStrongBinder(mWindowTokenClientToMirror);
}
@Override
@@ -269,6 +266,7 @@ public final class VirtualDisplayConfig implements Parcelable {
// static FieldType unparcelFieldName(Parcel in) { ... }
int flg = in.readInt();
boolean windowManagerMirroring = (flg & 0x100) != 0;
String name = in.readString();
int width = in.readInt();
int height = in.readInt();
@@ -277,7 +275,6 @@ public final class VirtualDisplayConfig implements Parcelable {
Surface surface = (flg & 0x20) == 0 ? null : (Surface) in.readTypedObject(Surface.CREATOR);
String uniqueId = (flg & 0x40) == 0 ? null : in.readString();
int displayIdToMirror = in.readInt();
IBinder windowTokenClientToMirror = (flg & 0x100) == 0 ? null : (IBinder) in.readStrongBinder();
this.mName = name;
com.android.internal.util.AnnotationValidations.validate(
@@ -300,7 +297,7 @@ public final class VirtualDisplayConfig implements Parcelable {
this.mSurface = surface;
this.mUniqueId = uniqueId;
this.mDisplayIdToMirror = displayIdToMirror;
this.mWindowTokenClientToMirror = windowTokenClientToMirror;
this.mWindowManagerMirroring = windowManagerMirroring;
// onConstructed(); // You can define this method to get a callback
}
@@ -334,7 +331,7 @@ public final class VirtualDisplayConfig implements Parcelable {
private @Nullable Surface mSurface;
private @Nullable String mUniqueId;
private int mDisplayIdToMirror;
private @Nullable IBinder mWindowTokenClientToMirror;
private boolean mWindowManagerMirroring;
private long mBuilderFieldsSet = 0L;
@@ -470,14 +467,14 @@ public final class VirtualDisplayConfig implements Parcelable {
}
/**
* The window token of the level of the WindowManager hierarchy to mirror, or null if mirroring
* should not be performed.
* Indicates if WindowManager is responsible for mirroring content to this VirtualDisplay, or
* if DisplayManager should record contents instead.
*/
@DataClass.Generated.Member
public @NonNull Builder setWindowTokenClientToMirror(@NonNull IBinder value) {
public @NonNull Builder setWindowManagerMirroring(boolean value) {
checkNotUsed();
mBuilderFieldsSet |= 0x100;
mWindowTokenClientToMirror = value;
mWindowManagerMirroring = value;
return this;
}
@@ -499,7 +496,7 @@ public final class VirtualDisplayConfig implements Parcelable {
mDisplayIdToMirror = DEFAULT_DISPLAY;
}
if ((mBuilderFieldsSet & 0x100) == 0) {
mWindowTokenClientToMirror = null;
mWindowManagerMirroring = false;
}
VirtualDisplayConfig o = new VirtualDisplayConfig(
mName,
@@ -510,7 +507,7 @@ public final class VirtualDisplayConfig implements Parcelable {
mSurface,
mUniqueId,
mDisplayIdToMirror,
mWindowTokenClientToMirror);
mWindowManagerMirroring);
return o;
}
@@ -523,10 +520,10 @@ public final class VirtualDisplayConfig implements Parcelable {
}
@DataClass.Generated(
time = 1643938791506L,
time = 1646227247934L,
codegenVersion = "1.0.23",
sourceFile = "frameworks/base/core/java/android/hardware/display/VirtualDisplayConfig.java",
inputSignatures = "private @android.annotation.NonNull java.lang.String mName\nprivate @android.annotation.IntRange int mWidth\nprivate @android.annotation.IntRange int mHeight\nprivate @android.annotation.IntRange int mDensityDpi\nprivate @android.hardware.display.DisplayManager.VirtualDisplayFlag int mFlags\nprivate @android.annotation.Nullable android.view.Surface mSurface\nprivate @android.annotation.Nullable java.lang.String mUniqueId\nprivate int mDisplayIdToMirror\nprivate @android.annotation.Nullable android.os.IBinder mWindowTokenClientToMirror\nclass VirtualDisplayConfig extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genParcelable=true, genAidl=true, genBuilder=true)")
inputSignatures = "private @android.annotation.NonNull java.lang.String mName\nprivate @android.annotation.IntRange int mWidth\nprivate @android.annotation.IntRange int mHeight\nprivate @android.annotation.IntRange int mDensityDpi\nprivate @android.hardware.display.DisplayManager.VirtualDisplayFlag int mFlags\nprivate @android.annotation.Nullable android.view.Surface mSurface\nprivate @android.annotation.Nullable java.lang.String mUniqueId\nprivate int mDisplayIdToMirror\nprivate boolean mWindowManagerMirroring\nclass VirtualDisplayConfig extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genParcelable=true, genAidl=true, genBuilder=true)")
@Deprecated
private void __metadata() {}

View File

@@ -0,0 +1,19 @@
/**
* 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 android.view;
parcelable ContentRecordingSession;

View File

@@ -0,0 +1,447 @@
/*
* 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 android.view;
import static android.view.Display.INVALID_DISPLAY;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.IBinder;
import android.os.Parcel;
import android.os.Parcelable;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.DataClass;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/**
* Description of a content recording session.
*
* @hide
*/
@DataClass(
genConstructor = false,
genToString = true,
genSetters = true,
genEqualsHashCode = true
)
public final class ContentRecordingSession implements Parcelable {
/**
* An entire DisplayContent is being recorded. Recording may also be paused.
*/
public static final int RECORD_CONTENT_DISPLAY = 0;
/**
* A single Task is being recorded. Recording may also be paused.
*/
public static final int RECORD_CONTENT_TASK = 1;
/**
* Unique logical identifier of the {@link android.hardware.display.VirtualDisplay} that has
* recorded content rendered to its surface.
*/
private int mDisplayId = INVALID_DISPLAY;
/**
* The content to record.
*/
@RecordContent
private int mContentToRecord = RECORD_CONTENT_DISPLAY;
/**
* The window token of the layer of the hierarchy to record.
* The display content if {@link #getContentToRecord()} is
* {@link RecordContent#RECORD_CONTENT_DISPLAY}, or task if {@link #getContentToRecord()} is
* {@link RecordContent#RECORD_CONTENT_TASK}.
*/
@VisibleForTesting
@Nullable
private IBinder mTokenToRecord = null;
/**
* Default instance, with recording the display.
*/
private ContentRecordingSession() {
}
/**
* Returns an instance initialized for display recording.
*/
public static ContentRecordingSession createDisplaySession(
@NonNull IBinder displayContentWindowToken) {
return new ContentRecordingSession().setContentToRecord(RECORD_CONTENT_DISPLAY)
.setTokenToRecord(displayContentWindowToken);
}
/**
* Returns an instance initialized for task recording.
*/
public static ContentRecordingSession createTaskSession(
@NonNull IBinder taskWindowContainerToken) {
return new ContentRecordingSession().setContentToRecord(RECORD_CONTENT_TASK)
.setTokenToRecord(taskWindowContainerToken);
}
/**
* Returns {@code true} if this is a valid session.
*/
public static boolean isValid(ContentRecordingSession session) {
return session != null && (session.getDisplayId() > INVALID_DISPLAY
&& session.getTokenToRecord() != null);
}
/**
* Returns {@code true} when both sessions are for the same display.
*/
public static boolean isSameDisplay(ContentRecordingSession session,
ContentRecordingSession incomingSession) {
return session != null && incomingSession != null
&& session.getDisplayId() == incomingSession.getDisplayId();
}
// Code below generated by codegen v1.0.23.
//
// DO NOT MODIFY!
// CHECKSTYLE:OFF Generated code
//
// To regenerate run:
// $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/view/ContentRecordingSession.java
//
// To exclude the generated code from IntelliJ auto-formatting enable (one-time):
// Settings > Editor > Code Style > Formatter Control
//@formatter:off
@IntDef(prefix = "RECORD_CONTENT_", value = {
RECORD_CONTENT_DISPLAY,
RECORD_CONTENT_TASK
})
@Retention(RetentionPolicy.SOURCE)
@DataClass.Generated.Member
public @interface RecordContent {}
@DataClass.Generated.Member
public static String recordContentToString(@RecordContent int value) {
switch (value) {
case RECORD_CONTENT_DISPLAY:
return "RECORD_CONTENT_DISPLAY";
case RECORD_CONTENT_TASK:
return "RECORD_CONTENT_TASK";
default: return Integer.toHexString(value);
}
}
@DataClass.Generated.Member
/* package-private */ ContentRecordingSession(
int displayId,
@RecordContent int contentToRecord,
@VisibleForTesting @Nullable IBinder tokenToRecord) {
this.mDisplayId = displayId;
this.mContentToRecord = contentToRecord;
if (!(mContentToRecord == RECORD_CONTENT_DISPLAY)
&& !(mContentToRecord == RECORD_CONTENT_TASK)) {
throw new java.lang.IllegalArgumentException(
"contentToRecord was " + mContentToRecord + " but must be one of: "
+ "RECORD_CONTENT_DISPLAY(" + RECORD_CONTENT_DISPLAY + "), "
+ "RECORD_CONTENT_TASK(" + RECORD_CONTENT_TASK + ")");
}
this.mTokenToRecord = tokenToRecord;
com.android.internal.util.AnnotationValidations.validate(
VisibleForTesting.class, null, mTokenToRecord);
// onConstructed(); // You can define this method to get a callback
}
/**
* Unique logical identifier of the {@link android.hardware.display.VirtualDisplay} that has
* recorded content rendered to its surface.
*/
@DataClass.Generated.Member
public int getDisplayId() {
return mDisplayId;
}
/**
* The content to record.
*/
@DataClass.Generated.Member
public @RecordContent int getContentToRecord() {
return mContentToRecord;
}
/**
* The window token of the layer of the hierarchy to record.
* The display content if {@link #getContentToRecord()} is
* {@link RecordContent#RECORD_CONTENT_DISPLAY}, or task if {@link #getContentToRecord()} is
* {@link RecordContent#RECORD_CONTENT_TASK}.
*/
@DataClass.Generated.Member
public @VisibleForTesting @Nullable IBinder getTokenToRecord() {
return mTokenToRecord;
}
/**
* Unique logical identifier of the {@link android.hardware.display.VirtualDisplay} that has
* recorded content rendered to its surface.
*/
@DataClass.Generated.Member
public @NonNull ContentRecordingSession setDisplayId( int value) {
mDisplayId = value;
return this;
}
/**
* The content to record.
*/
@DataClass.Generated.Member
public @NonNull ContentRecordingSession setContentToRecord(@RecordContent int value) {
mContentToRecord = value;
if (!(mContentToRecord == RECORD_CONTENT_DISPLAY)
&& !(mContentToRecord == RECORD_CONTENT_TASK)) {
throw new java.lang.IllegalArgumentException(
"contentToRecord was " + mContentToRecord + " but must be one of: "
+ "RECORD_CONTENT_DISPLAY(" + RECORD_CONTENT_DISPLAY + "), "
+ "RECORD_CONTENT_TASK(" + RECORD_CONTENT_TASK + ")");
}
return this;
}
/**
* The window token of the layer of the hierarchy to record.
* The display content if {@link #getContentToRecord()} is
* {@link RecordContent#RECORD_CONTENT_DISPLAY}, or task if {@link #getContentToRecord()} is
* {@link RecordContent#RECORD_CONTENT_TASK}.
*/
@DataClass.Generated.Member
public @NonNull ContentRecordingSession setTokenToRecord(@VisibleForTesting @NonNull IBinder value) {
mTokenToRecord = value;
com.android.internal.util.AnnotationValidations.validate(
VisibleForTesting.class, null, mTokenToRecord);
return this;
}
@Override
@DataClass.Generated.Member
public String toString() {
// You can override field toString logic by defining methods like:
// String fieldNameToString() { ... }
return "ContentRecordingSession { " +
"displayId = " + mDisplayId + ", " +
"contentToRecord = " + recordContentToString(mContentToRecord) + ", " +
"tokenToRecord = " + mTokenToRecord +
" }";
}
@Override
@DataClass.Generated.Member
public boolean equals(@Nullable Object o) {
// You can override field equality logic by defining either of the methods like:
// boolean fieldNameEquals(ContentRecordingSession other) { ... }
// boolean fieldNameEquals(FieldType otherValue) { ... }
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
@SuppressWarnings("unchecked")
ContentRecordingSession that = (ContentRecordingSession) o;
//noinspection PointlessBooleanExpression
return true
&& mDisplayId == that.mDisplayId
&& mContentToRecord == that.mContentToRecord
&& java.util.Objects.equals(mTokenToRecord, that.mTokenToRecord);
}
@Override
@DataClass.Generated.Member
public int hashCode() {
// You can override field hashCode logic by defining methods like:
// int fieldNameHashCode() { ... }
int _hash = 1;
_hash = 31 * _hash + mDisplayId;
_hash = 31 * _hash + mContentToRecord;
_hash = 31 * _hash + java.util.Objects.hashCode(mTokenToRecord);
return _hash;
}
@Override
@DataClass.Generated.Member
public void writeToParcel(@NonNull Parcel dest, int flags) {
// You can override field parcelling by defining methods like:
// void parcelFieldName(Parcel dest, int flags) { ... }
byte flg = 0;
if (mTokenToRecord != null) flg |= 0x4;
dest.writeByte(flg);
dest.writeInt(mDisplayId);
dest.writeInt(mContentToRecord);
if (mTokenToRecord != null) dest.writeStrongBinder(mTokenToRecord);
}
@Override
@DataClass.Generated.Member
public int describeContents() { return 0; }
/** @hide */
@SuppressWarnings({"unchecked", "RedundantCast"})
@DataClass.Generated.Member
/* package-private */ ContentRecordingSession(@NonNull Parcel in) {
// You can override field unparcelling by defining methods like:
// static FieldType unparcelFieldName(Parcel in) { ... }
byte flg = in.readByte();
int displayId = in.readInt();
int contentToRecord = in.readInt();
IBinder tokenToRecord = (flg & 0x4) == 0 ? null : (IBinder) in.readStrongBinder();
this.mDisplayId = displayId;
this.mContentToRecord = contentToRecord;
if (!(mContentToRecord == RECORD_CONTENT_DISPLAY)
&& !(mContentToRecord == RECORD_CONTENT_TASK)) {
throw new java.lang.IllegalArgumentException(
"contentToRecord was " + mContentToRecord + " but must be one of: "
+ "RECORD_CONTENT_DISPLAY(" + RECORD_CONTENT_DISPLAY + "), "
+ "RECORD_CONTENT_TASK(" + RECORD_CONTENT_TASK + ")");
}
this.mTokenToRecord = tokenToRecord;
com.android.internal.util.AnnotationValidations.validate(
VisibleForTesting.class, null, mTokenToRecord);
// onConstructed(); // You can define this method to get a callback
}
@DataClass.Generated.Member
public static final @NonNull Parcelable.Creator<ContentRecordingSession> CREATOR
= new Parcelable.Creator<ContentRecordingSession>() {
@Override
public ContentRecordingSession[] newArray(int size) {
return new ContentRecordingSession[size];
}
@Override
public ContentRecordingSession createFromParcel(@NonNull Parcel in) {
return new ContentRecordingSession(in);
}
};
/**
* A builder for {@link ContentRecordingSession}
*/
@SuppressWarnings("WeakerAccess")
@DataClass.Generated.Member
public static final class Builder {
private int mDisplayId;
private @RecordContent int mContentToRecord;
private @VisibleForTesting @Nullable IBinder mTokenToRecord;
private long mBuilderFieldsSet = 0L;
public Builder() {
}
/**
* Unique logical identifier of the {@link android.hardware.display.VirtualDisplay} that has
* recorded content rendered to its surface.
*/
@DataClass.Generated.Member
public @NonNull Builder setDisplayId(int value) {
checkNotUsed();
mBuilderFieldsSet |= 0x1;
mDisplayId = value;
return this;
}
/**
* The content to record.
*/
@DataClass.Generated.Member
public @NonNull Builder setContentToRecord(@RecordContent int value) {
checkNotUsed();
mBuilderFieldsSet |= 0x2;
mContentToRecord = value;
return this;
}
/**
* The window token of the layer of the hierarchy to record.
* The display content if {@link #getContentToRecord()} is
* {@link RecordContent#RECORD_CONTENT_DISPLAY}, or task if {@link #getContentToRecord()} is
* {@link RecordContent#RECORD_CONTENT_TASK}.
*/
@DataClass.Generated.Member
public @NonNull Builder setTokenToRecord(@VisibleForTesting @NonNull IBinder value) {
checkNotUsed();
mBuilderFieldsSet |= 0x4;
mTokenToRecord = value;
return this;
}
/** Builds the instance. This builder should not be touched after calling this! */
public @NonNull ContentRecordingSession build() {
checkNotUsed();
mBuilderFieldsSet |= 0x8; // Mark builder used
if ((mBuilderFieldsSet & 0x1) == 0) {
mDisplayId = INVALID_DISPLAY;
}
if ((mBuilderFieldsSet & 0x2) == 0) {
mContentToRecord = RECORD_CONTENT_DISPLAY;
}
if ((mBuilderFieldsSet & 0x4) == 0) {
mTokenToRecord = null;
}
ContentRecordingSession o = new ContentRecordingSession(
mDisplayId,
mContentToRecord,
mTokenToRecord);
return o;
}
private void checkNotUsed() {
if ((mBuilderFieldsSet & 0x8) != 0) {
throw new IllegalStateException(
"This Builder should not be reused. Use a new Builder instance instead");
}
}
}
@DataClass.Generated(
time = 1644843382972L,
codegenVersion = "1.0.23",
sourceFile = "frameworks/base/core/java/android/view/ContentRecordingSession.java",
inputSignatures = "public static final int RECORD_CONTENT_DISPLAY\npublic static final int RECORD_CONTENT_TASK\nprivate int mDisplayId\nprivate @android.view.ContentRecordingSession.RecordContent int mContentToRecord\nprivate @com.android.internal.annotations.VisibleForTesting @android.annotation.Nullable android.os.IBinder mTokenToRecord\npublic static android.view.ContentRecordingSession createDisplaySession(android.os.IBinder)\npublic static android.view.ContentRecordingSession createTaskSession(android.os.IBinder)\npublic static boolean isValid(android.view.ContentRecordingSession)\npublic static boolean isSameDisplay(android.view.ContentRecordingSession,android.view.ContentRecordingSession)\nclass ContentRecordingSession extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genConstructor=false, genToString=true, genSetters=true, genEqualsHashCode=true)")
@Deprecated
private void __metadata() {}
//@formatter:on
// End of generated code
}

View File

@@ -32,6 +32,7 @@ import android.graphics.Region;
import android.os.Bundle;
import android.os.IRemoteCallback;
import android.os.ParcelFileDescriptor;
import android.view.ContentRecordingSession;
import android.view.DisplayCutout;
import android.view.DisplayInfo;
import android.view.IAppTransitionAnimationSpecsFuture;
@@ -878,6 +879,17 @@ interface IWindowManager
*/
void detachWindowContextFromWindowContainer(IBinder clientToken);
/**
* Updates the content recording session. If a different session is already in progress, then
* the pre-existing session is stopped, and the new incoming session takes over.
*
* The DisplayContent for the new session will begin recording when
* {@link RootWindowContainer#onDisplayChanged} is invoked for the new {@link VirtualDisplay}.
*
* @param incomingSession the nullable incoming content recording session
*/
void setContentRecordingSession(in ContentRecordingSession incomingSession);
/**
* Registers a listener, which is to be called whenever cross-window blur is enabled/disabled.
*

View File

@@ -75,6 +75,8 @@ per-file ContentInfo.java = file:/core/java/android/service/autofill/OWNERS
per-file ContentInfo.java = file:/core/java/android/widget/OWNERS
# WindowManager
per-file ContentRecordingSession.aidl = file:/services/core/java/com/android/server/wm/OWNERS
per-file ContentRecordingSession.java = file:/services/core/java/com/android/server/wm/OWNERS
per-file DisplayCutout.aidl = file:/services/core/java/com/android/server/wm/OWNERS
per-file DisplayCutout.java = file:/services/core/java/com/android/server/wm/OWNERS
per-file IDisplay*.aidl = file:/services/core/java/com/android/server/wm/OWNERS

View File

@@ -82,7 +82,7 @@ public enum ProtoLogGroup implements IProtoLogGroup {
Consts.TAG_WM),
WM_DEBUG_WINDOW_INSETS(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
Consts.TAG_WM),
WM_DEBUG_LAYER_MIRRORING(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, true,
WM_DEBUG_CONTENT_RECORDING(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, true,
Consts.TAG_WM),
WM_DEBUG_WALLPAPER(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false, Consts.TAG_WM),
WM_DEBUG_BACK_PREVIEW(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, true,

View File

@@ -0,0 +1,117 @@
/*
* 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 android.view;
import static android.view.ContentRecordingSession.RECORD_CONTENT_DISPLAY;
import static android.view.ContentRecordingSession.RECORD_CONTENT_TASK;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.Display.INVALID_DISPLAY;
import static com.google.common.truth.Truth.assertThat;
import android.os.Binder;
import android.os.IBinder;
import android.os.Parcel;
import android.platform.test.annotations.Presubmit;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import org.junit.Test;
import org.junit.runner.RunWith;
/**
* Tests for {@link ContentRecordingSession} class.
*
* Build/Install/Run:
* atest FrameworksCoreTests:ContentRecordingSessionTest
*/
@RunWith(AndroidJUnit4.class)
@SmallTest
@Presubmit
public class ContentRecordingSessionTest {
private static final int DISPLAY_ID = 1;
private static final IBinder WINDOW_TOKEN = new Binder("DisplayContentWindowToken");
@Test
public void testParcelable() {
ContentRecordingSession session = ContentRecordingSession.createTaskSession(WINDOW_TOKEN);
session.setDisplayId(DISPLAY_ID);
Parcel parcel = Parcel.obtain();
session.writeToParcel(parcel, 0 /* flags */);
parcel.setDataPosition(0);
ContentRecordingSession session2 = ContentRecordingSession.CREATOR.createFromParcel(parcel);
assertThat(session).isEqualTo(session2);
parcel.recycle();
}
@Test
public void testTaskConstructor() {
ContentRecordingSession session = ContentRecordingSession.createTaskSession(WINDOW_TOKEN);
assertThat(session.getContentToRecord()).isEqualTo(RECORD_CONTENT_TASK);
assertThat(session.getTokenToRecord()).isEqualTo(WINDOW_TOKEN);
}
@Test
public void testDisplayConstructor() {
ContentRecordingSession session = ContentRecordingSession.createDisplaySession(
WINDOW_TOKEN);
assertThat(session.getContentToRecord()).isEqualTo(RECORD_CONTENT_DISPLAY);
assertThat(session.getTokenToRecord()).isEqualTo(WINDOW_TOKEN);
}
@Test
public void testIsValid() {
ContentRecordingSession session = ContentRecordingSession.createDisplaySession(
WINDOW_TOKEN);
assertThat(ContentRecordingSession.isValid(session)).isFalse();
session.setDisplayId(DEFAULT_DISPLAY);
assertThat(ContentRecordingSession.isValid(session)).isTrue();
session.setDisplayId(INVALID_DISPLAY);
assertThat(ContentRecordingSession.isValid(session)).isFalse();
}
@Test
public void testIsSameDisplay() {
assertThat(ContentRecordingSession.isSameDisplay(null, null)).isFalse();
ContentRecordingSession session = ContentRecordingSession.createDisplaySession(
WINDOW_TOKEN);
session.setDisplayId(DEFAULT_DISPLAY);
assertThat(ContentRecordingSession.isSameDisplay(session, null)).isFalse();
ContentRecordingSession incomingSession = ContentRecordingSession.createDisplaySession(
WINDOW_TOKEN);
incomingSession.setDisplayId(DEFAULT_DISPLAY);
assertThat(ContentRecordingSession.isSameDisplay(session, incomingSession)).isTrue();
incomingSession.setDisplayId(DEFAULT_DISPLAY + 1);
assertThat(ContentRecordingSession.isSameDisplay(session, incomingSession)).isFalse();
}
@Test
public void testEquals() {
ContentRecordingSession session = ContentRecordingSession.createTaskSession(WINDOW_TOKEN);
session.setDisplayId(DISPLAY_ID);
ContentRecordingSession session2 = ContentRecordingSession.createTaskSession(WINDOW_TOKEN);
session2.setDisplayId(DISPLAY_ID);
assertThat(session).isEqualTo(session2);
}
}

View File

@@ -13,6 +13,7 @@ per-file *Insets* = file:/services/core/java/com/android/server/wm/OWNERS
per-file *View* = file:/services/core/java/com/android/server/wm/OWNERS
per-file *Visibility* = file:/services/core/java/com/android/server/wm/OWNERS
per-file *Window* = file:/services/core/java/com/android/server/wm/OWNERS
per-file *ContentRecord* = file:/services/core/java/com/android/server/wm/OWNERS
# Scroll Capture
per-file *ScrollCapture*.java = file:/packages/SystemUI/src/com/android/systemui/screenshot/OWNERS

View File

@@ -193,12 +193,6 @@
"group": "WM_DEBUG_SYNC_ENGINE",
"at": "com\/android\/server\/wm\/BLASTSyncEngine.java"
},
"-1898316768": {
"message": "Unable to retrieve window container to start layer mirroring for display %d",
"level": "VERBOSE",
"group": "WM_DEBUG_LAYER_MIRRORING",
"at": "com\/android\/server\/wm\/DisplayContent.java"
},
"-1895337367": {
"message": "Delete root task display=%d winMode=%d",
"level": "VERBOSE",
@@ -349,6 +343,12 @@
"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",
@@ -511,6 +511,12 @@
"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",
@@ -733,6 +739,12 @@
"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",
"level": "VERBOSE",
"group": "WM_DEBUG_CONTENT_RECORDING",
"at": "com\/android\/server\/wm\/DisplayContent.java"
},
"-1311436264": {
"message": "Unregister task fragment organizer=%s uid=%d pid=%d",
"level": "VERBOSE",
@@ -847,6 +859,12 @@
"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",
@@ -1027,6 +1045,12 @@
"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",
@@ -1087,6 +1111,12 @@
"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",
@@ -1105,12 +1135,6 @@
"group": "WM_ERROR",
"at": "com\/android\/server\/wm\/WindowManagerService.java"
},
"-904499590": {
"message": "Provided surface for layer mirroring on display %d is not present, so do not update the surface",
"level": "VERBOSE",
"group": "WM_DEBUG_LAYER_MIRRORING",
"at": "com\/android\/server\/wm\/DisplayContent.java"
},
"-883738232": {
"message": "Adding more than one toast window for UID at a time.",
"level": "WARN",
@@ -1345,12 +1369,6 @@
"group": "WM_DEBUG_ORIENTATION",
"at": "com\/android\/server\/wm\/RootWindowContainer.java"
},
"-663411559": {
"message": "Going ahead with updating layer mirroring for display %d to new bounds %s and\/or orientation %d.",
"level": "VERBOSE",
"group": "WM_DEBUG_LAYER_MIRRORING",
"at": "com\/android\/server\/wm\/DisplayContent.java"
},
"-655104359": {
"message": "Frontmost changed immersion: %s",
"level": "DEBUG",
@@ -1531,6 +1549,18 @@
"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",
@@ -1609,10 +1639,10 @@
"group": "WM_ERROR",
"at": "com\/android\/server\/wm\/WindowManagerService.java"
},
"-384564722": {
"message": "Unable to start layer mirroring for display %d since the surface is not available.",
"-381522987": {
"message": "Display %d state is now (%d), so update recording?",
"level": "VERBOSE",
"group": "WM_DEBUG_LAYER_MIRRORING",
"group": "WM_DEBUG_CONTENT_RECORDING",
"at": "com\/android\/server\/wm\/DisplayContent.java"
},
"-381475323": {
@@ -1639,6 +1669,12 @@
"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",
@@ -1699,12 +1735,6 @@
"group": "WM_DEBUG_STATES",
"at": "com\/android\/server\/wm\/TaskFragment.java"
},
"-309399422": {
"message": "Display %d state is now (%d), so update layer mirroring?",
"level": "VERBOSE",
"group": "WM_DEBUG_LAYER_MIRRORING",
"at": "com\/android\/server\/wm\/DisplayContent.java"
},
"-304728471": {
"message": "New wallpaper: target=%s prev=%s",
"level": "DEBUG",
@@ -1753,6 +1783,12 @@
"group": "WM_DEBUG_APP_TRANSITIONS",
"at": "com\/android\/server\/wm\/AppTransitionController.java"
},
"-237664290": {
"message": "Pause the recording session on display %s",
"level": "VERBOSE",
"group": "WM_DEBUG_CONTENT_RECORDING",
"at": "com\/android\/server\/wm\/ContentRecordingController.java"
},
"-235225312": {
"message": "Skipping config check for initializing activity: %s",
"level": "VERBOSE",
@@ -1789,12 +1825,6 @@
"group": "WM_DEBUG_WINDOW_MOVEMENT",
"at": "com\/android\/server\/wm\/WindowManagerService.java"
},
"-190034097": {
"message": "Unable to retrieve window container to update layer mirroring for display %d",
"level": "VERBOSE",
"group": "WM_DEBUG_LAYER_MIRRORING",
"at": "com\/android\/server\/wm\/DisplayContent.java"
},
"-182877285": {
"message": "Wallpaper layer changed: assigning layers + relayout",
"level": "VERBOSE",
@@ -1903,12 +1933,6 @@
"group": "WM_DEBUG_STATES",
"at": "com\/android\/server\/wm\/TaskFragment.java"
},
"-79877120": {
"message": "Display %d has content (%b) so disable layer mirroring",
"level": "VERBOSE",
"group": "WM_DEBUG_LAYER_MIRRORING",
"at": "com\/android\/server\/wm\/DisplayContent.java"
},
"-70719599": {
"message": "Unregister remote animations for organizer=%s uid=%d pid=%d",
"level": "VERBOSE",
@@ -2473,12 +2497,6 @@
"group": "WM_DEBUG_WINDOW_ORGANIZER",
"at": "com\/android\/server\/wm\/DisplayAreaOrganizerController.java"
},
"504397469": {
"message": "Unable to update layer mirroring for display %d to new bounds %s and\/or orientation %d, since the surface is not available.",
"level": "VERBOSE",
"group": "WM_DEBUG_LAYER_MIRRORING",
"at": "com\/android\/server\/wm\/DisplayContent.java"
},
"508887531": {
"message": "applyAnimation voice: anim=%s transit=%s isEntrance=%b Callers=%s",
"level": "VERBOSE",
@@ -3253,6 +3271,12 @@
"group": "WM_DEBUG_WINDOW_ORGANIZER",
"at": "com\/android\/server\/wm\/TaskOrganizerController.java"
},
"1401287081": {
"message": "Handle incoming session on display %d, with a pre-existing session %s",
"level": "VERBOSE",
"group": "WM_DEBUG_CONTENT_RECORDING",
"at": "com\/android\/server\/wm\/ContentRecordingController.java"
},
"1401295262": {
"message": "Mode default, asking user",
"level": "WARN",
@@ -3265,12 +3289,6 @@
"group": "WM_DEBUG_SCREEN_ON",
"at": "com\/android\/server\/wm\/WindowManagerService.java"
},
"1407569006": {
"message": "Display %d was already layer mirroring, so apply transformations if necessary",
"level": "VERBOSE",
"group": "WM_DEBUG_LAYER_MIRRORING",
"at": "com\/android\/server\/wm\/DisplayContent.java"
},
"1422781269": {
"message": "Resuming rotation after re-position",
"level": "DEBUG",
@@ -3517,12 +3535,6 @@
"group": "WM_DEBUG_CONFIGURATION",
"at": "com\/android\/server\/wm\/ActivityRecord.java"
},
"1687376052": {
"message": "Display %d has no content and is on, so start layer mirroring for state %d",
"level": "VERBOSE",
"group": "WM_DEBUG_LAYER_MIRRORING",
"at": "com\/android\/server\/wm\/DisplayContent.java"
},
"1689989893": {
"message": "SyncGroup %d: Set ready",
"level": "VERBOSE",
@@ -3535,6 +3547,12 @@
"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",
@@ -3667,6 +3685,12 @@
"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",
@@ -3942,6 +3966,9 @@
"WM_DEBUG_CONTAINERS": {
"tag": "WindowManager"
},
"WM_DEBUG_CONTENT_RECORDING": {
"tag": "WindowManager"
},
"WM_DEBUG_DRAW": {
"tag": "WindowManager"
},
@@ -3960,9 +3987,6 @@
"WM_DEBUG_KEEP_SCREEN_ON": {
"tag": "WindowManager"
},
"WM_DEBUG_LAYER_MIRRORING": {
"tag": "WindowManager"
},
"WM_DEBUG_LOCKTASK": {
"tag": "WindowManager"
},

View File

@@ -29,7 +29,9 @@ import android.os.IBinder;
import android.os.RemoteException;
import android.util.ArrayMap;
import android.util.Log;
import android.view.ContentRecordingSession;
import android.view.Surface;
import android.view.WindowManagerGlobal;
import java.util.Map;
@@ -106,16 +108,12 @@ public final class MediaProjection {
if (isSecure) {
flags |= DisplayManager.VIRTUAL_DISPLAY_FLAG_SECURE;
}
Context windowContext = mContext.createWindowContext(mContext.getDisplayNoVerify(),
TYPE_APPLICATION, null /* options */);
final VirtualDisplayConfig.Builder builder = buildMirroredVirtualDisplay(name, width,
height, dpi, windowContext.getWindowContextToken());
builder.setFlags(flags);
final VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder(name, width,
height, dpi).setFlags(flags);
if (surface != null) {
builder.setSurface(surface);
}
VirtualDisplay virtualDisplay = createVirtualDisplay(builder.build(), callback, handler,
windowContext);
VirtualDisplay virtualDisplay = createVirtualDisplay(builder, callback, handler);
return virtualDisplay;
}
@@ -145,37 +143,15 @@ public final class MediaProjection {
public VirtualDisplay createVirtualDisplay(@NonNull String name,
int width, int height, int dpi, int flags, @Nullable Surface surface,
@Nullable VirtualDisplay.Callback callback, @Nullable Handler handler) {
Context windowContext = mContext.createWindowContext(mContext.getDisplayNoVerify(),
TYPE_APPLICATION, null /* options */);
final VirtualDisplayConfig.Builder builder = buildMirroredVirtualDisplay(name, width,
height, dpi, windowContext.getWindowContextToken());
builder.setFlags(flags);
final VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder(name, width,
height, dpi).setFlags(flags);
if (surface != null) {
builder.setSurface(surface);
}
VirtualDisplay virtualDisplay = createVirtualDisplay(builder.build(), callback, handler,
windowContext);
VirtualDisplay virtualDisplay = createVirtualDisplay(builder, callback, handler);
return virtualDisplay;
}
/**
* Constructs a {@link VirtualDisplayConfig.Builder}, which will mirror the contents of a
* DisplayArea. The DisplayArea to mirror is from the DisplayArea the caller is launched on.
*
* @param name The name of the virtual display, must be non-empty.
* @param width The width of the virtual display in pixels. Must be greater than 0.
* @param height The height of the virtual display in pixels. Must be greater than 0.
* @param dpi The density of the virtual display in dpi. Must be greater than 0.
* @return a config representing a VirtualDisplay
*/
private VirtualDisplayConfig.Builder buildMirroredVirtualDisplay(@NonNull String name,
int width, int height, int dpi, IBinder windowContextToken) {
final VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder(name, width,
height, dpi);
builder.setWindowTokenClientToMirror(windowContextToken);
return builder;
}
/**
* Creates a {@link android.hardware.display.VirtualDisplay} to capture the
* contents of the screen.
@@ -186,18 +162,52 @@ public final class MediaProjection {
* @param handler The {@link android.os.Handler} on which the callback should be invoked, or
* null if the callback should be invoked on the calling thread's main
* {@link android.os.Looper}.
* @param windowContext the WindowContext associated with the caller.
*
* @see android.hardware.display.VirtualDisplay
* @hide
*/
@Nullable
public VirtualDisplay createVirtualDisplay(@NonNull VirtualDisplayConfig virtualDisplayConfig,
@Nullable VirtualDisplay.Callback callback, @Nullable Handler handler,
Context windowContext) {
DisplayManager dm = mContext.getSystemService(DisplayManager.class);
return dm.createVirtualDisplay(this, virtualDisplayConfig, callback, handler,
windowContext);
public VirtualDisplay createVirtualDisplay(
@NonNull VirtualDisplayConfig.Builder virtualDisplayConfig,
@Nullable VirtualDisplay.Callback callback, @Nullable Handler handler) {
try {
final Context windowContext = mContext.createWindowContext(
mContext.getDisplayNoVerify(),
TYPE_APPLICATION, null /* options */);
final IBinder windowContextToken = windowContext.getWindowContextToken();
virtualDisplayConfig.setWindowManagerMirroring(true);
final DisplayManager dm = mContext.getSystemService(DisplayManager.class);
final VirtualDisplay virtualDisplay = dm.createVirtualDisplay(this,
virtualDisplayConfig.build(),
callback, handler, windowContext);
setSession(windowContextToken, virtualDisplay);
return virtualDisplay;
} catch (RemoteException e) {
// Can not capture if WMS is not accessible, so bail out.
throw e.rethrowFromSystemServer();
}
}
/**
* Updates the {@link ContentRecordingSession} describing the recording taking place on this
* {@link VirtualDisplay}.
*
* @throws RemoteException if updating the session on the server failed.
*/
private void setSession(@NonNull IBinder windowContextToken,
@Nullable VirtualDisplay virtualDisplay)
throws RemoteException {
if (virtualDisplay == null) {
// Not able to set up a new VirtualDisplay.
return;
}
// Identify the VirtualDisplay that will be hosting the recording.
ContentRecordingSession session = ContentRecordingSession.createDisplaySession(
windowContextToken);
session.setDisplayId(virtualDisplay.getDisplay().getDisplayId());
// TODO(b/216625226) handle task recording.
// Successfully set up, so save the current session details.
WindowManagerGlobal.getWindowManagerService().setContentRecordingSession(session);
}
/**

View File

@@ -1 +1,2 @@
michaelwr@google.com
santoscordon@google.com

View File

@@ -109,27 +109,26 @@ abstract class DisplayDevice {
}
/**
* Returns the window token of the level of the WindowManager hierarchy to mirror, or null
* if layer mirroring by SurfaceFlinger should not be performed.
* For now, only used for mirroring started from MediaProjection.
* Returns the if WindowManager is responsible for mirroring on this display. If {@code false},
* then SurfaceFlinger performs no layer mirroring on this display.
* Only used for mirroring started from MediaProjection.
*/
@Nullable
public IBinder getWindowTokenClientToMirrorLocked() {
return null;
public boolean isWindowManagerMirroringLocked() {
return false;
}
/**
* Updates the window token of the level of the level of the WindowManager hierarchy to mirror.
* If windowToken is null, then no layer mirroring by SurfaceFlinger to should be performed.
* For now, only used for mirroring started from MediaProjection.
* Updates if WindowManager is responsible for mirroring on this display. If {@code false}, then
* SurfaceFlinger performs no layer mirroring to this display.
* Only used for mirroring started from MediaProjection.
*/
public void setWindowTokenClientToMirrorLocked(IBinder windowToken) {
public void setWindowManagerMirroringLocked(boolean isMirroring) {
}
/**
* Returns the default size of the surface associated with the display, or null if the surface
* is not provided for layer mirroring by SurfaceFlinger.
* For now, only used for mirroring started from MediaProjection.
* Only used for mirroring started from MediaProjection.
*/
@Nullable
public Point getDisplaySurfaceDefaultSize() {

View File

@@ -2265,13 +2265,12 @@ public final class DisplayManagerService extends SystemService {
final DisplayDeviceInfo info = device.getDisplayDeviceInfoLocked();
final boolean ownContent = (info.flags & DisplayDeviceInfo.FLAG_OWN_CONTENT_ONLY) != 0;
// Mirror the part of WM hierarchy that corresponds to the provided window token.
IBinder windowTokenClientToMirror = device.getWindowTokenClientToMirrorLocked();
// Find the logical display that the display device is showing.
// Certain displays only ever show their own content.
LogicalDisplay display = mLogicalDisplayMapper.getDisplayLocked(device);
if (!ownContent && windowTokenClientToMirror == null) {
// Proceed with display-managed mirroring only if window manager will not be handling it.
if (!ownContent && !device.isWindowManagerMirroringLocked()) {
// Only mirror the display if content recording is not taking place in WM.
if (display != null && !display.hasContentLocked()) {
// If the display does not have any content of its own, then
// automatically mirror the requested logical display contents if possible.
@@ -3864,23 +3863,11 @@ public final class DisplayManagerService extends SystemService {
}
@Override
public IBinder getWindowTokenClientToMirror(int displayId) {
final DisplayDevice device;
synchronized (mSyncRoot) {
device = getDeviceForDisplayLocked(displayId);
if (device == null) {
return null;
}
}
return device.getWindowTokenClientToMirrorLocked();
}
@Override
public void setWindowTokenClientToMirror(int displayId, IBinder windowToken) {
public void setWindowManagerMirroring(int displayId, boolean isMirroring) {
synchronized (mSyncRoot) {
final DisplayDevice device = getDeviceForDisplayLocked(displayId);
if (device != null) {
device.setWindowTokenClientToMirrorLocked(windowToken);
device.setWindowManagerMirroringLocked(isMirroring);
}
}
}

View File

@@ -33,7 +33,6 @@ import static com.android.server.display.DisplayDeviceInfo.FLAG_ALWAYS_UNLOCKED;
import static com.android.server.display.DisplayDeviceInfo.FLAG_OWN_DISPLAY_GROUP;
import static com.android.server.display.DisplayDeviceInfo.FLAG_TRUSTED;
import android.annotation.Nullable;
import android.content.Context;
import android.graphics.Point;
import android.hardware.display.IVirtualDisplayCallback;
@@ -235,7 +234,7 @@ public class VirtualDisplayAdapter extends DisplayAdapter {
private Display.Mode mMode;
private boolean mIsDisplayOn;
private int mDisplayIdToMirror;
private IBinder mWindowTokenClientToMirror;
private boolean mIsWindowManagerMirroring;
public VirtualDisplayDevice(IBinder displayToken, IBinder appToken,
int ownerUid, String ownerPackageName, Surface surface, int flags,
@@ -258,7 +257,7 @@ public class VirtualDisplayAdapter extends DisplayAdapter {
mUniqueIndex = uniqueIndex;
mIsDisplayOn = surface != null;
mDisplayIdToMirror = virtualDisplayConfig.getDisplayIdToMirror();
mWindowTokenClientToMirror = virtualDisplayConfig.getWindowTokenClientToMirror();
mIsWindowManagerMirroring = virtualDisplayConfig.isWindowManagerMirroring();
}
@Override
@@ -289,15 +288,14 @@ public class VirtualDisplayAdapter extends DisplayAdapter {
}
@Override
@Nullable
public IBinder getWindowTokenClientToMirrorLocked() {
return mWindowTokenClientToMirror;
public boolean isWindowManagerMirroringLocked() {
return mIsWindowManagerMirroring;
}
@Override
public void setWindowTokenClientToMirrorLocked(IBinder windowToken) {
if (mWindowTokenClientToMirror != windowToken) {
mWindowTokenClientToMirror = windowToken;
public void setWindowManagerMirroringLocked(boolean mirroring) {
if (mIsWindowManagerMirroring != mirroring) {
mIsWindowManagerMirroring = mirroring;
sendDisplayDeviceEventLocked(this, DISPLAY_DEVICE_EVENT_CHANGED);
sendTraversalRequestLocked();
}
@@ -391,7 +389,7 @@ public class VirtualDisplayAdapter extends DisplayAdapter {
pw.println("mDisplayState=" + Display.stateToString(mDisplayState));
pw.println("mStopped=" + mStopped);
pw.println("mDisplayIdToMirror=" + mDisplayIdToMirror);
pw.println("mWindowTokenClientToMirror=" + mWindowTokenClientToMirror);
pw.println("mWindowManagerMirroring=" + mIsWindowManagerMirroring);
}

View File

@@ -1 +1,2 @@
michaelwr@google.com
santoscordon@google.com

View File

@@ -0,0 +1,94 @@
/*
* 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 com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_CONTENT_RECORDING;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.view.ContentRecordingSession;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.protolog.common.ProtoLog;
/**
* Orchestrates the handoff between displays if the recording session changes, and keeps track of
* the current recording session state. Only supports one content recording session on the device at
* once.
*/
final class ContentRecordingController {
/**
* The current recording session.
*/
@Nullable
private ContentRecordingSession mSession = null;
@Nullable
private DisplayContent mDisplayContent = null;
/**
* Returns the current recording session. If returns {@code null}, then recording is not taking
* place.
*/
@Nullable
@VisibleForTesting
ContentRecordingSession getContentRecordingSessionLocked() {
// Copy out the session, to allow it to be modified without updating this reference.
return mSession;
}
/**
* 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 wmService the window manager service
*/
void setContentRecordingSessionLocked(@Nullable ContentRecordingSession incomingSession,
@NonNull WindowManagerService wmService) {
if (incomingSession != null && (!ContentRecordingSession.isValid(incomingSession)
|| ContentRecordingSession.isSameDisplay(mSession, incomingSession))) {
// Ignore an invalid session, or a session for the same display as currently recording.
return;
}
DisplayContent incomingDisplayContent = null;
if (incomingSession != null) {
// Recording will start on a new display, possibly taking over from a current session.
ProtoLog.v(WM_DEBUG_CONTENT_RECORDING,
"Handle incoming session on display %d, with a pre-existing session %s",
incomingSession.getDisplayId(),
mSession == null ? null : mSession.getDisplayId());
incomingDisplayContent = wmService.mRoot.getDisplayContentOrCreate(
incomingSession.getDisplayId());
incomingDisplayContent.setContentRecordingSession(incomingSession);
}
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",
mDisplayContent.getDisplayId());
mDisplayContent.pauseRecording();
mDisplayContent.setContentRecordingSession(null);
}
// Update the cached states.
mDisplayContent = incomingDisplayContent;
mSession = incomingSession;
}
}

View File

@@ -37,6 +37,7 @@ 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;
@@ -88,10 +89,10 @@ import static android.window.DisplayAreaOrganizer.FEATURE_WINDOWED_MAGNIFICATION
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_APP_TRANSITIONS;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_BOOT;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_CONTENT_RECORDING;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_FOCUS;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_FOCUS_LIGHT;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_IME;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_LAYER_MIRRORING;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ORIENTATION;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_SCREEN_ON;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_WALLPAPER;
@@ -197,6 +198,7 @@ import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
import android.util.proto.ProtoOutputStream;
import android.view.ContentRecordingSession;
import android.view.Display;
import android.view.DisplayCutout;
import android.view.DisplayInfo;
@@ -304,22 +306,28 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
*/
private SurfaceControl mWindowingLayer;
// TODO(216756854) move all recording fields to the controller
/**
* The window token of the layer of the hierarchy to mirror, or null if this DisplayContent
* is not being used for layer mirroring.
* The session for content recording, or null if this DisplayContent is not being used for
* recording.
*/
@VisibleForTesting IBinder mTokenToMirror = null;
@VisibleForTesting private ContentRecordingSession mContentRecordingSession = null;
/**
* The surface for mirroring the contents of this hierarchy, or null if layer mirroring is
* 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.
*/
private SurfaceControl mMirroredSurface = null;
@Nullable private SurfaceControl mRecordedSurface = null;
/**
* The last bounds of the DisplayArea to mirror.
* The last bounds of the region to record.
*/
private Rect mLastMirroredDisplayAreaBounds = null;
@Nullable private Rect mLastRecordedBounds = null;
/**
* The default per Display minimal size of tasks. Calculated at construction.
@@ -2535,44 +2543,41 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
// Update IME parent if needed.
updateImeParent();
// Update mirroring surface for MediaProjection, if this DisplayContent is being used
// for layer mirroring.
if (isCurrentlyMirroring() && mLastMirroredDisplayAreaBounds != null) {
// Mirroring has already begun, but update mirroring since the display is now on.
final WindowContainer wc = mWmService.mWindowContextListenerController.getContainer(
mTokenToMirror);
if (wc == null) {
ProtoLog.v(WM_DEBUG_LAYER_MIRRORING,
"Unable to retrieve window container to update layer mirroring for "
// 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_LAYER_MIRRORING,
"Display %d was already layer mirroring, so apply transformations if necessary",
ProtoLog.v(WM_DEBUG_CONTENT_RECORDING,
"Display %d was already recording, so apply transformations if necessary",
mDisplayId);
// Retrieve the size of the DisplayArea to mirror, and continue with the update
// Retrieve the size of the region to record, and continue with the update
// if the bounds or orientation has changed.
final Rect displayAreaBounds = wc.getDisplayContent().getBounds();
int displayAreaOrientation = wc.getDisplayContent().getOrientation();
if (!mLastMirroredDisplayAreaBounds.equals(displayAreaBounds)
|| lastOrientation != displayAreaOrientation) {
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_LAYER_MIRRORING,
"Going ahead with updating layer mirroring for display %d to new "
ProtoLog.v(WM_DEBUG_CONTENT_RECORDING,
"Going ahead with updating recording for display %d to new "
+ "bounds %s and/or orientation %d.",
mDisplayId, displayAreaBounds, displayAreaOrientation);
mDisplayId, recordedContentBounds, recordedContentOrientation);
updateMirroredSurface(mWmService.mTransactionFactory.get(),
displayAreaBounds, surfaceSize);
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_LAYER_MIRRORING,
"Unable to update layer mirroring for display %d to new bounds %s"
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, displayAreaBounds, displayAreaOrientation);
mDisplayId, recordedContentBounds, recordedContentOrientation);
}
}
}
@@ -4563,8 +4568,8 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
mTmpApplySurfaceChangesTransactionState.preferMinimalPostProcessing,
true /* inTraversal, must call performTraversalInTrans... below */);
}
// If the display now has content, or no longer has content, update layer mirroring.
updateMirroring();
// If the display now has content, or no longer has content, update recording.
updateRecording();
final boolean wallpaperVisible = mWallpaperController.isWallpaperVisible();
if (wallpaperVisible != mLastWallpaperVisible) {
@@ -5558,13 +5563,13 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
} else if (displayState == Display.STATE_ON) {
mOffTokenAcquirer.release(mDisplayId);
}
ProtoLog.v(WM_DEBUG_LAYER_MIRRORING,
"Display %d state is now (%d), so update layer mirroring?",
ProtoLog.v(WM_DEBUG_CONTENT_RECORDING,
"Display %d state is now (%d), so update recording?",
mDisplayId, displayState);
if (lastDisplayState != displayState) {
// If state is on due to surface being added, then start layer mirroring.
// If state is off due to surface being removed, then stop layer mirroring.
updateMirroring();
// If state is on due to surface being added, then start recording.
// If state is off due to surface being removed, then stop recording.
updateRecording();
}
}
// Dispatch pending Configuration to WindowContext if the associated display changes to
@@ -5867,11 +5872,12 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
}
mRemoved = true;
if (mMirroredSurface != null) {
if (mRecordedSurface != null) {
// Do not wait for the mirrored surface to be garbage collected, but clean up
// immediately.
mWmService.mTransactionFactory.get().remove(mMirroredSurface).apply();
mMirroredSurface = null;
mWmService.mTransactionFactory.get().remove(mRecordedSurface).apply();
mRecordedSurface = null;
clearContentRecordingSession();
}
// Only update focus/visibility for the last one because there may be many root tasks are
@@ -6112,68 +6118,84 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
}
/**
* Start mirroring 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 mirroring, falls
* 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 startMirrorIfNeeded() {
// Only mirror if this display does not have its own content, is not mirroring already,
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 || isCurrentlyMirroring() || mDisplay.getState() == Display.STATE_OFF) {
if (mLastHasContent || isCurrentlyRecording() || mDisplay.getState() == Display.STATE_OFF
|| mContentRecordingSession == null) {
return;
}
// Given the WindowToken of the DisplayArea to mirror, retrieve the associated
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 tokenToMirror = mWmService.mDisplayManagerInternal.getWindowTokenClientToMirror(
mDisplayId);
if (tokenToMirror == null) {
// This DisplayContent instance is not involved in layer mirroring. If the display
// has been created for capturing, fall back to prior MediaProjection approach.
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(
tokenToMirror);
tokenToRecord);
if (wc == null) {
// Un-set the window token to mirror for this VirtualDisplay, to fall back to the
// Un-set the window token to record for this VirtualDisplay. Fall back to the
// original MediaProjection approach.
mWmService.mDisplayManagerInternal.setWindowTokenClientToMirror(mDisplayId, null);
ProtoLog.v(WM_DEBUG_LAYER_MIRRORING,
"Unable to retrieve window container to start layer mirroring for display %d",
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();
Point surfaceSize = fetchSurfaceSizeIfPresent();
final Point surfaceSize = fetchSurfaceSizeIfPresent();
if (surfaceSize == null) {
ProtoLog.v(WM_DEBUG_LAYER_MIRRORING,
"Unable to start layer mirroring for display %d since the surface is not "
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_LAYER_MIRRORING,
"Display %d has no content and is on, so start layer mirroring for state %d",
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.
SurfaceControl sc = wc.getDisplayContent().getSurfaceControl();
mMirroredSurface = SurfaceControl.mirrorSurface(sc);
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(mMirroredSurface, mSurfaceControl)
.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, wc.getDisplayContent().getBounds(), surfaceSize);
mTokenToMirror = tokenToMirror;
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
@@ -6182,34 +6204,64 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
}
/**
* Start mirroring if this DisplayContent no longer has content. Stop mirroring if it now
* Pause the recording session.
*/
public void pauseRecording() {
if (mRecordedSurface == null) {
return;
}
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);
}
/**
* Start recording if this DisplayContent no longer has content. Stop recording if it now
* has content or the display is not on.
*/
private void updateMirroring() {
if (isCurrentlyMirroring() && (mLastHasContent
@VisibleForTesting void updateRecording() {
if (isCurrentlyRecording() && (mLastHasContent
|| mDisplay.getState() == Display.STATE_OFF)) {
ProtoLog.v(WM_DEBUG_LAYER_MIRRORING,
"Display %d has content (%b) so disable layer mirroring", 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(mMirroredSurface)
// 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();
// Stop mirroring by destroying the reference to the mirrored layer.
mMirroredSurface = null;
// Do not un-set the token, in case content is removed and mirroring should begin again.
pauseRecording();
} else {
// Display no longer has content, or now has a surface to write to, so try to start
// mirroring to it.
startMirrorIfNeeded();
// recording.
startRecordingIfNeeded();
}
}
@@ -6217,22 +6269,23 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
* 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 displayAreaBounds bounds of the DisplayArea to mirror to the surface provided by
* the app.
* @param surfaceSize the default size of the surface to write the display area content to
* @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 displayAreaBounds, Point surfaceSize) {
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) displayAreaBounds.width();
float scaleY = surfaceSize.y / (float) displayAreaBounds.height();
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) displayAreaBounds.width());
int scaledHeight = Math.round(scale * (float) displayAreaBounds.height());
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.
@@ -6248,16 +6301,16 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
transaction
// Crop the area to capture to exclude the 'extra' wallpaper that is used
// for parallax (b/189930234).
.setWindowCrop(mMirroredSurface, displayAreaBounds.width(),
displayAreaBounds.height())
.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(mMirroredSurface, scale, 0 /* dtdx */, 0 /* dtdy */, scale)
.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(mMirroredSurface, shiftedX /* x */, shiftedY /* y */)
.setPosition(mRecordedSurface, shiftedX /* x */, shiftedY /* y */)
.apply();
mLastMirroredDisplayAreaBounds = new Rect(displayAreaBounds);
mLastRecordedBounds = new Rect(recordedContentBounds);
}
/**
@@ -6274,8 +6327,8 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
// 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_LAYER_MIRRORING,
"Provided surface for layer mirroring on display %d is not present, so do not"
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;
@@ -6284,10 +6337,15 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
}
/**
* Returns {@code true} if this DisplayContent is currently layer mirroring.
* Returns {@code true} if this DisplayContent is currently recording.
*/
boolean isCurrentlyMirroring() {
return mTokenToMirror != null && mMirroredSurface != null;
boolean isCurrentlyRecording() {
return mContentRecordingSession != null && mRecordedSurface != null;
}
@VisibleForTesting
@Nullable ContentRecordingSession getContentRecordingSession() {
return mContentRecordingSession;
}
/** The entry for proceeding to handle {@link #mFixedRotationLaunchingApp}. */

View File

@@ -229,6 +229,7 @@ import android.util.TimeUtils;
import android.util.TypedValue;
import android.util.proto.ProtoOutputStream;
import android.view.Choreographer;
import android.view.ContentRecordingSession;
import android.view.Display;
import android.view.DisplayInfo;
import android.view.Gravity;
@@ -748,6 +749,9 @@ public class WindowManagerService extends IWindowManager.Stub
private InputTarget mFocusedInputTarget;
@VisibleForTesting
final ContentRecordingController mContentRecordingController = new ContentRecordingController();
@VisibleForTesting
final class SettingsObserver extends ContentObserver {
private final Uri mDisplayInversionEnabledUri =
@@ -2894,6 +2898,16 @@ public class WindowManagerService extends IWindowManager.Stub
}
}
/**
* Updates the current content mirroring session.
*/
@Override
public void setContentRecordingSession(@Nullable ContentRecordingSession incomingSession) {
synchronized (mGlobalLock) {
mContentRecordingController.setContentRecordingSessionLocked(incomingSession, this);
}
}
// TODO(multi-display): remove when no default display use case.
void prepareAppTransitionNone() {
if (!checkCallingPermission(MANAGE_APP_TOKENS, "prepareAppTransition()")) {

View File

@@ -0,0 +1,183 @@
/*
* 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.Display.DEFAULT_DISPLAY;
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.Mockito.atLeastOnce;
import static org.mockito.Mockito.never;
import android.os.Binder;
import android.os.IBinder;
import android.platform.test.annotations.Presubmit;
import android.view.ContentRecordingSession;
import androidx.test.filters.SmallTest;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
/**
* Tests for the {@link ContentRecordingController} class.
*
* Build/Install/Run:
* atest WmTests:ContentRecordingControllerTests
*/
@SmallTest
@Presubmit
@RunWith(WindowTestRunner.class)
public class ContentRecordingControllerTests extends WindowTestsBase {
private static final IBinder TEST_TOKEN = new RecordingTestToken();
private final ContentRecordingSession mDefaultSession =
ContentRecordingSession.createDisplaySession(
TEST_TOKEN);
@Before
public void setup() {
spyOn(mDisplayContent);
mDefaultSession.setDisplayId(DEFAULT_DISPLAY);
}
@Test
public void testGetContentRecordingSessionLocked() {
ContentRecordingController controller = new ContentRecordingController();
assertThat(controller.getContentRecordingSessionLocked()).isNull();
}
@Test
public void testSetContentRecordingSessionLocked_defaultSession() {
ContentRecordingController controller = new ContentRecordingController();
controller.setContentRecordingSessionLocked(mDefaultSession, mWm);
ContentRecordingSession resultingSession = controller.getContentRecordingSessionLocked();
assertThat(resultingSession).isEqualTo(mDefaultSession);
}
@Test
public void testSetContentRecordingSessionLocked_invalidDisplayId_notAccepted() {
ContentRecordingController controller = new ContentRecordingController();
// GIVEN an invalid display session (no display id is set).
ContentRecordingSession session = ContentRecordingSession.createDisplaySession(TEST_TOKEN);
// WHEN updating the session.
controller.setContentRecordingSessionLocked(session, mWm);
ContentRecordingSession resultingSession = controller.getContentRecordingSessionLocked();
// THEN the invalid session was not accepted.
assertThat(resultingSession).isNull();
}
@Test
public void testSetContentRecordingSessionLocked_invalidToken_notAccepted() {
ContentRecordingController controller = new ContentRecordingController();
// GIVEN an invalid display session (null token).
ContentRecordingSession session = ContentRecordingSession.createDisplaySession(null);
session.setDisplayId(DEFAULT_DISPLAY);
// WHEN updating the session.
controller.setContentRecordingSessionLocked(session, mWm);
ContentRecordingSession resultingSession = controller.getContentRecordingSessionLocked();
// THEN the invalid session was not accepted.
assertThat(resultingSession).isNull();
}
@Test
public void testSetContentRecordingSessionLocked_newDisplaySession_accepted() {
ContentRecordingController controller = new ContentRecordingController();
// GIVEN a valid display session.
ContentRecordingSession session = ContentRecordingSession.createDisplaySession(TEST_TOKEN);
session.setDisplayId(DEFAULT_DISPLAY);
// WHEN updating the session.
controller.setContentRecordingSessionLocked(session, mWm);
ContentRecordingSession resultingSession = controller.getContentRecordingSessionLocked();
// THEN the valid session was accepted.
assertThat(resultingSession).isEqualTo(session);
verify(mDisplayContent, atLeastOnce()).setContentRecordingSession(session);
}
@Test
public void testSetContentRecordingSessionLocked_updateCurrentDisplaySession_notAccepted() {
ContentRecordingController controller = new ContentRecordingController();
// GIVEN a valid display session already in place.
ContentRecordingSession session = ContentRecordingSession.createDisplaySession(TEST_TOKEN);
session.setDisplayId(DEFAULT_DISPLAY);
controller.setContentRecordingSessionLocked(session, mWm);
verify(mDisplayContent, atLeastOnce()).setContentRecordingSession(session);
// WHEN updating the session.
ContentRecordingSession sessionUpdate = ContentRecordingSession.createDisplaySession(
new RecordingTestToken());
sessionUpdate.setDisplayId(DEFAULT_DISPLAY);
controller.setContentRecordingSessionLocked(sessionUpdate, mWm);
ContentRecordingSession resultingSession = controller.getContentRecordingSessionLocked();
// THEN the session was not accepted.
assertThat(resultingSession).isEqualTo(session);
verify(mDisplayContent, never()).setContentRecordingSession(sessionUpdate);
}
@Test
public void testSetContentRecordingSessionLocked_disableCurrentDisplaySession_accepted() {
ContentRecordingController controller = new ContentRecordingController();
// GIVEN a valid display session already in place.
ContentRecordingSession session = ContentRecordingSession.createDisplaySession(TEST_TOKEN);
session.setDisplayId(DEFAULT_DISPLAY);
controller.setContentRecordingSessionLocked(session, mWm);
verify(mDisplayContent, atLeastOnce()).setContentRecordingSession(session);
// WHEN updating the session.
ContentRecordingSession sessionUpdate = null;
controller.setContentRecordingSessionLocked(sessionUpdate, mWm);
ContentRecordingSession resultingSession = controller.getContentRecordingSessionLocked();
// THEN the valid session was accepted.
assertThat(resultingSession).isEqualTo(sessionUpdate);
// Do not need to update the display content, since it will handle stopping the session
// via state change callbacks.
}
@Test
public void testSetContentRecordingSessionLocked_takeOverCurrentDisplaySession_accepted() {
ContentRecordingController controller = new ContentRecordingController();
// GIVEN a valid display session already in place.
ContentRecordingSession session = ContentRecordingSession.createDisplaySession(TEST_TOKEN);
session.setDisplayId(DEFAULT_DISPLAY);
controller.setContentRecordingSessionLocked(session, mWm);
verify(mDisplayContent, atLeastOnce()).setContentRecordingSession(session);
// WHEN updating the session.
final DisplayContent virtualDisplay = new TestDisplayContent.Builder(mAtm,
mDisplayInfo).build();
ContentRecordingSession sessionUpdate = ContentRecordingSession.createDisplaySession(
TEST_TOKEN);
sessionUpdate.setDisplayId(virtualDisplay.getDisplayId());
controller.setContentRecordingSessionLocked(sessionUpdate, mWm);
ContentRecordingSession resultingSession = controller.getContentRecordingSessionLocked();
// THEN the valid session was accepted.
assertThat(resultingSession).isEqualTo(sessionUpdate);
verify(virtualDisplay).setContentRecordingSession(sessionUpdate);
// THEN the recording was paused on the prior display.
verify(mDisplayContent).pauseRecording();
}
private static class RecordingTestToken extends Binder {
}
}

View File

@@ -119,6 +119,7 @@ import android.os.SystemClock;
import android.platform.test.annotations.Presubmit;
import android.util.ArraySet;
import android.util.DisplayMetrics;
import android.view.ContentRecordingSession;
import android.view.DisplayCutout;
import android.view.DisplayInfo;
import android.view.Gravity;
@@ -2301,8 +2302,18 @@ public class DisplayContentTests extends WindowTestsBase {
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.mTokenToMirror).isEqualTo(tokenToMirror);
assertThat(virtualDisplay.isCurrentlyRecording()).isTrue();
assertThat(virtualDisplay.getContentRecordingSession()).isEqualTo(session);
mockSession.finishMocking();
}
@@ -2329,9 +2340,14 @@ public class DisplayContentTests extends WindowTestsBase {
final DisplayContent virtualDisplay = new TestDisplayContent.Builder(mAtm,
mDisplayInfo).build();
// THEN mirroring is initiated for the default display's DisplayArea.
assertThat(virtualDisplay.mTokenToMirror).isEqualTo(tokenToMirror);
// 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),
@@ -2359,7 +2375,7 @@ public class DisplayContentTests extends WindowTestsBase {
// GIVEN MediaProjection has already initialized the WindowToken of the DisplayArea to
// mirror.
setUpDefaultTaskDisplayAreaWindowToken();
final IBinder tokenToMirror = setUpDefaultTaskDisplayAreaWindowToken();
// GIVEN SurfaceControl does not mirror a null surface.
Point surfaceSize = new Point(
@@ -2373,9 +2389,15 @@ public class DisplayContentTests extends WindowTestsBase {
// WHEN getting the DisplayContent for the new virtual display.
DisplayContent actualDC = mWm.mRoot.getDisplayContent(displayId);
ContentRecordingSession session = ContentRecordingSession.createDisplaySession(
tokenToMirror);
session.setDisplayId(displayId);
mWm.mContentRecordingController.setContentRecordingSessionLocked(session, mWm);
actualDC.updateRecording();
// THEN mirroring is not started, since a null surface indicates the VirtualDisplay is off.
assertThat(actualDC.mTokenToMirror).isNull();
assertThat(actualDC.isCurrentlyRecording()).isFalse();
assertThat(actualDC.getContentRecordingSession()).isEqualTo(session);
display.release();
mockSession.finishMocking();
@@ -2402,44 +2424,27 @@ public class DisplayContentTests extends WindowTestsBase {
// GIVEN a new VirtualDisplay with an associated surface.
final VirtualDisplay display = createVirtualDisplay(surfaceSize, new Surface());
final int displayId = display.getDisplay().getDisplayId();
// GIVEN a session for this display.
ContentRecordingSession session = ContentRecordingSession.createDisplaySession(
tokenToMirror);
session.setDisplayId(displayId);
mWm.setContentRecordingSession(session);
mWm.mRoot.onDisplayAdded(displayId);
// WHEN getting the DisplayContent for the new virtual display.
DisplayContent actualDC = mWm.mRoot.getDisplayContent(displayId);
actualDC.updateRecording();
// THEN mirroring is initiated for the default display's DisplayArea.
assertThat(actualDC.mTokenToMirror).isEqualTo(tokenToMirror);
assertThat(actualDC.isCurrentlyRecording()).isTrue();
assertThat(actualDC.getContentRecordingSession()).isEqualTo(session);
display.release();
mockSession.finishMocking();
}
@Test
public void testKeepClearAreasMultipleWindows() {
final WindowState w1 = createWindow(null, TYPE_NAVIGATION_BAR, mDisplayContent, "w1");
final Rect rect1 = new Rect(0, 0, 10, 10);
w1.setKeepClearAreas(Arrays.asList(rect1), Collections.emptyList());
final WindowState w2 = createWindow(null, TYPE_NOTIFICATION_SHADE, mDisplayContent, "w2");
final Rect rect2 = new Rect(10, 10, 20, 20);
w2.setKeepClearAreas(Arrays.asList(rect2), Collections.emptyList());
// No keep clear areas on display, because the windows are not visible
assertEquals(Arrays.asList(), mDisplayContent.getKeepClearAreas());
makeWindowVisible(w1);
// The returned keep-clear areas contain the areas just from the visible window
assertEquals(new ArraySet(Arrays.asList(rect1)),
new ArraySet(mDisplayContent.getKeepClearAreas()));
makeWindowVisible(w1, w2);
// The returned keep-clear areas contain the areas from all visible windows
assertEquals(new ArraySet(Arrays.asList(rect1, rect2)),
new ArraySet(mDisplayContent.getKeepClearAreas()));
}
private class TestToken extends Binder {
private static class MirroringTestToken extends Binder {
}
/**
@@ -2449,10 +2454,7 @@ public class DisplayContentTests extends WindowTestsBase {
private IBinder setUpDefaultTaskDisplayAreaWindowToken() {
// GIVEN MediaProjection has already initialized the WindowToken of the DisplayArea to
// mirror.
final IBinder tokenToMirror = new TestToken();
doReturn(tokenToMirror).when(mWm.mDisplayManagerInternal).getWindowTokenClientToMirror(
anyInt());
final IBinder tokenToMirror = new MirroringTestToken();
// GIVEN the default task display area is represented by the WindowToken.
spyOn(mWm.mWindowContextListenerController);
doReturn(mDefaultDisplay.getDefaultTaskDisplayArea()).when(
@@ -2481,6 +2483,31 @@ public class DisplayContentTests extends WindowTestsBase {
DisplayMetrics.DENSITY_140, surface, VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR);
}
@Test
public void testKeepClearAreasMultipleWindows() {
final WindowState w1 = createWindow(null, TYPE_NAVIGATION_BAR, mDisplayContent, "w1");
final Rect rect1 = new Rect(0, 0, 10, 10);
w1.setKeepClearAreas(Arrays.asList(rect1), Collections.emptyList());
final WindowState w2 = createWindow(null, TYPE_NOTIFICATION_SHADE, mDisplayContent, "w2");
final Rect rect2 = new Rect(10, 10, 20, 20);
w2.setKeepClearAreas(Arrays.asList(rect2), Collections.emptyList());
// No keep clear areas on display, because the windows are not visible
assertEquals(Arrays.asList(), mDisplayContent.getKeepClearAreas());
makeWindowVisible(w1);
// The returned keep-clear areas contain the areas just from the visible window
assertEquals(new ArraySet(Arrays.asList(rect1)),
new ArraySet(mDisplayContent.getKeepClearAreas()));
makeWindowVisible(w1, w2);
// The returned keep-clear areas contain the areas from all visible windows
assertEquals(new ArraySet(Arrays.asList(rect1, rect2)),
new ArraySet(mDisplayContent.getKeepClearAreas()));
}
private void removeRootTaskTests(Runnable runnable) {
final TaskDisplayArea taskDisplayArea = mRootWindowContainer.getDefaultTaskDisplayArea();
final Task rootTask1 = taskDisplayArea.createRootTask(WINDOWING_MODE_FULLSCREEN,