From 2c13cbbab57d923c7349bbbdbcae433bf9006b48 Mon Sep 17 00:00:00 2001 From: Naomi Musgrave Date: Wed, 7 Dec 2022 16:29:20 +0000 Subject: [PATCH] [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 --- core/api/current.txt | 11 +- .../projection/MediaProjectionConfig.aidl | 19 ++ .../projection/MediaProjectionConfig.java | 293 ++++++++++++++++++ .../projection/MediaProjectionManager.java | 60 +++- media/tests/projection/Android.bp | 46 +++ media/tests/projection/AndroidManifest.xml | 32 ++ media/tests/projection/AndroidTest.xml | 32 ++ media/tests/projection/TEST_MAPPING | 13 + .../projection/MediaProjectionConfigTest.java | 84 +++++ .../MediaProjectionManagerTest.java | 125 ++++++++ 10 files changed, 710 insertions(+), 5 deletions(-) create mode 100644 media/java/android/media/projection/MediaProjectionConfig.aidl create mode 100644 media/java/android/media/projection/MediaProjectionConfig.java create mode 100644 media/tests/projection/Android.bp create mode 100644 media/tests/projection/AndroidManifest.xml create mode 100644 media/tests/projection/AndroidTest.xml create mode 100644 media/tests/projection/TEST_MAPPING create mode 100644 media/tests/projection/src/android/media/projection/MediaProjectionConfigTest.java create mode 100644 media/tests/projection/src/android/media/projection/MediaProjectionManagerTest.java diff --git a/core/api/current.txt b/core/api/current.txt index aedad0d748f88..877e2d6b54b4b 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -25468,8 +25468,17 @@ package android.media.projection { 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 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 0000000000000..f78385f43d3d4 --- /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 0000000000000..29afaa6f4c436 --- /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. + * + *

+ * 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. + *

+ */ + @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 CREATOR + = new Parcelable.Creator() { + @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 b3bd980451648..a4215e68bfef8 100644 --- a/media/java/android/media/projection/MediaProjectionManager.java +++ b/media/java/android/media/projection/MediaProjectionManager.java @@ -38,6 +38,13 @@ import java.util.Map; @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 @@ public final class MediaProjectionManager { } /** - * Returns an Intent that must 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 must 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 = @@ -79,6 +88,49 @@ public final class MediaProjectionManager { return i; } + /** + * Returns an {@link Intent} that must 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)}. + * + *

+ * If {@link MediaProjectionConfig} was created from: + *

  • + *
      + * {@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. + *
    + *
      + * {@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. + *
    + *
  • + * + * @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 diff --git a/media/tests/projection/Android.bp b/media/tests/projection/Android.bp new file mode 100644 index 0000000000000..08d950128ce25 --- /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 0000000000000..62f148cfdde12 --- /dev/null +++ b/media/tests/projection/AndroidManifest.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + + diff --git a/media/tests/projection/AndroidTest.xml b/media/tests/projection/AndroidTest.xml new file mode 100644 index 0000000000000..f64930a0eb3fd --- /dev/null +++ b/media/tests/projection/AndroidTest.xml @@ -0,0 +1,32 @@ + + + + diff --git a/media/tests/projection/TEST_MAPPING b/media/tests/projection/TEST_MAPPING new file mode 100644 index 0000000000000..ddb68af107344 --- /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 0000000000000..a30f2e3c7c885 --- /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 0000000000000..a3e49088ac4ad --- /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); + } +}