summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/java/android/hardware/display/DisplayManagerInternal.java18
-rw-r--r--core/java/android/hardware/display/VirtualDisplayConfig.java45
-rw-r--r--core/java/android/view/ContentRecordingSession.aidl19
-rw-r--r--core/java/android/view/ContentRecordingSession.java447
-rw-r--r--core/java/android/view/IWindowManager.aidl12
-rw-r--r--core/java/android/view/OWNERS2
-rw-r--r--core/java/com/android/internal/protolog/ProtoLogGroup.java2
-rw-r--r--core/tests/coretests/src/android/view/ContentRecordingSessionTest.java117
-rw-r--r--core/tests/coretests/src/android/view/OWNERS1
-rw-r--r--data/etc/services.core.protolog.json144
-rw-r--r--media/java/android/media/projection/MediaProjection.java88
-rw-r--r--media/java/android/media/projection/OWNERS1
-rw-r--r--services/core/java/com/android/server/display/DisplayDevice.java21
-rw-r--r--services/core/java/com/android/server/display/DisplayManagerService.java23
-rw-r--r--services/core/java/com/android/server/display/VirtualDisplayAdapter.java18
-rw-r--r--services/core/java/com/android/server/media/projection/OWNERS1
-rw-r--r--services/core/java/com/android/server/wm/ContentRecordingController.java94
-rw-r--r--services/core/java/com/android/server/wm/DisplayContent.java278
-rw-r--r--services/core/java/com/android/server/wm/WindowManagerService.java14
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/ContentRecordingControllerTests.java183
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java99
21 files changed, 1305 insertions, 322 deletions
diff --git a/core/java/android/hardware/display/DisplayManagerInternal.java b/core/java/android/hardware/display/DisplayManagerInternal.java
index 83e1061d8143..be482c9146c9 100644
--- a/core/java/android/hardware/display/DisplayManagerInternal.java
+++ b/core/java/android/hardware/display/DisplayManagerInternal.java
@@ -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);
diff --git a/core/java/android/hardware/display/VirtualDisplayConfig.java b/core/java/android/hardware/display/VirtualDisplayConfig.java
index e292394c36ff..b76b98d3c37a 100644
--- a/core/java/android/hardware/display/VirtualDisplayConfig.java
+++ b/core/java/android/hardware/display/VirtualDisplayConfig.java
@@ -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() {}
diff --git a/core/java/android/view/ContentRecordingSession.aidl b/core/java/android/view/ContentRecordingSession.aidl
new file mode 100644
index 000000000000..ddbc175ca7ae
--- /dev/null
+++ b/core/java/android/view/ContentRecordingSession.aidl
@@ -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;
diff --git a/core/java/android/view/ContentRecordingSession.java b/core/java/android/view/ContentRecordingSession.java
new file mode 100644
index 000000000000..db4ec1155e64
--- /dev/null
+++ b/core/java/android/view/ContentRecordingSession.java
@@ -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
+
+}
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index f8a1b45a1c75..3f29e3f3078e 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -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;
@@ -879,6 +880,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.
*
* @param listener the listener to be registered
diff --git a/core/java/android/view/OWNERS b/core/java/android/view/OWNERS
index e313388affb6..3e2849012b2b 100644
--- a/core/java/android/view/OWNERS
+++ b/core/java/android/view/OWNERS
@@ -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
diff --git a/core/java/com/android/internal/protolog/ProtoLogGroup.java b/core/java/com/android/internal/protolog/ProtoLogGroup.java
index def598ca8724..45c6d5f10a5a 100644
--- a/core/java/com/android/internal/protolog/ProtoLogGroup.java
+++ b/core/java/com/android/internal/protolog/ProtoLogGroup.java
@@ -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,
diff --git a/core/tests/coretests/src/android/view/ContentRecordingSessionTest.java b/core/tests/coretests/src/android/view/ContentRecordingSessionTest.java
new file mode 100644
index 000000000000..df96a7d4568a
--- /dev/null
+++ b/core/tests/coretests/src/android/view/ContentRecordingSessionTest.java
@@ -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);
+ }
+}
diff --git a/core/tests/coretests/src/android/view/OWNERS b/core/tests/coretests/src/android/view/OWNERS
index 10f6f1fd22f1..a142e27a1ab0 100644
--- a/core/tests/coretests/src/android/view/OWNERS
+++ b/core/tests/coretests/src/android/view/OWNERS
@@ -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
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index ed8986993ab0..3d505315fc95 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -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"
},
diff --git a/media/java/android/media/projection/MediaProjection.java b/media/java/android/media/projection/MediaProjection.java
index 5259c4f07639..4dde5e8d39a2 100644
--- a/media/java/android/media/projection/MediaProjection.java
+++ b/media/java/android/media/projection/MediaProjection.java
@@ -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,38 +143,16 @@ 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);
}
/**
diff --git a/media/java/android/media/projection/OWNERS b/media/java/android/media/projection/OWNERS
index 7e7335d68d3b..9ca391013aa3 100644
--- a/media/java/android/media/projection/OWNERS
+++ b/media/java/android/media/projection/OWNERS
@@ -1 +1,2 @@
michaelwr@google.com
+santoscordon@google.com
diff --git a/services/core/java/com/android/server/display/DisplayDevice.java b/services/core/java/com/android/server/display/DisplayDevice.java
index 5de162ce711c..311fa7c16958 100644
--- a/services/core/java/com/android/server/display/DisplayDevice.java
+++ b/services/core/java/com/android/server/display/DisplayDevice.java
@@ -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() {
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 66e9da040102..5d1ad2a377dd 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -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);
}
}
}
diff --git a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
index 797c3fb44338..ea313743d28d 100644
--- a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
@@ -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);
}
diff --git a/services/core/java/com/android/server/media/projection/OWNERS b/services/core/java/com/android/server/media/projection/OWNERS
index 7e7335d68d3b..9ca391013aa3 100644
--- a/services/core/java/com/android/server/media/projection/OWNERS
+++ b/services/core/java/com/android/server/media/projection/OWNERS
@@ -1 +1,2 @@
michaelwr@google.com
+santoscordon@google.com
diff --git a/services/core/java/com/android/server/wm/ContentRecordingController.java b/services/core/java/com/android/server/wm/ContentRecordingController.java
new file mode 100644
index 000000000000..fca4942d4b79
--- /dev/null
+++ b/services/core/java/com/android/server/wm/ContentRecordingController.java
@@ -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;
+ }
+}
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 77787e1e7530..eb2c01adc79e 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -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}. */
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 03e21405ff03..fd69f54a7a10 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -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;
@@ -749,6 +750,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 =
Settings.Secure.getUriFor(Settings.Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED);
@@ -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()")) {
diff --git a/services/tests/wmtests/src/com/android/server/wm/ContentRecordingControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/ContentRecordingControllerTests.java
new file mode 100644
index 000000000000..7698033d8df2
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/ContentRecordingControllerTests.java
@@ -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 {
+ }
+}
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
index 925f4f5a4130..b7be932c0fd3 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -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,