[MediaProjection] introduce display id specification on intent
Allow calling apps to specify display capture on the screen
capture intent. Limited to display 0, since the recording
privacy indicator must be present on the display that is being
captured; currently the status bar is only present on display 0.
Bug: 260217633
API-Coverage-Bug: 261567291
Test: atest MediaProjectionTests:MediaProjectionManagerTest
Test: atest MediaProjectionTests:MediaProjectionConfigTest
Change-Id: I302edbad63292db25fc9d2339e0da50af0a4c4fb
diff --git a/core/api/current.txt b/core/api/current.txt
index aedad0d..877e2d6 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -25468,8 +25468,17 @@
method public void onStop();
}
+ public final class MediaProjectionConfig implements android.os.Parcelable {
+ method @NonNull public static android.media.projection.MediaProjectionConfig createConfigForDisplay(@IntRange(from=android.view.Display.DEFAULT_DISPLAY, to=android.view.Display.DEFAULT_DISPLAY) int);
+ method @NonNull public static android.media.projection.MediaProjectionConfig createConfigForUserChoice();
+ method public int describeContents();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.media.projection.MediaProjectionConfig> CREATOR;
+ }
+
public final class MediaProjectionManager {
- method public android.content.Intent createScreenCaptureIntent();
+ method @NonNull public android.content.Intent createScreenCaptureIntent();
+ method @NonNull public android.content.Intent createScreenCaptureIntent(@NonNull android.media.projection.MediaProjectionConfig);
method public android.media.projection.MediaProjection getMediaProjection(int, @NonNull android.content.Intent);
}
diff --git a/media/java/android/media/projection/MediaProjectionConfig.aidl b/media/java/android/media/projection/MediaProjectionConfig.aidl
new file mode 100644
index 0000000..f78385f
--- /dev/null
+++ b/media/java/android/media/projection/MediaProjectionConfig.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.media.projection;
+
+parcelable MediaProjectionConfig;
diff --git a/media/java/android/media/projection/MediaProjectionConfig.java b/media/java/android/media/projection/MediaProjectionConfig.java
new file mode 100644
index 0000000..29afaa6
--- /dev/null
+++ b/media/java/android/media/projection/MediaProjectionConfig.java
@@ -0,0 +1,293 @@
+/*
+ * 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.media.projection;
+
+import static android.view.Display.DEFAULT_DISPLAY;
+
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+import android.annotation.IntDef;
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Parcelable;
+
+import com.android.internal.util.AnnotationValidations;
+import com.android.internal.util.DataClass;
+
+import java.lang.annotation.Retention;
+
+/**
+ * Configure the {@link MediaProjection} session requested from
+ * {@link MediaProjectionManager#createScreenCaptureIntent(MediaProjectionConfig)}.
+ */
+@DataClass(
+ genEqualsHashCode = true,
+ genAidl = true,
+ genSetters = false,
+ genConstructor = false,
+ genBuilder = false,
+ genToString = false,
+ genHiddenConstDefs = true,
+ genHiddenGetters = true,
+ genConstDefs = false
+)
+public final class MediaProjectionConfig implements Parcelable {
+
+ /**
+ * The user, rather than the host app, determines which region of the display to capture.
+ * @hide
+ */
+ public static final int CAPTURE_REGION_USER_CHOICE = 0;
+
+ /**
+ * The host app specifies a particular display to capture.
+ * @hide
+ */
+ public static final int CAPTURE_REGION_FIXED_DISPLAY = 1;
+
+ /** @hide */
+ @IntDef(prefix = "CAPTURE_REGION_", value = {
+ CAPTURE_REGION_USER_CHOICE,
+ CAPTURE_REGION_FIXED_DISPLAY
+ })
+ @Retention(SOURCE)
+ public @interface CaptureRegion {
+ }
+
+ /**
+ * The particular display to capture. Only used when {@link #getRegionToCapture()} is
+ * {@link #CAPTURE_REGION_FIXED_DISPLAY}; ignored otherwise.
+ *
+ * Only supports values of {@link android.view.Display#DEFAULT_DISPLAY}.
+ */
+ @IntRange(from = DEFAULT_DISPLAY, to = DEFAULT_DISPLAY)
+ private int mDisplayToCapture;
+
+ /**
+ * The region to capture. Defaults to the user's choice.
+ */
+ @CaptureRegion
+ private int mRegionToCapture = CAPTURE_REGION_USER_CHOICE;
+
+ /**
+ * Default instance, with region set to the user's choice.
+ */
+ private MediaProjectionConfig() {
+ }
+
+ /**
+ * Customized instance, with region set to the provided value.
+ */
+ private MediaProjectionConfig(@CaptureRegion int captureRegion) {
+ mRegionToCapture = captureRegion;
+ }
+
+ /**
+ * Returns an instance which restricts the user to capturing a particular display.
+ *
+ * @param displayId The id of the display to capture. Only supports values of
+ * {@link android.view.Display#DEFAULT_DISPLAY}.
+ * @throws IllegalArgumentException If the given {@code displayId} is outside the range of
+ * supported values.
+ */
+ @NonNull
+ public static MediaProjectionConfig createConfigForDisplay(
+ @IntRange(from = DEFAULT_DISPLAY, to = DEFAULT_DISPLAY) int displayId) {
+ if (displayId != DEFAULT_DISPLAY) {
+ throw new IllegalArgumentException(
+ "A config for capturing the non-default display is not supported; requested "
+ + "display id "
+ + displayId);
+ }
+ MediaProjectionConfig config = new MediaProjectionConfig(CAPTURE_REGION_FIXED_DISPLAY);
+ config.mDisplayToCapture = displayId;
+ return config;
+ }
+
+ /**
+ * 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
+ * dialog shown to the user will be the same as if just
+ * {@link MediaProjectionManager#createScreenCaptureIntent()} was invoked.
+ * </p>
+ */
+ @NonNull
+ public static MediaProjectionConfig createConfigForUserChoice() {
+ return new MediaProjectionConfig(CAPTURE_REGION_USER_CHOICE);
+ }
+
+ /**
+ * Returns string representation of the captured region.
+ */
+ @NonNull
+ private static String captureRegionToString(int value) {
+ switch (value) {
+ case CAPTURE_REGION_USER_CHOICE:
+ return "CAPTURE_REGION_USERS_CHOICE";
+ case CAPTURE_REGION_FIXED_DISPLAY:
+ return "CAPTURE_REGION_GIVEN_DISPLAY";
+ default:
+ return Integer.toHexString(value);
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "MediaProjectionConfig { "
+ + "displayToCapture = " + mDisplayToCapture + ", "
+ + "regionToCapture = " + captureRegionToString(mRegionToCapture)
+ + " }";
+ }
+
+
+
+
+
+ // Code below generated by codegen v1.0.23.
+ //
+ // DO NOT MODIFY!
+ // CHECKSTYLE:OFF Generated code
+ //
+ // To regenerate run:
+ // $ codegen $ANDROID_BUILD_TOP/frameworks/base/media/java/android/media/projection/MediaProjectionConfig.java
+ //
+ // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+ // Settings > Editor > Code Style > Formatter Control
+ //@formatter:off
+
+
+ /**
+ * The particular display to capture. Only used when {@link #getRegionToCapture()} is
+ * {@link #CAPTURE_REGION_FIXED_DISPLAY}; ignored otherwise.
+ *
+ * Only supports values of {@link android.view.Display#DEFAULT_DISPLAY}.
+ *
+ * @hide
+ */
+ @DataClass.Generated.Member
+ public @IntRange(from = DEFAULT_DISPLAY, to = DEFAULT_DISPLAY) int getDisplayToCapture() {
+ return mDisplayToCapture;
+ }
+
+ /**
+ * The region to capture. Defaults to the user's choice.
+ *
+ * @hide
+ */
+ @DataClass.Generated.Member
+ public @CaptureRegion int getRegionToCapture() {
+ return mRegionToCapture;
+ }
+
+ @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(MediaProjectionConfig other) { ... }
+ // boolean fieldNameEquals(FieldType otherValue) { ... }
+
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ @SuppressWarnings("unchecked")
+ MediaProjectionConfig that = (MediaProjectionConfig) o;
+ //noinspection PointlessBooleanExpression
+ return true
+ && mDisplayToCapture == that.mDisplayToCapture
+ && mRegionToCapture == that.mRegionToCapture;
+ }
+
+ @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 + mDisplayToCapture;
+ _hash = 31 * _hash + mRegionToCapture;
+ return _hash;
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public void writeToParcel(@NonNull android.os.Parcel dest, int flags) {
+ // You can override field parcelling by defining methods like:
+ // void parcelFieldName(Parcel dest, int flags) { ... }
+
+ dest.writeInt(mDisplayToCapture);
+ dest.writeInt(mRegionToCapture);
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public int describeContents() { return 0; }
+
+ /** @hide */
+ @SuppressWarnings({"unchecked", "RedundantCast"})
+ @DataClass.Generated.Member
+ /* package-private */ MediaProjectionConfig(@NonNull android.os.Parcel in) {
+ // You can override field unparcelling by defining methods like:
+ // static FieldType unparcelFieldName(Parcel in) { ... }
+
+ int displayToCapture = in.readInt();
+ int regionToCapture = in.readInt();
+
+ this.mDisplayToCapture = displayToCapture;
+ AnnotationValidations.validate(
+ IntRange.class, null, mDisplayToCapture,
+ "from", DEFAULT_DISPLAY,
+ "to", DEFAULT_DISPLAY);
+ this.mRegionToCapture = regionToCapture;
+ AnnotationValidations.validate(
+ CaptureRegion.class, null, mRegionToCapture);
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ @DataClass.Generated.Member
+ public static final @NonNull Parcelable.Creator<MediaProjectionConfig> CREATOR
+ = new Parcelable.Creator<MediaProjectionConfig>() {
+ @Override
+ public MediaProjectionConfig[] newArray(int size) {
+ return new MediaProjectionConfig[size];
+ }
+
+ @Override
+ public MediaProjectionConfig createFromParcel(@NonNull android.os.Parcel in) {
+ return new MediaProjectionConfig(in);
+ }
+ };
+
+ @DataClass.Generated(
+ time = 1671030124845L,
+ codegenVersion = "1.0.23",
+ sourceFile = "frameworks/base/media/java/android/media/projection/MediaProjectionConfig.java",
+ inputSignatures = "public static final int CAPTURE_REGION_USER_CHOICE\npublic static final int CAPTURE_REGION_FIXED_DISPLAY\nprivate @android.annotation.IntRange int mDisplayToCapture\nprivate @android.media.projection.MediaProjectionConfig.CaptureRegion int mRegionToCapture\npublic static @android.annotation.NonNull android.media.projection.MediaProjectionConfig createConfigForDisplay(int)\npublic static @android.annotation.NonNull android.media.projection.MediaProjectionConfig createConfigForUserChoice()\nprivate static @android.annotation.NonNull java.lang.String captureRegionToString(int)\npublic @java.lang.Override java.lang.String toString()\nclass MediaProjectionConfig extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genAidl=true, genSetters=false, genConstructor=false, genBuilder=false, genToString=false, genHiddenConstDefs=true, genHiddenGetters=true, genConstDefs=false)")
+ @Deprecated
+ private void __metadata() {}
+
+
+ //@formatter:on
+ // End of generated code
+
+}
diff --git a/media/java/android/media/projection/MediaProjectionManager.java b/media/java/android/media/projection/MediaProjectionManager.java
index b3bd980..a4215e68 100644
--- a/media/java/android/media/projection/MediaProjectionManager.java
+++ b/media/java/android/media/projection/MediaProjectionManager.java
@@ -38,6 +38,13 @@
@SystemService(Context.MEDIA_PROJECTION_SERVICE)
public final class MediaProjectionManager {
private static final String TAG = "MediaProjectionManager";
+
+ /**
+ * Intent extra to customize the permission dialog based on the host app's preferences.
+ * @hide
+ */
+ public static final String EXTRA_MEDIA_PROJECTION_CONFIG =
+ "android.media.projection.extra.EXTRA_MEDIA_PROJECTION_CONFIG";
/** @hide */
public static final String EXTRA_APP_TOKEN = "android.media.projection.extra.EXTRA_APP_TOKEN";
/** @hide */
@@ -64,11 +71,13 @@
}
/**
- * Returns an Intent that <b>must</b> be passed to startActivityForResult()
- * in order to start screen capture. The activity will prompt
- * the user whether to allow screen capture. The result of this
- * activity should be passed to getMediaProjection.
+ * Returns an {@link Intent} that <b>must</b> be passed to
+ * {@link Activity#startActivityForResult(Intent, int)} (or similar) in order to start screen
+ * capture. The activity will prompt the user whether to allow screen capture. The result of
+ * this activity (received by overriding {@link Activity#onActivityResult(int, int, Intent)})
+ * should be passed to {@link #getMediaProjection(int, Intent)}.
*/
+ @NonNull
public Intent createScreenCaptureIntent() {
Intent i = new Intent();
final ComponentName mediaProjectionPermissionDialogComponent =
@@ -80,6 +89,49 @@
}
/**
+ * Returns an {@link Intent} that <b>must</b> be passed to
+ * {@link Activity#startActivityForResult(Intent, int)} (or similar) in order to start screen
+ * capture. Customizes the activity and resulting {@link MediaProjection} session based up
+ * the provided {@code config}. The activity will prompt the user whether to allow screen
+ * capture. The result of this activity (received by overriding
+ * {@link Activity#onActivityResult(int, int, Intent)}) should be passed to
+ * {@link #getMediaProjection(int, Intent)}.
+ *
+ * <p>
+ * If {@link MediaProjectionConfig} was created from:
+ * <li>
+ * <ul>
+ * {@link MediaProjectionConfig#createConfigForDisplay(int)}, then creates an
+ * {@link Intent} for capturing this particular display. The activity limits the user's
+ * choice to just the display specified.
+ * </ul>
+ * <ul>
+ * {@link MediaProjectionConfig#createConfigForUserChoice()}, then creates an
+ * {@link Intent} for deferring which region to capture to the user. This gives the
+ * user the same behaviour as calling {@link #createScreenCaptureIntent()}. The
+ * activity gives the user the choice between
+ * {@link android.view.Display#DEFAULT_DISPLAY}, or a different region.
+ * </ul>
+ * </li>
+ *
+ * @param config Customization for the {@link MediaProjection} that this {@link Intent} requests
+ * the user's consent for.
+ * @return An {@link Intent} requesting the user's consent, specialized based upon the given
+ * configuration.
+ */
+ @NonNull
+ public Intent createScreenCaptureIntent(@NonNull MediaProjectionConfig config) {
+ Intent i = new Intent();
+ final ComponentName mediaProjectionPermissionDialogComponent =
+ ComponentName.unflattenFromString(mContext.getResources()
+ .getString(com.android.internal.R.string
+ .config_mediaProjectionPermissionDialogComponent));
+ i.setComponent(mediaProjectionPermissionDialogComponent);
+ i.putExtra(EXTRA_MEDIA_PROJECTION_CONFIG, config);
+ return i;
+ }
+
+ /**
* Retrieves the {@link MediaProjection} obtained from a successful screen
* capture request. The result code and data from the request are provided
* by overriding {@link Activity#onActivityResult(int, int, Intent)
diff --git a/media/tests/projection/Android.bp b/media/tests/projection/Android.bp
new file mode 100644
index 0000000..08d9501
--- /dev/null
+++ b/media/tests/projection/Android.bp
@@ -0,0 +1,46 @@
+//########################################################################
+// Build MediaProjectionTests package
+//########################################################################
+
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "frameworks_base_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["frameworks_base_license"],
+}
+
+android_test {
+ name: "MediaProjectionTests",
+
+ srcs: ["**/*.java"],
+
+ libs: [
+ "android.test.base",
+ "android.test.mock",
+ "android.test.runner",
+ ],
+
+ static_libs: [
+ "androidx.test.runner",
+ "androidx.test.rules",
+ "androidx.test.ext.junit",
+ "mockito-target-extended-minus-junit4",
+ "platform-test-annotations",
+ "testng",
+ "truth-prebuilt",
+ ],
+
+ // Needed for mockito-target-extended-minus-junit4
+ jni_libs: [
+ "libdexmakerjvmtiagent",
+ "libstaticjvmtiagent",
+ ],
+
+ test_suites: ["device-tests"],
+
+ platform_apis: true,
+
+ certificate: "platform",
+}
diff --git a/media/tests/projection/AndroidManifest.xml b/media/tests/projection/AndroidManifest.xml
new file mode 100644
index 0000000..62f148c
--- /dev/null
+++ b/media/tests/projection/AndroidManifest.xml
@@ -0,0 +1,32 @@
+<!--
+ ~ 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.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ android:installLocation="internalOnly"
+ package="android.media.projection.mediaprojectiontests"
+ android:sharedUserId="com.android.uid.test">
+ <uses-permission android:name="android.permission.READ_COMPAT_CHANGE_CONFIG" />
+
+ <application android:debuggable="true"
+ android:testOnly="true">
+ <uses-library android:name="android.test.mock" android:required="true"/>
+ <uses-library android:name="android.test.runner"/>
+ </application>
+
+ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="android.media.projection.mediaprojectiontests"
+ android:label="MediaProjection package tests"/>
+</manifest>
diff --git a/media/tests/projection/AndroidTest.xml b/media/tests/projection/AndroidTest.xml
new file mode 100644
index 0000000..f64930a
--- /dev/null
+++ b/media/tests/projection/AndroidTest.xml
@@ -0,0 +1,32 @@
+<!--
+ ~ 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.
+ -->
+
+<configuration description="Runs MediaProjection package Tests.">
+ <option name="test-suite-tag" value="apct" />
+ <option name="test-suite-tag" value="apct-instrumentation" />
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="cleanup-apks" value="true" />
+ <option name="install-arg" value="-t" />
+ <option name="test-file-name" value="MediaProjectionTests.apk" />
+ </target_preparer>
+
+ <option name="test-tag" value="MediaProjectionTests" />
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest">
+ <option name="package" value="android.media.projection.mediaprojectiontests" />
+ <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
+ <option name="hidden-api-checks" value="false" />
+ </test>
+</configuration>
diff --git a/media/tests/projection/TEST_MAPPING b/media/tests/projection/TEST_MAPPING
new file mode 100644
index 0000000..ddb68af
--- /dev/null
+++ b/media/tests/projection/TEST_MAPPING
@@ -0,0 +1,13 @@
+{
+ "presubmit": [
+ {
+ "name": "FrameworksServicesTests",
+ "options": [
+ {"include-filter": "android.media.projection.mediaprojectiontests"},
+ {"exclude-annotation": "android.platform.test.annotations.FlakyTest"},
+ {"exclude-annotation": "androidx.test.filters.FlakyTest"},
+ {"exclude-annotation": "org.junit.Ignore"}
+ ]
+ }
+ ]
+}
diff --git a/media/tests/projection/src/android/media/projection/MediaProjectionConfigTest.java b/media/tests/projection/src/android/media/projection/MediaProjectionConfigTest.java
new file mode 100644
index 0000000..a30f2e3
--- /dev/null
+++ b/media/tests/projection/src/android/media/projection/MediaProjectionConfigTest.java
@@ -0,0 +1,84 @@
+/*
+ * 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.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.view.Display.DEFAULT_DISPLAY;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertThrows;
+
+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 the {@link MediaProjectionConfig} class.
+ *
+ * Build/Install/Run:
+ * atest MediaProjectionTests:MediaProjectionConfigTest
+ */
+@SmallTest
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class MediaProjectionConfigTest {
+ private static final MediaProjectionConfig DISPLAY_CONFIG =
+ MediaProjectionConfig.createConfigForDisplay(DEFAULT_DISPLAY);
+ private static final MediaProjectionConfig USERS_CHOICE_CONFIG =
+ MediaProjectionConfig.createConfigForUserChoice();
+
+ @Test
+ public void testParcelable() {
+ Parcel parcel = Parcel.obtain();
+ DISPLAY_CONFIG.writeToParcel(parcel, 0 /* flags */);
+ parcel.setDataPosition(0);
+ MediaProjectionConfig config = MediaProjectionConfig.CREATOR.createFromParcel(parcel);
+ assertThat(DISPLAY_CONFIG).isEqualTo(config);
+ parcel.recycle();
+ }
+
+ @Test
+ public void testCreateDisplayConfig() {
+ assertThrows(IllegalArgumentException.class,
+ () -> MediaProjectionConfig.createConfigForDisplay(-1));
+ assertThrows(IllegalArgumentException.class,
+ () -> MediaProjectionConfig.createConfigForDisplay(DEFAULT_DISPLAY + 1));
+ assertThat(DISPLAY_CONFIG.getRegionToCapture()).isEqualTo(CAPTURE_REGION_FIXED_DISPLAY);
+ assertThat(DISPLAY_CONFIG.getDisplayToCapture()).isEqualTo(DEFAULT_DISPLAY);
+ }
+
+ @Test
+ public void testCreateUsersChoiceConfig() {
+ assertThat(USERS_CHOICE_CONFIG.getRegionToCapture()).isEqualTo(CAPTURE_REGION_USER_CHOICE);
+ }
+
+ @Test
+ public void testEquals() {
+ assertThat(MediaProjectionConfig.createConfigForUserChoice()).isEqualTo(
+ USERS_CHOICE_CONFIG);
+ assertThat(DISPLAY_CONFIG).isNotEqualTo(USERS_CHOICE_CONFIG);
+ assertThat(MediaProjectionConfig.createConfigForDisplay(DEFAULT_DISPLAY)).isEqualTo(
+ DISPLAY_CONFIG);
+ }
+}
diff --git a/media/tests/projection/src/android/media/projection/MediaProjectionManagerTest.java b/media/tests/projection/src/android/media/projection/MediaProjectionManagerTest.java
new file mode 100644
index 0000000..a3e4908
--- /dev/null
+++ b/media/tests/projection/src/android/media/projection/MediaProjectionManagerTest.java
@@ -0,0 +1,125 @@
+/*
+ * 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.media.projection;
+
+import static android.media.projection.MediaProjectionManager.EXTRA_MEDIA_PROJECTION_CONFIG;
+import static android.view.Display.DEFAULT_DISPLAY;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+import static org.mockito.quality.Strictness.LENIENT;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.Resources;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.MockitoSession;
+
+/**
+ * Tests for the {@link MediaProjectionManager} class.
+ *
+ * Build/Install/Run:
+ * atest MediaProjectionTests:MediaProjectionManagerTest
+ */
+@SmallTest
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class MediaProjectionManagerTest {
+ private MediaProjectionManager mMediaProjectionManager;
+ private Context mContext;
+ private MockitoSession mMockingSession;
+ private static final MediaProjectionConfig DISPLAY_CONFIG =
+ MediaProjectionConfig.createConfigForDisplay(DEFAULT_DISPLAY);
+ private static final MediaProjectionConfig USERS_CHOICE_CONFIG =
+ MediaProjectionConfig.createConfigForUserChoice();
+
+ @Before
+ public void setup() throws Exception {
+ mMockingSession =
+ mockitoSession()
+ .initMocks(this)
+ .strictness(LENIENT)
+ .startMocking();
+ mContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
+ spyOn(mContext);
+ mMediaProjectionManager = new MediaProjectionManager(mContext);
+ }
+
+ @After
+ public void teardown() {
+ mMockingSession.finishMocking();
+ }
+
+ @Test
+ public void testCreateScreenCaptureIntent() {
+ final String dialogPackage = "test.package";
+ preparePermissionDialogComponent(dialogPackage);
+
+ final Intent intent = mMediaProjectionManager.createScreenCaptureIntent();
+ assertThat(intent).isNotNull();
+ assertThat(intent.getComponent().getPackageName()).contains(dialogPackage);
+ }
+
+ @Test
+ public void testCreateScreenCaptureIntent_display() {
+ final String dialogPackage = "test.package";
+ preparePermissionDialogComponent(dialogPackage);
+
+ final Intent intent = mMediaProjectionManager.createScreenCaptureIntent(DISPLAY_CONFIG);
+ assertThat(intent).isNotNull();
+ assertThat(intent.getComponent().getPackageName()).contains(dialogPackage);
+ assertThat(intent.getParcelableExtra(EXTRA_MEDIA_PROJECTION_CONFIG,
+ MediaProjectionConfig.class)).isEqualTo(DISPLAY_CONFIG);
+ }
+
+ @Test
+ public void testCreateScreenCaptureIntent_usersChoice() {
+ final String dialogPackage = "test.package";
+ preparePermissionDialogComponent(dialogPackage);
+
+ final Intent intent = mMediaProjectionManager.createScreenCaptureIntent(
+ USERS_CHOICE_CONFIG);
+ assertThat(intent).isNotNull();
+ assertThat(intent.getComponent().getPackageName()).contains(dialogPackage);
+ assertThat(intent.getParcelableExtra(EXTRA_MEDIA_PROJECTION_CONFIG,
+ MediaProjectionConfig.class)).isEqualTo(USERS_CHOICE_CONFIG);
+ }
+
+ private void preparePermissionDialogComponent(@NonNull String dialogPackage) {
+ final Resources mockResources = mock(Resources.class);
+ when(mContext.getResources()).thenReturn(mockResources);
+ doReturn(dialogPackage + "/.TestActivity").when(mockResources).getString(
+ com.android.internal.R.string
+ .config_mediaProjectionPermissionDialogComponent);
+ }
+}