From 10864c6764ddd880febfe08a55f00ff38e63c7d1 Mon Sep 17 00:00:00 2001 From: Eugene Susla Date: Mon, 2 Nov 2020 14:38:46 -0800 Subject: [PATCH] CDM profile API This introduces API for devs to request a "watch" profile, as well as an optional "persistent CDM grants", e.g. for devices with wifi/LTE. This also introduces a permission that will be mandatory for apps to declare in the manifest when opting into the above CTS-Coverage-Bug: 172290699 Bug: 165951651 Test: presubmit Change-Id: Ie5016c92855f33edb46fa8eb7ae2a9226e29cdda --- api/current.txt | 5 +- core/api/current.txt | 5 +- .../android/companion/AssociationRequest.java | 340 ++++++++++++++---- core/res/AndroidManifest.xml | 8 + 4 files changed, 284 insertions(+), 74 deletions(-) diff --git a/api/current.txt b/api/current.txt index 93eb32caf6976..17e3bddb91797 100644 --- a/api/current.txt +++ b/api/current.txt @@ -133,6 +133,7 @@ package android { field public static final String RECORD_AUDIO = "android.permission.RECORD_AUDIO"; field public static final String RECORD_BACKGROUND_AUDIO = "android.permission.RECORD_BACKGROUND_AUDIO"; field public static final String REORDER_TASKS = "android.permission.REORDER_TASKS"; + field public static final String REQUEST_COMPANION_PROFILE_WATCH = "android.permission.REQUEST_COMPANION_PROFILE_WATCH"; field public static final String REQUEST_COMPANION_RUN_IN_BACKGROUND = "android.permission.REQUEST_COMPANION_RUN_IN_BACKGROUND"; field public static final String REQUEST_COMPANION_USE_DATA_IN_BACKGROUND = "android.permission.REQUEST_COMPANION_USE_DATA_IN_BACKGROUND"; field public static final String REQUEST_DELETE_PACKAGES = "android.permission.REQUEST_DELETE_PACKAGES"; @@ -9434,14 +9435,16 @@ package android.companion { public final class AssociationRequest implements android.os.Parcelable { method public int describeContents(); - method public void writeToParcel(android.os.Parcel, int); + method public void writeToParcel(@NonNull android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator CREATOR; + field public static final String DEVICE_PROFILE_WATCH = "android.app.role.COMPANION_DEVICE_WATCH"; } public static final class AssociationRequest.Builder { ctor public AssociationRequest.Builder(); method @NonNull public android.companion.AssociationRequest.Builder addDeviceFilter(@Nullable android.companion.DeviceFilter); method @NonNull public android.companion.AssociationRequest build(); + method @NonNull public android.companion.AssociationRequest.Builder setDeviceProfile(@NonNull String); method @NonNull public android.companion.AssociationRequest.Builder setSingleDevice(boolean); } diff --git a/core/api/current.txt b/core/api/current.txt index 9329e2ae47fc4..db5108d578a92 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -133,6 +133,7 @@ package android { field public static final String RECORD_AUDIO = "android.permission.RECORD_AUDIO"; field public static final String RECORD_BACKGROUND_AUDIO = "android.permission.RECORD_BACKGROUND_AUDIO"; field public static final String REORDER_TASKS = "android.permission.REORDER_TASKS"; + field public static final String REQUEST_COMPANION_PROFILE_WATCH = "android.permission.REQUEST_COMPANION_PROFILE_WATCH"; field public static final String REQUEST_COMPANION_RUN_IN_BACKGROUND = "android.permission.REQUEST_COMPANION_RUN_IN_BACKGROUND"; field public static final String REQUEST_COMPANION_USE_DATA_IN_BACKGROUND = "android.permission.REQUEST_COMPANION_USE_DATA_IN_BACKGROUND"; field public static final String REQUEST_DELETE_PACKAGES = "android.permission.REQUEST_DELETE_PACKAGES"; @@ -9434,14 +9435,16 @@ package android.companion { public final class AssociationRequest implements android.os.Parcelable { method public int describeContents(); - method public void writeToParcel(android.os.Parcel, int); + method public void writeToParcel(@NonNull android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator CREATOR; + field public static final String DEVICE_PROFILE_WATCH = "android.app.role.COMPANION_DEVICE_WATCH"; } public static final class AssociationRequest.Builder { ctor public AssociationRequest.Builder(); method @NonNull public android.companion.AssociationRequest.Builder addDeviceFilter(@Nullable android.companion.DeviceFilter); method @NonNull public android.companion.AssociationRequest build(); + method @NonNull public android.companion.AssociationRequest.Builder setDeviceProfile(@NonNull String); method @NonNull public android.companion.AssociationRequest.Builder setSingleDevice(boolean); } diff --git a/core/java/android/companion/AssociationRequest.java b/core/java/android/companion/AssociationRequest.java index 8170bf92ae1e2..57b0828b334c5 100644 --- a/core/java/android/companion/AssociationRequest.java +++ b/core/java/android/companion/AssociationRequest.java @@ -16,8 +16,11 @@ package android.companion; +import static com.android.internal.util.CollectionUtils.emptyIfNull; + import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.StringDef; import android.compat.annotation.UnsupportedAppUsage; import android.os.Build; import android.os.Parcel; @@ -25,7 +28,7 @@ import android.os.Parcelable; import android.provider.OneTimeUseBuilder; import com.android.internal.util.ArrayUtils; -import com.android.internal.util.CollectionUtils; +import com.android.internal.util.DataClass; import java.util.ArrayList; import java.util.List; @@ -43,23 +46,66 @@ import java.util.Objects; * You can also set {@link Builder#setSingleDevice single device} to request a popup with single * device to be shown instead of a list to choose from */ +@DataClass( + genToString = true, + genEqualsHashCode = true, + genHiddenGetters = true, + genParcelable = true, + genHiddenConstructor = true, + genBuilder = false) public final class AssociationRequest implements Parcelable { - private final boolean mSingleDevice; - private final List> mDeviceFilters; - private String mCallingPackage; + /** + * Device profile: watch. + * + * @see AssociationRequest.Builder#setDeviceProfile + */ + public static final String DEVICE_PROFILE_WATCH = + "android.app.role.COMPANION_DEVICE_WATCH"; - private AssociationRequest( - boolean singleDevice, @Nullable List> deviceFilters) { - this.mSingleDevice = singleDevice; - this.mDeviceFilters = CollectionUtils.emptyIfNull(deviceFilters); + /** @hide */ + @StringDef(value = { DEVICE_PROFILE_WATCH }) + public @interface DeviceProfile {} + + /** + * Whether only a single device should match the provided filter. + * + * When scanning for a single device with a specifc {@link BluetoothDeviceFilter} mac + * address, bonded devices are also searched among. This allows to obtain the necessary app + * privileges even if the device is already paired. + */ + private boolean mSingleDevice = false; + + /** + * If set, only devices matching either of the given filters will be shown to the user + */ + @DataClass.PluralOf("deviceFilter") + private @NonNull List> mDeviceFilters = new ArrayList<>(); + + /** + * If set, association will be requested as a corresponding kind of device + */ + private @Nullable @DeviceProfile String mDeviceProfile = null; + + /** + * The app package making the request. + * + * Populated by the system. + * + * @hide + */ + private @Nullable String mCallingPackage = null; + + /** @hide */ + public void setCallingPackage(@NonNull String pkg) { + mCallingPackage = pkg; } - private AssociationRequest(Parcel in) { - this( - in.readByte() != 0, - in.readParcelableList(new ArrayList<>(), AssociationRequest.class.getClassLoader())); - setCallingPackage(in.readString()); + private void onConstructed() { + if (mDeviceProfile != null + && !Objects.equals(mDeviceProfile, DEVICE_PROFILE_WATCH)) { + throw new IllegalArgumentException("Invalid device profile: " + mDeviceProfile); + } } /** @hide */ @@ -75,70 +121,13 @@ public final class AssociationRequest implements Parcelable { return mDeviceFilters; } - /** @hide */ - public String getCallingPackage() { - return mCallingPackage; - } - - /** @hide */ - public void setCallingPackage(String pkg) { - mCallingPackage = pkg; - } - - @Override - public boolean equals(@Nullable Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - AssociationRequest that = (AssociationRequest) o; - return mSingleDevice == that.mSingleDevice - && Objects.equals(mDeviceFilters, that.mDeviceFilters) - && Objects.equals(mCallingPackage, that.mCallingPackage); - } - - @Override - public int hashCode() { - return Objects.hash(mSingleDevice, mDeviceFilters, mCallingPackage); - } - - @Override - public String toString() { - return "AssociationRequest{" - + "mSingleDevice=" + mSingleDevice - + ", mDeviceFilters=" + mDeviceFilters - + ", mCallingPackage=" + mCallingPackage - + '}'; - } - - @Override - public void writeToParcel(Parcel dest, int flags) { - dest.writeByte((byte) (mSingleDevice ? 1 : 0)); - dest.writeParcelableList(mDeviceFilters, flags); - dest.writeString(mCallingPackage); - } - - @Override - public int describeContents() { - return 0; - } - - public static final @android.annotation.NonNull Creator CREATOR = new Creator() { - @Override - public AssociationRequest createFromParcel(Parcel in) { - return new AssociationRequest(in); - } - - @Override - public AssociationRequest[] newArray(int size) { - return new AssociationRequest[size]; - } - }; - /** * A builder for {@link AssociationRequest} */ public static final class Builder extends OneTimeUseBuilder { private boolean mSingleDevice = false; @Nullable private ArrayList> mDeviceFilters = null; + private @Nullable String mDeviceProfile = null; public Builder() {} @@ -172,12 +161,219 @@ public final class AssociationRequest implements Parcelable { return this; } + /** + * If set, association will be requested as a corresponding kind of device + */ + @NonNull + public Builder setDeviceProfile(@NonNull @DeviceProfile String deviceProfile) { + checkNotUsed(); + mDeviceProfile = deviceProfile; + return this; + } + /** @inheritDoc */ @NonNull @Override public AssociationRequest build() { markUsed(); - return new AssociationRequest(mSingleDevice, mDeviceFilters); + return new AssociationRequest( + mSingleDevice, emptyIfNull(mDeviceFilters), + mDeviceProfile, null); } } + + + + + // Code below generated by codegen v1.0.20. + // + // DO NOT MODIFY! + // CHECKSTYLE:OFF Generated code + // + // To regenerate run: + // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/companion/AssociationRequest.java + // + // To exclude the generated code from IntelliJ auto-formatting enable (one-time): + // Settings > Editor > Code Style > Formatter Control + //@formatter:off + + + /** + * Creates a new AssociationRequest. + * + * @param singleDevice + * Whether only a single device should match the provided filter. + * + * When scanning for a single device with a specifc {@link BluetoothDeviceFilter} mac + * address, bonded devices are also searched among. This allows to obtain the necessary app + * privileges even if the device is already paired. + * @param deviceFilters + * If set, only devices matching either of the given filters will be shown to the user + * @param deviceProfile + * If set, association will be requested as a corresponding kind of device + * @param callingPackage + * The app package making the request. + * + * Populated by the system. + * @hide + */ + @DataClass.Generated.Member + public AssociationRequest( + boolean singleDevice, + @NonNull List> deviceFilters, + @Nullable @DeviceProfile String deviceProfile, + @Nullable String callingPackage) { + this.mSingleDevice = singleDevice; + this.mDeviceFilters = deviceFilters; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mDeviceFilters); + this.mDeviceProfile = deviceProfile; + com.android.internal.util.AnnotationValidations.validate( + DeviceProfile.class, null, mDeviceProfile); + this.mCallingPackage = callingPackage; + + onConstructed(); + } + + /** + * If set, association will be requested as a corresponding kind of device + * + * @hide + */ + @DataClass.Generated.Member + public @Nullable @DeviceProfile String getDeviceProfile() { + return mDeviceProfile; + } + + /** + * The app package making the request. + * + * Populated by the system. + * + * @hide + */ + @DataClass.Generated.Member + public @Nullable String getCallingPackage() { + return mCallingPackage; + } + + @Override + @DataClass.Generated.Member + public String toString() { + // You can override field toString logic by defining methods like: + // String fieldNameToString() { ... } + + return "AssociationRequest { " + + "singleDevice = " + mSingleDevice + ", " + + "deviceFilters = " + mDeviceFilters + ", " + + "deviceProfile = " + mDeviceProfile + ", " + + "callingPackage = " + mCallingPackage + + " }"; + } + + @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(AssociationRequest other) { ... } + // boolean fieldNameEquals(FieldType otherValue) { ... } + + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + @SuppressWarnings("unchecked") + AssociationRequest that = (AssociationRequest) o; + //noinspection PointlessBooleanExpression + return true + && mSingleDevice == that.mSingleDevice + && Objects.equals(mDeviceFilters, that.mDeviceFilters) + && Objects.equals(mDeviceProfile, that.mDeviceProfile) + && Objects.equals(mCallingPackage, that.mCallingPackage); + } + + @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 + Boolean.hashCode(mSingleDevice); + _hash = 31 * _hash + Objects.hashCode(mDeviceFilters); + _hash = 31 * _hash + Objects.hashCode(mDeviceProfile); + _hash = 31 * _hash + Objects.hashCode(mCallingPackage); + return _hash; + } + + @Override + @DataClass.Generated.Member + public void writeToParcel(@NonNull Parcel dest, int flags) { + // You can override field parcelling by defining methods like: + // void parcelFieldName(Parcel dest, int flags) { ... } + + byte flg = 0; + if (mSingleDevice) flg |= 0x1; + if (mDeviceProfile != null) flg |= 0x4; + if (mCallingPackage != null) flg |= 0x8; + dest.writeByte(flg); + dest.writeParcelableList(mDeviceFilters, flags); + if (mDeviceProfile != null) dest.writeString(mDeviceProfile); + if (mCallingPackage != null) dest.writeString(mCallingPackage); + } + + @Override + @DataClass.Generated.Member + public int describeContents() { return 0; } + + /** @hide */ + @SuppressWarnings({"unchecked", "RedundantCast"}) + @DataClass.Generated.Member + /* package-private */ AssociationRequest(@NonNull Parcel in) { + // You can override field unparcelling by defining methods like: + // static FieldType unparcelFieldName(Parcel in) { ... } + + byte flg = in.readByte(); + boolean singleDevice = (flg & 0x1) != 0; + List> deviceFilters = new ArrayList<>(); + in.readParcelableList(deviceFilters, DeviceFilter.class.getClassLoader()); + String deviceProfile = (flg & 0x4) == 0 ? null : in.readString(); + String callingPackage = (flg & 0x8) == 0 ? null : in.readString(); + + this.mSingleDevice = singleDevice; + this.mDeviceFilters = deviceFilters; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mDeviceFilters); + this.mDeviceProfile = deviceProfile; + com.android.internal.util.AnnotationValidations.validate( + DeviceProfile.class, null, mDeviceProfile); + this.mCallingPackage = callingPackage; + + onConstructed(); + } + + @DataClass.Generated.Member + public static final @NonNull Parcelable.Creator CREATOR + = new Parcelable.Creator() { + @Override + public AssociationRequest[] newArray(int size) { + return new AssociationRequest[size]; + } + + @Override + public AssociationRequest createFromParcel(@NonNull Parcel in) { + return new AssociationRequest(in); + } + }; + + @DataClass.Generated( + time = 1604534468409L, + codegenVersion = "1.0.20", + sourceFile = "frameworks/base/core/java/android/companion/AssociationRequest.java", + inputSignatures = "public static final java.lang.String DEVICE_PROFILE_WATCH\nprivate boolean mSingleDevice\nprivate @com.android.internal.util.DataClass.PluralOf(\"deviceFilter\") @android.annotation.NonNull java.util.List> mDeviceFilters\nprivate @android.annotation.Nullable @android.companion.AssociationRequest.DeviceProfile java.lang.String mDeviceProfile\nprivate @android.annotation.Nullable java.lang.String mCallingPackage\npublic void setCallingPackage(java.lang.String)\nprivate void onConstructed()\npublic @android.compat.annotation.UnsupportedAppUsage boolean isSingleDevice()\npublic @android.annotation.NonNull @android.compat.annotation.UnsupportedAppUsage java.util.List> getDeviceFilters()\nclass AssociationRequest extends java.lang.Object implements [android.os.Parcelable]\nprivate boolean mSingleDevice\nprivate @android.annotation.Nullable java.util.ArrayList> mDeviceFilters\nprivate @android.annotation.Nullable java.lang.String mDeviceProfile\npublic @android.annotation.NonNull android.companion.AssociationRequest.Builder setSingleDevice(boolean)\npublic @android.annotation.NonNull android.companion.AssociationRequest.Builder addDeviceFilter(android.companion.DeviceFilter)\npublic @android.annotation.NonNull android.companion.AssociationRequest.Builder setDeviceProfile(java.lang.String)\npublic @android.annotation.NonNull @java.lang.Override android.companion.AssociationRequest build()\nclass Builder extends android.provider.OneTimeUseBuilder implements []\n@com.android.internal.util.DataClass(genToString=true, genEqualsHashCode=true, genHiddenGetters=true, genParcelable=true, genHiddenConstructor=true, genBuilder=false)") + @Deprecated + private void __metadata() {} + + + //@formatter:on + // End of generated code + } diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index a778f14af7659..1ce65279173ee 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -2658,6 +2658,14 @@ android:description="@string/permdesc_useDataInBackground" android:protectionLevel="normal" /> + + +