summaryrefslogtreecommitdiff
path: root/media
diff options
context:
space:
mode:
author Vadim Caen <caen@google.com> 2025-03-21 12:15:43 -0700
committer Android (Google) Code Review <android-gerrit@google.com> 2025-03-21 12:15:43 -0700
commit7543cedb9f9abd61475e055b2db21615005c9c26 (patch)
tree72360e47c67c09519694f8ebd28c1eadf060039d /media
parenta1a7381a9c4d65c18fd29f44bda9f05a7d6eaefb (diff)
parenta97ff8e4a2102603d8c41a5989e8eb91e69a4fc1 (diff)
Merge changes from topic "media-proj-builder" into main
* changes: Introduce MediaProjectionAppContent Builder pattern for MediaProjectionConfig
Diffstat (limited to 'media')
-rw-r--r--media/java/android/media/projection/MediaProjectionAppContent.aidl19
-rw-r--r--media/java/android/media/projection/MediaProjectionAppContent.java123
-rw-r--r--media/java/android/media/projection/MediaProjectionConfig.java354
-rw-r--r--media/java/android/media/projection/MediaProjectionManager.java10
-rw-r--r--media/java/android/media/projection/TEST_MAPPING2
-rw-r--r--media/tests/projection/Android.bp1
-rw-r--r--media/tests/projection/src/android/media/projection/MediaProjectionAppContentTest.java86
-rw-r--r--media/tests/projection/src/android/media/projection/MediaProjectionConfigTest.java32
8 files changed, 597 insertions, 30 deletions
diff --git a/media/java/android/media/projection/MediaProjectionAppContent.aidl b/media/java/android/media/projection/MediaProjectionAppContent.aidl
new file mode 100644
index 000000000000..6ead69b9fdc6
--- /dev/null
+++ b/media/java/android/media/projection/MediaProjectionAppContent.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2025 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.media.projection;
+
+parcelable MediaProjectionAppContent; \ No newline at end of file
diff --git a/media/java/android/media/projection/MediaProjectionAppContent.java b/media/java/android/media/projection/MediaProjectionAppContent.java
new file mode 100644
index 000000000000..da0bdc191c0c
--- /dev/null
+++ b/media/java/android/media/projection/MediaProjectionAppContent.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright 2025 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.media.projection;
+
+import android.annotation.FlaggedApi;
+import android.graphics.Bitmap;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import androidx.annotation.NonNull;
+
+import java.util.Objects;
+
+/**
+ * Holds information about content an app can share via the MediaProjection APIs.
+ * <p>
+ * An application requesting a {@link MediaProjection session} can add its own content in the
+ * list of available content along with the whole screen or a single application.
+ * <p>
+ * Each instance of {@link MediaProjectionAppContent} contains an id that is used to identify the
+ * content chosen by the user back to the advertising application, thus the meaning of the id is
+ * only relevant to that application.
+ */
+@FlaggedApi(com.android.media.projection.flags.Flags.FLAG_APP_CONTENT_SHARING)
+public final class MediaProjectionAppContent implements Parcelable {
+
+ private final Bitmap mThumbnail;
+ private final CharSequence mTitle;
+ private final int mId;
+
+ /**
+ * Constructor to pass a thumbnail, title and id.
+ *
+ * @param thumbnail The thumbnail representing this content to be shown to the user.
+ * @param title A user visible string representing the title of this content.
+ * @param id An arbitrary int defined by the advertising application to be fed back once
+ * the user made their choice.
+ */
+ public MediaProjectionAppContent(@NonNull Bitmap thumbnail, @NonNull CharSequence title,
+ int id) {
+ mThumbnail = Objects.requireNonNull(thumbnail, "thumbnail can't be null").asShared();
+ mTitle = Objects.requireNonNull(title, "title can't be null");
+ mId = id;
+ }
+
+ /**
+ * Returns thumbnail representing this content to be shown to the user.
+ *
+ * @hide
+ */
+ @NonNull
+ public Bitmap getThumbnail() {
+ return mThumbnail;
+ }
+
+ /**
+ * Returns user visible string representing the title of this content.
+ *
+ * @hide
+ */
+ @NonNull
+ public CharSequence getTitle() {
+ return mTitle;
+ }
+
+ /**
+ * Returns the arbitrary int defined by the advertising application to be fed back once
+ * the user made their choice.
+ *
+ * @hide
+ */
+ public int getId() {
+ return mId;
+ }
+
+ private MediaProjectionAppContent(Parcel in) {
+ mThumbnail = in.readParcelable(this.getClass().getClassLoader(), Bitmap.class);
+ mTitle = in.readCharSequence();
+ mId = in.readInt();
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeParcelable(mThumbnail, flags);
+ dest.writeCharSequence(mTitle);
+ dest.writeInt(mId);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @NonNull
+ public static final Creator<MediaProjectionAppContent> CREATOR =
+ new Creator<>() {
+ @NonNull
+ @Override
+ public MediaProjectionAppContent createFromParcel(@NonNull Parcel in) {
+ return new MediaProjectionAppContent(in);
+ }
+
+ @NonNull
+ @Override
+ public MediaProjectionAppContent[] newArray(int size) {
+ return new MediaProjectionAppContent[size];
+ }
+ };
+}
diff --git a/media/java/android/media/projection/MediaProjectionConfig.java b/media/java/android/media/projection/MediaProjectionConfig.java
index 598b534e81ca..cd674e9f2ad1 100644
--- a/media/java/android/media/projection/MediaProjectionConfig.java
+++ b/media/java/android/media/projection/MediaProjectionConfig.java
@@ -20,23 +20,56 @@ import static android.view.Display.DEFAULT_DISPLAY;
import static java.lang.annotation.RetentionPolicy.SOURCE;
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SuppressLint;
import android.os.Parcelable;
-import com.android.internal.util.AnnotationValidations;
+import com.android.media.projection.flags.Flags;
import java.lang.annotation.Retention;
+import java.util.Arrays;
+import java.util.Objects;
/**
* Configure the {@link MediaProjection} session requested from
* {@link MediaProjectionManager#createScreenCaptureIntent(MediaProjectionConfig)}.
+ * <p>
+ * This configuration should be used to provide the user with options for choosing the content to
+ * be shared with the requesting application.
*/
public final class MediaProjectionConfig implements Parcelable {
/**
+ * Bitmask for setting whether this configuration is for projecting the whole display.
+ */
+ @FlaggedApi(Flags.FLAG_APP_CONTENT_SHARING)
+ public static final int PROJECTION_SOURCE_DISPLAY = 1 << 1;
+
+ /**
+ * Bitmask for setting whether this configuration is for projecting the a custom region display.
+ *
+ * @hide
+ */
+ public static final int PROJECTION_SOURCE_DISPLAY_REGION = 1 << 2;
+
+ /**
+ * Bitmask for setting whether this configuration is for projecting the a single application.
+ */
+ @FlaggedApi(Flags.FLAG_APP_CONTENT_SHARING)
+ public static final int PROJECTION_SOURCE_APP = 1 << 3;
+
+ /**
+ * Bitmask for setting whether this configuration is for projecting the content provided by an
+ * application.
+ */
+ @FlaggedApi(com.android.media.projection.flags.Flags.FLAG_APP_CONTENT_SHARING)
+ public static final int PROJECTION_SOURCE_APP_CONTENT = 1 << 4;
+
+ /**
* The user, rather than the host app, determines which region of the display to capture.
*
* @hide
@@ -44,39 +77,109 @@ public final class MediaProjectionConfig implements Parcelable {
public static final int CAPTURE_REGION_USER_CHOICE = 0;
/**
+ * @hide
+ */
+ public static final int DEFAULT_PROJECTION_SOURCES =
+ PROJECTION_SOURCE_DISPLAY | PROJECTION_SOURCE_APP;
+
+ /**
* The host app specifies a particular display to capture.
*
* @hide
*/
public static final int CAPTURE_REGION_FIXED_DISPLAY = 1;
+ private static final int[] PROJECTION_SOURCES =
+ new int[]{PROJECTION_SOURCE_DISPLAY, PROJECTION_SOURCE_DISPLAY_REGION,
+ PROJECTION_SOURCE_APP,
+ PROJECTION_SOURCE_APP_CONTENT};
+
+ private static final String[] PROJECTION_SOURCES_STRING =
+ new String[]{"PROJECTION_SOURCE_DISPLAY", "PROJECTION_SOURCE_DISPLAY_REGION",
+ "PROJECTION_SOURCE_APP", "PROJECTION_SOURCE_APP_CONTENT"};
+
+ private static final int VALID_PROJECTION_SOURCES = createValidSourcesMask();
+
+ private final int mInitialSelection;
+
/** @hide */
@IntDef(prefix = "CAPTURE_REGION_", value = {CAPTURE_REGION_USER_CHOICE,
CAPTURE_REGION_FIXED_DISPLAY})
@Retention(SOURCE)
+ @Deprecated // Remove when FLAG_APP_CONTENT_SHARING is removed
public @interface CaptureRegion {
}
+ /** @hide */
+ @IntDef(flag = true, prefix = "PROJECTION_SOURCE_", value = {PROJECTION_SOURCE_DISPLAY,
+ PROJECTION_SOURCE_DISPLAY_REGION, PROJECTION_SOURCE_APP, PROJECTION_SOURCE_APP_CONTENT})
+ @Retention(SOURCE)
+ public @interface MediaProjectionSource {
+ }
+
/**
- * The particular display to capture. Only used when {@link #getRegionToCapture()} is
- * {@link #CAPTURE_REGION_FIXED_DISPLAY}; ignored otherwise.
+ * The particular display to capture. Only used when {@link #PROJECTION_SOURCE_DISPLAY} is set,
+ * ignored otherwise.
* <p>
* Only supports values of {@link android.view.Display#DEFAULT_DISPLAY}.
*/
@IntRange(from = DEFAULT_DISPLAY, to = DEFAULT_DISPLAY)
- private int mDisplayToCapture;
+ private final int mDisplayToCapture;
/**
* The region to capture. Defaults to the user's choice.
*/
@CaptureRegion
+ @Deprecated // Remove when FLAG_APP_CONTENT_SHARING is removed
private int mRegionToCapture;
/**
+ * The region to capture. Defaults to the user's choice.
+ */
+ @MediaProjectionSource
+ private final int mProjectionSources;
+
+ /**
+ * @see #getRequesterHint()
+ */
+ @Nullable
+ private final String mRequesterHint;
+
+ /**
* Customized instance, with region set to the provided value.
+ * @deprecated To be removed FLAG_APP_CONTENT_SHARING is removed
*/
+ @Deprecated // Remove when FLAG_APP_CONTENT_SHARING is removed
private MediaProjectionConfig(@CaptureRegion int captureRegion) {
+ if (Flags.appContentSharing()) {
+ throw new UnsupportedOperationException(
+ "Flag FLAG_APP_CONTENT_SHARING enabled. This method must not be called.");
+ }
mRegionToCapture = captureRegion;
+ mDisplayToCapture = DEFAULT_DISPLAY;
+
+ mRequesterHint = null;
+ mInitialSelection = -1;
+ mProjectionSources = -1;
+ }
+
+ /**
+ * Customized instance, with region set to the provided value.
+ */
+ private MediaProjectionConfig(@MediaProjectionSource int projectionSource,
+ @Nullable String requesterHint, int displayId, int initialSelection) {
+ if (!Flags.appContentSharing()) {
+ throw new UnsupportedOperationException(
+ "Flag FLAG_APP_CONTENT_SHARING disabled. This method must not be called");
+ }
+ if (projectionSource == 0) {
+ mProjectionSources = DEFAULT_PROJECTION_SOURCES;
+ } else {
+ mProjectionSources = projectionSource;
+ }
+ mRequesterHint = requesterHint;
+ mDisplayToCapture = displayId;
+ mInitialSelection = initialSelection;
}
/**
@@ -84,16 +187,17 @@ public final class MediaProjectionConfig implements Parcelable {
*/
@NonNull
public static MediaProjectionConfig createConfigForDefaultDisplay() {
- MediaProjectionConfig config = new MediaProjectionConfig(CAPTURE_REGION_FIXED_DISPLAY);
- config.mDisplayToCapture = DEFAULT_DISPLAY;
- return config;
+ if (Flags.appContentSharing()) {
+ return new Builder().setSourceEnabled(PROJECTION_SOURCE_DISPLAY, true).build();
+ } else {
+ return new MediaProjectionConfig(CAPTURE_REGION_FIXED_DISPLAY);
+ }
}
/**
* Returns an instance which allows the user to decide which region is captured. The consent
* dialog presents the user with all possible options. If the user selects display capture,
* then only the {@link android.view.Display#DEFAULT_DISPLAY} is supported.
- *
* <p>
* When passed in to
* {@link MediaProjectionManager#createScreenCaptureIntent(MediaProjectionConfig)}, the consent
@@ -103,13 +207,18 @@ public final class MediaProjectionConfig implements Parcelable {
*/
@NonNull
public static MediaProjectionConfig createConfigForUserChoice() {
- return new MediaProjectionConfig(CAPTURE_REGION_USER_CHOICE);
+ if (Flags.appContentSharing()) {
+ return new MediaProjectionConfig.Builder().build();
+ } else {
+ return new MediaProjectionConfig(CAPTURE_REGION_USER_CHOICE);
+ }
}
/**
* Returns string representation of the captured region.
*/
@NonNull
+ @Deprecated // Remove when FLAG_APP_CONTENT_SHARING is removed
private static String captureRegionToString(int value) {
return switch (value) {
case CAPTURE_REGION_USER_CHOICE -> "CAPTURE_REGION_USERS_CHOICE";
@@ -118,16 +227,42 @@ public final class MediaProjectionConfig implements Parcelable {
};
}
+ /**
+ * Returns string representation of the captured region.
+ */
+ @NonNull
+ private static String projectionSourceToString(int value) {
+ StringBuilder stringBuilder = new StringBuilder();
+ for (int i = 0; i < PROJECTION_SOURCES.length; i++) {
+ if ((value & PROJECTION_SOURCES[i]) > 0) {
+ stringBuilder.append(PROJECTION_SOURCES_STRING[i]);
+ stringBuilder.append(" ");
+ value &= ~PROJECTION_SOURCES[i];
+ }
+ }
+ if (value > 0) {
+ stringBuilder.append("Unknown projection sources: ");
+ stringBuilder.append(Integer.toHexString(value));
+ }
+ return stringBuilder.toString();
+ }
+
@Override
public String toString() {
- return "MediaProjectionConfig { " + "displayToCapture = " + mDisplayToCapture + ", "
- + "regionToCapture = " + captureRegionToString(mRegionToCapture) + " }";
+ if (Flags.appContentSharing()) {
+ return ("MediaProjectionConfig{mInitialSelection=%d, mDisplayToCapture=%d, "
+ + "mProjectionSource=%s, mRequesterHint='%s'}").formatted(mInitialSelection,
+ mDisplayToCapture, projectionSourceToString(mProjectionSources),
+ mRequesterHint);
+ } else {
+ return "MediaProjectionConfig { " + "displayToCapture = " + mDisplayToCapture + ", "
+ + "regionToCapture = " + captureRegionToString(mRegionToCapture) + " }";
+ }
}
-
/**
- * The particular display to capture. Only used when {@link #getRegionToCapture()} is
- * {@link #CAPTURE_REGION_FIXED_DISPLAY}; ignored otherwise.
+ * The particular display to capture. Only used when {@link #PROJECTION_SOURCE_DISPLAY} is
+ * set; ignored otherwise.
* <p>
* Only supports values of {@link android.view.Display#DEFAULT_DISPLAY}.
*
@@ -146,27 +281,57 @@ public final class MediaProjectionConfig implements Parcelable {
return mRegionToCapture;
}
+ /**
+ * A bitmask representing of requested projection sources.
+ * <p>
+ * The system supports different kind of media projection session. Although the user is
+ * picking the target content, the requesting application can configure the choices displayed
+ * to the user.
+ */
+ @FlaggedApi(Flags.FLAG_APP_CONTENT_SHARING)
+ public @MediaProjectionSource int getProjectionSources() {
+ return mProjectionSources;
+ }
+
@Override
public boolean equals(@Nullable Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
MediaProjectionConfig that = (MediaProjectionConfig) o;
- return mDisplayToCapture == that.mDisplayToCapture
- && mRegionToCapture == that.mRegionToCapture;
+ if (Flags.appContentSharing()) {
+ return mDisplayToCapture == that.mDisplayToCapture
+ && mProjectionSources == that.mProjectionSources
+ && mInitialSelection == that.mInitialSelection
+ && Objects.equals(mRequesterHint, that.mRequesterHint);
+ } else {
+ return mDisplayToCapture == that.mDisplayToCapture
+ && mRegionToCapture == that.mRegionToCapture;
+ }
}
@Override
public int hashCode() {
int _hash = 1;
- _hash = 31 * _hash + mDisplayToCapture;
- _hash = 31 * _hash + mRegionToCapture;
+ if (Flags.appContentSharing()) {
+ return Objects.hash(mDisplayToCapture, mProjectionSources, mInitialSelection,
+ mRequesterHint);
+ } else {
+ _hash = 31 * _hash + mDisplayToCapture;
+ _hash = 31 * _hash + mRegionToCapture;
+ }
return _hash;
}
@Override
public void writeToParcel(@NonNull android.os.Parcel dest, int flags) {
dest.writeInt(mDisplayToCapture);
- dest.writeInt(mRegionToCapture);
+ if (Flags.appContentSharing()) {
+ dest.writeInt(mProjectionSources);
+ dest.writeString(mRequesterHint);
+ dest.writeInt(mInitialSelection);
+ } else {
+ dest.writeInt(mRegionToCapture);
+ }
}
@Override
@@ -176,12 +341,17 @@ public final class MediaProjectionConfig implements Parcelable {
/** @hide */
/* package-private */ MediaProjectionConfig(@NonNull android.os.Parcel in) {
- int displayToCapture = in.readInt();
- int regionToCapture = in.readInt();
-
- mDisplayToCapture = displayToCapture;
- mRegionToCapture = regionToCapture;
- AnnotationValidations.validate(CaptureRegion.class, null, mRegionToCapture);
+ mDisplayToCapture = in.readInt();
+ if (Flags.appContentSharing()) {
+ mProjectionSources = in.readInt();
+ mRequesterHint = in.readString();
+ mInitialSelection = in.readInt();
+ } else {
+ mRegionToCapture = in.readInt();
+ mProjectionSources = -1;
+ mRequesterHint = null;
+ mInitialSelection = -1;
+ }
}
public static final @NonNull Parcelable.Creator<MediaProjectionConfig> CREATOR =
@@ -196,4 +366,138 @@ public final class MediaProjectionConfig implements Parcelable {
return new MediaProjectionConfig(in);
}
};
+
+ /**
+ * Returns true if the provided source should be enabled.
+ *
+ * @param projectionSource projection source integer to check for. The parameter can also be a
+ * bitmask of multiple sources.
+ */
+ @FlaggedApi(Flags.FLAG_APP_CONTENT_SHARING)
+ public boolean isSourceEnabled(@MediaProjectionSource int projectionSource) {
+ return (mProjectionSources & projectionSource) > 0;
+ }
+
+ /**
+ * Returns a bit mask of one, and only one, of the projection type flag.
+ */
+ @FlaggedApi(Flags.FLAG_APP_CONTENT_SHARING)
+ @MediaProjectionSource
+ public int getInitiallySelectedSource() {
+ return mInitialSelection;
+ }
+
+ /**
+ * A hint set by the requesting app indicating who the requester of this {@link MediaProjection}
+ * session is.
+ * <p>
+ * The UI component prompting the user for the permission to start the session can use
+ * this hint to provide more information about the origin of the request (e.g. a browser
+ * tab title, a meeting id if sharing to a video conferencing app, a player name if
+ * sharing the screen within a game).
+ *
+ * @return the hint to be displayed if set, null otherwise.
+ */
+ @FlaggedApi(Flags.FLAG_APP_CONTENT_SHARING)
+ @Nullable
+ public CharSequence getRequesterHint() {
+ return mRequesterHint;
+ }
+
+ private static int createValidSourcesMask() {
+ int validSources = 0;
+ for (int projectionSource : PROJECTION_SOURCES) {
+ validSources |= projectionSource;
+ }
+ return validSources;
+ }
+
+ @FlaggedApi(Flags.FLAG_APP_CONTENT_SHARING)
+ public static final class Builder {
+ private int mOptions = 0;
+ private String mRequesterHint = null;
+
+ @MediaProjectionSource
+ private int mInitialSelection;
+
+ public Builder() {
+ if (!Flags.appContentSharing()) {
+ throw new UnsupportedOperationException("Flag FLAG_APP_CONTENT_SHARING disabled");
+ }
+ }
+
+ /**
+ * Indicates which projection source the UI component should display to the user
+ * first. Calling this method without enabling the respective choice will have no effect.
+ *
+ * @return instance of this {@link Builder}.
+ * @see #setSourceEnabled(int, boolean)
+ */
+ @NonNull
+ public Builder setInitiallySelectedSource(@MediaProjectionSource int projectionSource) {
+ for (int source : PROJECTION_SOURCES) {
+ if (projectionSource == source) {
+ mInitialSelection = projectionSource;
+ return this;
+ }
+ }
+ throw new IllegalArgumentException(
+ ("projectionSource is no a valid projection source. projectionSource must be "
+ + "one of %s but was %s")
+ .formatted(Arrays.toString(PROJECTION_SOURCES_STRING),
+ projectionSourceToString(projectionSource)));
+ }
+
+ /**
+ * Let the requesting app indicate who the requester of this {@link MediaProjection}
+ * session is..
+ * <p>
+ * The UI component prompting the user for the permission to start the session can use
+ * this hint to provide more information about the origin of the request (e.g. a browser
+ * tab title, a meeting id if sharing to a video conferencing app, a player name if
+ * sharing the screen within a game).
+ * <p>
+ * Note that setting this won't hide or change the name of the application
+ * requesting the session.
+ *
+ * @return instance of this {@link Builder}.
+ */
+ @NonNull
+ public Builder setRequesterHint(@Nullable String requesterHint) {
+ mRequesterHint = requesterHint;
+ return this;
+ }
+
+ /**
+ * Set whether the UI component requesting the user permission to share their screen
+ * should display an option to share the specified source
+ *
+ * @param source the projection source to enable or disable
+ * @param enabled true to enable the source, false otherwise
+ * @return this instance for chaining.
+ * @throws IllegalArgumentException if the source is not one of the valid sources.
+ */
+ @NonNull
+ @SuppressLint("MissingGetterMatchingBuilder") // isSourceEnabled is defined
+ public Builder setSourceEnabled(@MediaProjectionSource int source, boolean enabled) {
+ if ((source & VALID_PROJECTION_SOURCES) == 0) {
+ throw new IllegalArgumentException(
+ ("source is no a valid projection source. source must be "
+ + "any of %s but was %s")
+ .formatted(Arrays.toString(PROJECTION_SOURCES_STRING),
+ projectionSourceToString(source)));
+ }
+ mOptions = enabled ? mOptions | source : mOptions & ~source;
+ return this;
+ }
+
+ /**
+ * Builds a new immutable instance of {@link MediaProjectionConfig}
+ */
+ @NonNull
+ public MediaProjectionConfig build() {
+ return new MediaProjectionConfig(mOptions, mRequesterHint, DEFAULT_DISPLAY,
+ mInitialSelection);
+ }
+ }
}
diff --git a/media/java/android/media/projection/MediaProjectionManager.java b/media/java/android/media/projection/MediaProjectionManager.java
index 9036bf385d96..4a5392d3c0c3 100644
--- a/media/java/android/media/projection/MediaProjectionManager.java
+++ b/media/java/android/media/projection/MediaProjectionManager.java
@@ -29,6 +29,7 @@ import android.compat.annotation.Overridable;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
+import android.hardware.display.VirtualDisplay;
import android.os.Handler;
import android.os.IBinder;
import android.os.RemoteException;
@@ -78,9 +79,12 @@ public final class MediaProjectionManager {
private static final String TAG = "MediaProjectionManager";
/**
- * This change id ensures that users are presented with a choice of capturing a single app
- * or the entire screen when initiating a MediaProjection session, overriding the usage of
- * MediaProjectionConfig#createConfigForDefaultDisplay.
+ * If enabled, this change id ensures that users are presented with a choice of capturing a
+ * single app and the entire screen when initiating a MediaProjection session, overriding the
+ * usage of MediaProjectionConfig#createConfigForDefaultDisplay.
+ * <p>
+ *
+ * <a href=" https://developer.android.com/guide/practices/device-compatibility-mode#override_disable_media_projection_single_app_option">More info</a>
*
* @hide
*/
diff --git a/media/java/android/media/projection/TEST_MAPPING b/media/java/android/media/projection/TEST_MAPPING
index ea62287b7411..62e776b822d2 100644
--- a/media/java/android/media/projection/TEST_MAPPING
+++ b/media/java/android/media/projection/TEST_MAPPING
@@ -4,4 +4,4 @@
"path": "frameworks/base/services/core/java/com/android/server/media/projection"
}
]
-} \ No newline at end of file
+}
diff --git a/media/tests/projection/Android.bp b/media/tests/projection/Android.bp
index 0b02d3cb4250..0b4b7dbbca1f 100644
--- a/media/tests/projection/Android.bp
+++ b/media/tests/projection/Android.bp
@@ -26,6 +26,7 @@ android_test {
"androidx.test.runner",
"androidx.test.rules",
"androidx.test.ext.junit",
+ "flag-junit",
"frameworks-base-testutils",
"mockito-target-extended-minus-junit4",
"platform-test-annotations",
diff --git a/media/tests/projection/src/android/media/projection/MediaProjectionAppContentTest.java b/media/tests/projection/src/android/media/projection/MediaProjectionAppContentTest.java
new file mode 100644
index 000000000000..7e167c63a2a2
--- /dev/null
+++ b/media/tests/projection/src/android/media/projection/MediaProjectionAppContentTest.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2025 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.media.projection;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.graphics.Bitmap;
+import android.os.Parcel;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class MediaProjectionAppContentTest {
+
+ @Test
+ public void testConstructorAndGetters() {
+ // Create a mock Bitmap
+ Bitmap mockBitmap = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);
+
+ // Create a MediaProjectionAppContent object
+ MediaProjectionAppContent content = new MediaProjectionAppContent(mockBitmap, "Test Title",
+ 123);
+
+ // Verify the values using getters
+ assertThat(content.getTitle()).isEqualTo("Test Title");
+ assertThat(content.getId()).isEqualTo(123);
+ // Compare bitmap configurations and dimensions
+ assertThat(content.getThumbnail().getConfig()).isEqualTo(mockBitmap.getConfig());
+ assertThat(content.getThumbnail().getWidth()).isEqualTo(mockBitmap.getWidth());
+ assertThat(content.getThumbnail().getHeight()).isEqualTo(mockBitmap.getHeight());
+ }
+
+ @Test
+ public void testParcelable() {
+ // Create a mock Bitmap
+ Bitmap mockBitmap = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);
+
+ // Create a MediaProjectionAppContent object
+ MediaProjectionAppContent content = new MediaProjectionAppContent(mockBitmap, "Test Title",
+ 123);
+
+ // Parcel and unparcel the object
+ Parcel parcel = Parcel.obtain();
+ content.writeToParcel(parcel, 0);
+ parcel.setDataPosition(0);
+ MediaProjectionAppContent unparceledContent =
+ MediaProjectionAppContent.CREATOR.createFromParcel(parcel);
+
+ // Verify the values of the unparceled object
+ assertThat(unparceledContent.getTitle()).isEqualTo("Test Title");
+ assertThat(unparceledContent.getId()).isEqualTo(123);
+ // Compare bitmap configurations and dimensions
+ assertThat(unparceledContent.getThumbnail().getConfig()).isEqualTo(mockBitmap.getConfig());
+ assertThat(unparceledContent.getThumbnail().getWidth()).isEqualTo(mockBitmap.getWidth());
+ assertThat(unparceledContent.getThumbnail().getHeight()).isEqualTo(mockBitmap.getHeight());
+
+ parcel.recycle();
+ }
+
+ @Test
+ public void testCreatorNewArray() {
+ // Create a new array using the CREATOR
+ MediaProjectionAppContent[] contentArray = MediaProjectionAppContent.CREATOR.newArray(5);
+
+ // Verify that the array is not null and has the correct size
+ assertThat(contentArray).isNotNull();
+ assertThat(contentArray).hasLength(5);
+ }
+}
diff --git a/media/tests/projection/src/android/media/projection/MediaProjectionConfigTest.java b/media/tests/projection/src/android/media/projection/MediaProjectionConfigTest.java
index 2820606958b7..bc0eae1a3ec7 100644
--- a/media/tests/projection/src/android/media/projection/MediaProjectionConfigTest.java
+++ b/media/tests/projection/src/android/media/projection/MediaProjectionConfigTest.java
@@ -18,22 +18,31 @@ package android.media.projection;
import static android.media.projection.MediaProjectionConfig.CAPTURE_REGION_FIXED_DISPLAY;
import static android.media.projection.MediaProjectionConfig.CAPTURE_REGION_USER_CHOICE;
+import static android.media.projection.MediaProjectionConfig.PROJECTION_SOURCE_DISPLAY;
+import static android.media.projection.MediaProjectionConfig.DEFAULT_PROJECTION_SOURCES;
import static android.view.Display.DEFAULT_DISPLAY;
import static com.google.common.truth.Truth.assertThat;
import android.os.Parcel;
import android.platform.test.annotations.Presubmit;
+import android.platform.test.annotations.RequiresFlagsDisabled;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
+import com.android.media.projection.flags.Flags;
+
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
/**
* Tests for the {@link MediaProjectionConfig} class.
- *
+ * <p>
* Build/Install/Run:
* atest MediaProjectionTests:MediaProjectionConfigTest
*/
@@ -41,6 +50,11 @@ import org.junit.runner.RunWith;
@Presubmit
@RunWith(AndroidJUnit4.class)
public class MediaProjectionConfigTest {
+
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule =
+ DeviceFlagsValueProvider.createCheckFlagsRule();
+
private static final MediaProjectionConfig DISPLAY_CONFIG =
MediaProjectionConfig.createConfigForDefaultDisplay();
private static final MediaProjectionConfig USERS_CHOICE_CONFIG =
@@ -57,17 +71,33 @@ public class MediaProjectionConfigTest {
}
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_APP_CONTENT_SHARING)
public void testCreateDisplayConfig() {
assertThat(DISPLAY_CONFIG.getRegionToCapture()).isEqualTo(CAPTURE_REGION_FIXED_DISPLAY);
assertThat(DISPLAY_CONFIG.getDisplayToCapture()).isEqualTo(DEFAULT_DISPLAY);
}
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_APP_CONTENT_SHARING)
public void testCreateUsersChoiceConfig() {
assertThat(USERS_CHOICE_CONFIG.getRegionToCapture()).isEqualTo(CAPTURE_REGION_USER_CHOICE);
}
@Test
+ @RequiresFlagsEnabled(Flags.FLAG_APP_CONTENT_SHARING)
+ public void testDefaultProjectionSources() {
+ assertThat(USERS_CHOICE_CONFIG.getProjectionSources())
+ .isEqualTo(DEFAULT_PROJECTION_SOURCES);
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_APP_CONTENT_SHARING)
+ public void testCreateDisplayConfigProjectionSource() {
+ assertThat(DISPLAY_CONFIG.getProjectionSources()).isEqualTo(PROJECTION_SOURCE_DISPLAY);
+ assertThat(DISPLAY_CONFIG.getDisplayToCapture()).isEqualTo(DEFAULT_DISPLAY);
+ }
+
+ @Test
public void testEquals() {
assertThat(MediaProjectionConfig.createConfigForUserChoice()).isEqualTo(
USERS_CHOICE_CONFIG);