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
This commit is contained in:
Eugene Susla
2020-11-02 14:38:46 -08:00
parent 3f1e82b12c
commit 10864c6764
4 changed files with 284 additions and 74 deletions

View File

@@ -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<android.companion.AssociationRequest> 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);
}

View File

@@ -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<android.companion.AssociationRequest> 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);
}

View File

@@ -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<DeviceFilter<?>> 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<DeviceFilter<?>> 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<DeviceFilter<?>> 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<AssociationRequest> CREATOR = new Creator<AssociationRequest>() {
@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<AssociationRequest> {
private boolean mSingleDevice = false;
@Nullable private ArrayList<DeviceFilter<?>> 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<DeviceFilter<?>> 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<DeviceFilter<?>> 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<AssociationRequest> CREATOR
= new Parcelable.Creator<AssociationRequest>() {
@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<android.companion.DeviceFilter<?>> 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<android.companion.DeviceFilter<?>> getDeviceFilters()\nclass AssociationRequest extends java.lang.Object implements [android.os.Parcelable]\nprivate boolean mSingleDevice\nprivate @android.annotation.Nullable java.util.ArrayList<android.companion.DeviceFilter<?>> 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<android.companion.AssociationRequest> 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
}

View File

@@ -2658,6 +2658,14 @@
android:description="@string/permdesc_useDataInBackground"
android:protectionLevel="normal" />
<!-- Allows app to request to be associated with a device via
{@link android.companion.CompanionDeviceManager}
as a "watch"
<p>Protection level: normal
-->
<permission android:name="android.permission.REQUEST_COMPANION_PROFILE_WATCH"
android:protectionLevel="normal" />
<!-- Allows a companion app to associate to Wi-Fi.
<p>Only for use by a single pre-approved app.
@hide