Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 2c13cbba authored by Naomi Musgrave's avatar Naomi Musgrave
Browse files

[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
parent f95354cf
Loading
Loading
Loading
Loading
+10 −1
Original line number Diff line number Diff line
@@ -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<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);
  }
+19 −0
Original line number Diff line number Diff line
/*
 * 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;
+293 −0
Original line number Diff line number Diff line
/*
 * 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

}
+56 −4
Original line number Diff line number Diff line
@@ -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 <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 =
@@ -79,6 +88,49 @@ public final class MediaProjectionManager {
        return i;
    }

    /**
     * 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
+46 −0
Original line number Diff line number Diff line
//########################################################################
// 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",
}
Loading