/* * Copyright (C) 2017 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.companion; import static android.Manifest.permission.REQUEST_COMPANION_SELF_MANAGED; import static com.android.internal.util.CollectionUtils.emptyIfNull; import static java.util.Objects.requireNonNull; import android.Manifest; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.StringDef; import android.annotation.UserIdInt; import android.compat.annotation.UnsupportedAppUsage; import android.os.Build; import android.os.Parcel; import android.os.Parcelable; import android.provider.OneTimeUseBuilder; import com.android.internal.util.ArrayUtils; import com.android.internal.util.DataClass; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.List; import java.util.Objects; /** * A request for the user to select a companion device to associate with. * * You can optionally set {@link Builder#addDeviceFilter filters} for which devices to show to the * user to select from. * The exact type and fields of the filter you can set depend on the * medium type. See {@link Builder}'s static factory methods for specific protocols that are * supported. * * 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( genConstructor = false, genToString = true, genEqualsHashCode = true, genHiddenGetters = true, genParcelable = true, genConstDefs = false) public final class AssociationRequest implements Parcelable { /** * Device profile: watch. * * If specified, the current request may have a modified UI to highlight that the device being * set up is a specific kind of device, and some extra permissions may be granted to the app * as a result. * * Using it requires declaring uses-permission * {@link android.Manifest.permission#REQUEST_COMPANION_PROFILE_WATCH} in the manifest. * * Learn more * about device profiles. * * @see AssociationRequest.Builder#setDeviceProfile */ public static final String DEVICE_PROFILE_WATCH = "android.app.role.COMPANION_DEVICE_WATCH"; /** * Device profile: a virtual display capable of rendering Android applications, and sending back * input events. * * Only applications that have been granted * {@link android.Manifest.permission#REQUEST_COMPANION_PROFILE_APP_STREAMING} are allowed to * request to be associated with such devices. * * @see AssociationRequest.Builder#setDeviceProfile */ @RequiresPermission(Manifest.permission.REQUEST_COMPANION_PROFILE_APP_STREAMING) public static final String DEVICE_PROFILE_APP_STREAMING = "android.app.role.COMPANION_DEVICE_APP_STREAMING"; /** * Device profile: Android Automotive Projection * * Only applications that have been granted * {@link android.Manifest.permission#REQUEST_COMPANION_PROFILE_AUTOMOTIVE_PROJECTION} are * allowed to request to be associated with such devices. * * @see AssociationRequest.Builder#setDeviceProfile */ @RequiresPermission(Manifest.permission.REQUEST_COMPANION_PROFILE_AUTOMOTIVE_PROJECTION) public static final String DEVICE_PROFILE_AUTOMOTIVE_PROJECTION = "android.app.role.SYSTEM_AUTOMOTIVE_PROJECTION"; /** * Device profile: Allows the companion app to access notification, recent photos and media for * computer cross-device features. * * Only applications that have been granted * {@link android.Manifest.permission#REQUEST_COMPANION_PROFILE_COMPUTER} are allowed to * request to be associated with such devices. * * @see AssociationRequest.Builder#setDeviceProfile */ @RequiresPermission(Manifest.permission.REQUEST_COMPANION_PROFILE_COMPUTER) public static final String DEVICE_PROFILE_COMPUTER = "android.app.role.COMPANION_DEVICE_COMPUTER"; /** @hide */ @Retention(RetentionPolicy.SOURCE) @StringDef(value = { DEVICE_PROFILE_WATCH, DEVICE_PROFILE_COMPUTER, DEVICE_PROFILE_AUTOMOTIVE_PROJECTION, DEVICE_PROFILE_APP_STREAMING }) public @interface DeviceProfile {} /** * Whether only a single device should match the provided filter. * * When scanning for a single device with a specific {@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 final boolean mSingleDevice; /** * If set, only devices matching either of the given filters will be shown to the user */ @DataClass.PluralOf("deviceFilter") private final @NonNull List> mDeviceFilters; /** * Profile of the device. */ private final @Nullable @DeviceProfile String mDeviceProfile; /** * The Display name of the device to be shown in the CDM confirmation UI. Must be non-null for * "self-managed" association. */ private final @Nullable CharSequence mDisplayName; /** * Whether the association is to be managed by the companion application. */ private final boolean mSelfManaged; /** * Indicates that the application would prefer the CompanionDeviceManager to collect an explicit * confirmation from the user before creating an association, even if such confirmation is not * required. */ private final boolean mForceConfirmation; /** * The app package name of the application the association will belong to. * Populated by the system. * @hide */ private @Nullable String mPackageName; /** * The UserId of the user the association will belong to. * Populated by the system. * @hide */ private @UserIdInt int mUserId; /** * The user-readable description of the device profile's privileges. * Populated by the system. * @hide */ private @Nullable String mDeviceProfilePrivilegesDescription; /** * The time at which his request was created * @hide */ private final long mCreationTime; /** * Whether the user-prompt may be skipped once the device is found. * Populated by the system. * @hide */ private boolean mSkipPrompt; /** * Creates a new AssociationRequest. * * @param singleDevice * Whether only a single device should match the provided filter. * * When scanning for a single device with a specific {@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 * Profile of the device. * @param displayName * The Display name of the device to be shown in the CDM confirmation UI. Must be non-null for * "self-managed" association. * @param selfManaged * Whether the association is to be managed by the companion application. */ private AssociationRequest( boolean singleDevice, @NonNull List> deviceFilters, @Nullable @DeviceProfile String deviceProfile, @Nullable CharSequence displayName, boolean selfManaged, boolean forceConfirmation) { mSingleDevice = singleDevice; mDeviceFilters = requireNonNull(deviceFilters); mDeviceProfile = deviceProfile; mDisplayName = displayName; mSelfManaged = selfManaged; mForceConfirmation = forceConfirmation; mCreationTime = System.currentTimeMillis(); } /** * @return profile of the companion device. */ public @Nullable @DeviceProfile String getDeviceProfile() { return mDeviceProfile; } /** * The Display name of the device to be shown in the CDM confirmation UI. Must be non-null for * "self-managed" association. */ public @Nullable CharSequence getDisplayName() { return mDisplayName; } /** * Whether the association is to be managed by the companion application. * * @see Builder#setSelfManaged(boolean) */ public boolean isSelfManaged() { return mSelfManaged; } /** * Indicates whether the application requires the {@link CompanionDeviceManager} service to * collect an explicit confirmation from the user before creating an association, even if * such confirmation is not required from the service's perspective. * * @see Builder#setForceConfirmation(boolean) */ public boolean isForceConfirmation() { return mForceConfirmation; } /** * Whether only a single device should match the provided filter. * * When scanning for a single device with a specific {@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. */ public boolean isSingleDevice() { return mSingleDevice; } /** @hide */ public void setPackageName(@NonNull String packageName) { mPackageName = packageName; } /** @hide */ public void setUserId(@UserIdInt int userId) { mUserId = userId; } /** @hide */ public void setDeviceProfilePrivilegesDescription(@NonNull String desc) { mDeviceProfilePrivilegesDescription = desc; } /** @hide */ public void setSkipPrompt(boolean value) { mSkipPrompt = value; } /** @hide */ @NonNull @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) public List> getDeviceFilters() { return mDeviceFilters; } /** * A builder for {@link AssociationRequest} */ public static final class Builder extends OneTimeUseBuilder { private boolean mSingleDevice = false; private @Nullable ArrayList> mDeviceFilters = null; private @Nullable String mDeviceProfile; private @Nullable CharSequence mDisplayName; private boolean mSelfManaged = false; private boolean mForceConfirmation = false; public Builder() {} /** * Whether only a single device should match the provided filter. * * When scanning for a single device with a specific {@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 singleDevice if true, scanning for a device will stop as soon as at least one * fitting device is found */ @NonNull public Builder setSingleDevice(boolean singleDevice) { checkNotUsed(); this.mSingleDevice = singleDevice; return this; } /** * @param deviceFilter if set, only devices matching the given filter will be shown to the * user */ @NonNull public Builder addDeviceFilter(@Nullable DeviceFilter deviceFilter) { checkNotUsed(); if (deviceFilter != null) { mDeviceFilters = ArrayUtils.add(mDeviceFilters, deviceFilter); } 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; } /** * Adds a display name. * Generally {@link AssociationRequest}s are not required to provide a display name, except * for request for creating "self-managed" associations, which MUST provide a display name. * * @param displayName the display name of the device. */ @NonNull public Builder setDisplayName(@NonNull CharSequence displayName) { checkNotUsed(); mDisplayName = requireNonNull(displayName); return this; } /** * Indicate whether the association would be managed by the companion application. * * Requests for creating "self-managed" association MUST provide a Display name. * * @see #setDisplayName(CharSequence) */ @RequiresPermission(REQUEST_COMPANION_SELF_MANAGED) @NonNull public Builder setSelfManaged(boolean selfManaged) { checkNotUsed(); mSelfManaged = selfManaged; return this; } /** * Indicates whether the application requires the {@link CompanionDeviceManager} service to * collect an explicit confirmation from the user before creating an association, even if * such confirmation is not required from the service's perspective. */ @RequiresPermission(REQUEST_COMPANION_SELF_MANAGED) @NonNull public Builder setForceConfirmation(boolean forceConfirmation) { checkNotUsed(); mForceConfirmation = forceConfirmation; return this; } /** @inheritDoc */ @NonNull @Override public AssociationRequest build() { markUsed(); if (mSelfManaged && mDisplayName == null) { throw new IllegalStateException("Request for a self-managed association MUST " + "provide the display name of the device"); } return new AssociationRequest(mSingleDevice, emptyIfNull(mDeviceFilters), mDeviceProfile, mDisplayName, mSelfManaged, mForceConfirmation); } } // Code below generated by codegen v1.0.23. // // 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 /** * The app package name of the application the association will belong to. * Populated by the system. * * @hide */ @DataClass.Generated.Member public @Nullable String getPackageName() { return mPackageName; } /** * The UserId of the user the association will belong to. * Populated by the system. * * @hide */ @DataClass.Generated.Member public @UserIdInt int getUserId() { return mUserId; } /** * The user-readable description of the device profile's privileges. * Populated by the system. * * @hide */ @DataClass.Generated.Member public @Nullable String getDeviceProfilePrivilegesDescription() { return mDeviceProfilePrivilegesDescription; } /** * The time at which his request was created * * @hide */ @DataClass.Generated.Member public long getCreationTime() { return mCreationTime; } /** * Whether the user-prompt may be skipped once the device is found. * Populated by the system. * * @hide */ @DataClass.Generated.Member public boolean isSkipPrompt() { return mSkipPrompt; } @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 + ", " + "displayName = " + mDisplayName + ", " + "selfManaged = " + mSelfManaged + ", " + "forceConfirmation = " + mForceConfirmation + ", " + "packageName = " + mPackageName + ", " + "userId = " + mUserId + ", " + "deviceProfilePrivilegesDescription = " + mDeviceProfilePrivilegesDescription + ", " + "creationTime = " + mCreationTime + ", " + "skipPrompt = " + mSkipPrompt + " }"; } @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(mDisplayName, that.mDisplayName) && mSelfManaged == that.mSelfManaged && mForceConfirmation == that.mForceConfirmation && Objects.equals(mPackageName, that.mPackageName) && mUserId == that.mUserId && Objects.equals(mDeviceProfilePrivilegesDescription, that.mDeviceProfilePrivilegesDescription) && mCreationTime == that.mCreationTime && mSkipPrompt == that.mSkipPrompt; } @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(mDisplayName); _hash = 31 * _hash + Boolean.hashCode(mSelfManaged); _hash = 31 * _hash + Boolean.hashCode(mForceConfirmation); _hash = 31 * _hash + Objects.hashCode(mPackageName); _hash = 31 * _hash + mUserId; _hash = 31 * _hash + Objects.hashCode(mDeviceProfilePrivilegesDescription); _hash = 31 * _hash + Long.hashCode(mCreationTime); _hash = 31 * _hash + Boolean.hashCode(mSkipPrompt); 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) { ... } int flg = 0; if (mSingleDevice) flg |= 0x1; if (mSelfManaged) flg |= 0x10; if (mForceConfirmation) flg |= 0x20; if (mSkipPrompt) flg |= 0x400; if (mDeviceProfile != null) flg |= 0x4; if (mDisplayName != null) flg |= 0x8; if (mPackageName != null) flg |= 0x40; if (mDeviceProfilePrivilegesDescription != null) flg |= 0x100; dest.writeInt(flg); dest.writeParcelableList(mDeviceFilters, flags); if (mDeviceProfile != null) dest.writeString(mDeviceProfile); if (mDisplayName != null) dest.writeCharSequence(mDisplayName); if (mPackageName != null) dest.writeString(mPackageName); dest.writeInt(mUserId); if (mDeviceProfilePrivilegesDescription != null) dest.writeString(mDeviceProfilePrivilegesDescription); dest.writeLong(mCreationTime); } @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) { ... } int flg = in.readInt(); boolean singleDevice = (flg & 0x1) != 0; boolean selfManaged = (flg & 0x10) != 0; boolean forceConfirmation = (flg & 0x20) != 0; boolean skipPrompt = (flg & 0x400) != 0; List> deviceFilters = new ArrayList<>(); in.readParcelableList(deviceFilters, DeviceFilter.class.getClassLoader(), (Class>) (Class) android.companion.DeviceFilter.class); String deviceProfile = (flg & 0x4) == 0 ? null : in.readString(); CharSequence displayName = (flg & 0x8) == 0 ? null : (CharSequence) in.readCharSequence(); String packageName = (flg & 0x40) == 0 ? null : in.readString(); int userId = in.readInt(); String deviceProfilePrivilegesDescription = (flg & 0x100) == 0 ? null : in.readString(); long creationTime = in.readLong(); 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.mDisplayName = displayName; this.mSelfManaged = selfManaged; this.mForceConfirmation = forceConfirmation; this.mPackageName = packageName; this.mUserId = userId; com.android.internal.util.AnnotationValidations.validate( UserIdInt.class, null, mUserId); this.mDeviceProfilePrivilegesDescription = deviceProfilePrivilegesDescription; this.mCreationTime = creationTime; this.mSkipPrompt = skipPrompt; // 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 AssociationRequest[] newArray(int size) { return new AssociationRequest[size]; } @Override public AssociationRequest createFromParcel(@NonNull Parcel in) { return new AssociationRequest(in); } }; @DataClass.Generated( time = 1643238443303L, codegenVersion = "1.0.23", sourceFile = "frameworks/base/core/java/android/companion/AssociationRequest.java", inputSignatures = "public static final java.lang.String DEVICE_PROFILE_WATCH\npublic static final @android.annotation.RequiresPermission java.lang.String DEVICE_PROFILE_APP_STREAMING\npublic static final @android.annotation.RequiresPermission java.lang.String DEVICE_PROFILE_AUTOMOTIVE_PROJECTION\npublic static final @android.annotation.RequiresPermission java.lang.String DEVICE_PROFILE_COMPUTER\nprivate final boolean mSingleDevice\nprivate final @com.android.internal.util.DataClass.PluralOf(\"deviceFilter\") @android.annotation.NonNull java.util.List> mDeviceFilters\nprivate final @android.annotation.Nullable @android.companion.AssociationRequest.DeviceProfile java.lang.String mDeviceProfile\nprivate final @android.annotation.Nullable java.lang.CharSequence mDisplayName\nprivate final boolean mSelfManaged\nprivate final boolean mForceConfirmation\nprivate @android.annotation.Nullable java.lang.String mPackageName\nprivate @android.annotation.UserIdInt int mUserId\nprivate @android.annotation.Nullable java.lang.String mDeviceProfilePrivilegesDescription\nprivate final long mCreationTime\nprivate boolean mSkipPrompt\npublic @android.annotation.Nullable @android.companion.AssociationRequest.DeviceProfile java.lang.String getDeviceProfile()\npublic @android.annotation.Nullable java.lang.CharSequence getDisplayName()\npublic boolean isSelfManaged()\npublic boolean isForceConfirmation()\npublic boolean isSingleDevice()\npublic void setPackageName(java.lang.String)\npublic void setUserId(int)\npublic void setDeviceProfilePrivilegesDescription(java.lang.String)\npublic void setSkipPrompt(boolean)\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\nprivate @android.annotation.Nullable java.lang.CharSequence mDisplayName\nprivate boolean mSelfManaged\nprivate boolean mForceConfirmation\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 android.companion.AssociationRequest.Builder setDisplayName(java.lang.CharSequence)\npublic @android.annotation.RequiresPermission @android.annotation.NonNull android.companion.AssociationRequest.Builder setSelfManaged(boolean)\npublic @android.annotation.RequiresPermission @android.annotation.NonNull android.companion.AssociationRequest.Builder setForceConfirmation(boolean)\npublic @android.annotation.NonNull @java.lang.Override android.companion.AssociationRequest build()\nclass Builder extends android.provider.OneTimeUseBuilder implements []\n@com.android.internal.util.DataClass(genConstructor=false, genToString=true, genEqualsHashCode=true, genHiddenGetters=true, genParcelable=true, genConstDefs=false)") @Deprecated private void __metadata() {} //@formatter:on // End of generated code }