diff options
| author | 2020-12-17 14:26:56 -0800 | |
|---|---|---|
| committer | 2021-05-20 22:22:54 +0000 | |
| commit | 8f935b73c317e1485778cf90cc207ed866a3f97f (patch) | |
| tree | 85077e5aaac20bfa016563e376366ace29f2de42 | |
| parent | 42c7ce491e398d348d1817a639701f94d59e8248 (diff) | |
Allow skipping CDM dialog for same-oem
Used SHA256 from ADK to make the determination
Test: Manual
Bug: 165951651
Change-Id: I5ed42d1e67d5abaaaf24989b54dd8e95f04621e9
Merged-In: I5ed42d1e67d5abaaaf24989b54dd8e95f04621e9
5 files changed, 148 insertions, 8 deletions
diff --git a/core/java/android/companion/AssociationRequest.java b/core/java/android/companion/AssociationRequest.java index 7956be343bec..bb8fa9eb4007 100644 --- a/core/java/android/companion/AssociationRequest.java +++ b/core/java/android/companion/AssociationRequest.java @@ -123,6 +123,15 @@ public final class AssociationRequest implements Parcelable { */ private long mCreationTime; + /** + * Whether the user-prompt may be skipped once the device is found. + * + * Populated by the system. + * + * @hide + */ + private boolean mSkipPrompt = false; + private void onConstructed() { mCreationTime = System.currentTimeMillis(); } @@ -138,6 +147,11 @@ public final class AssociationRequest implements Parcelable { } /** @hide */ + public void setSkipPrompt(boolean value) { + mSkipPrompt = true; + } + + /** @hide */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) public boolean isSingleDevice() { return mSingleDevice; @@ -207,13 +221,14 @@ public final class AssociationRequest implements Parcelable { markUsed(); return new AssociationRequest( mSingleDevice, emptyIfNull(mDeviceFilters), - mDeviceProfile, null, null, -1L); + mDeviceProfile, null, null, -1L, false); } } + // Code below generated by codegen v1.0.22. // // DO NOT MODIFY! @@ -250,6 +265,10 @@ public final class AssociationRequest implements Parcelable { * Populated by the system. * @param creationTime * The time at which his request was created + * @param skipPrompt + * Whether the user-prompt may be skipped once the device is found. + * + * Populated by the system. * @hide */ @DataClass.Generated.Member @@ -259,7 +278,8 @@ public final class AssociationRequest implements Parcelable { @Nullable @DeviceProfile String deviceProfile, @Nullable String callingPackage, @Nullable String deviceProfilePrivilegesDescription, - long creationTime) { + long creationTime, + boolean skipPrompt) { this.mSingleDevice = singleDevice; this.mDeviceFilters = deviceFilters; com.android.internal.util.AnnotationValidations.validate( @@ -270,6 +290,7 @@ public final class AssociationRequest implements Parcelable { this.mCallingPackage = callingPackage; this.mDeviceProfilePrivilegesDescription = deviceProfilePrivilegesDescription; this.mCreationTime = creationTime; + this.mSkipPrompt = skipPrompt; onConstructed(); } @@ -318,6 +339,18 @@ public final class AssociationRequest implements Parcelable { 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() { @@ -330,7 +363,8 @@ public final class AssociationRequest implements Parcelable { "deviceProfile = " + mDeviceProfile + ", " + "callingPackage = " + mCallingPackage + ", " + "deviceProfilePrivilegesDescription = " + mDeviceProfilePrivilegesDescription + ", " + - "creationTime = " + mCreationTime + + "creationTime = " + mCreationTime + ", " + + "skipPrompt = " + mSkipPrompt + " }"; } @@ -352,7 +386,8 @@ public final class AssociationRequest implements Parcelable { && Objects.equals(mDeviceProfile, that.mDeviceProfile) && Objects.equals(mCallingPackage, that.mCallingPackage) && Objects.equals(mDeviceProfilePrivilegesDescription, that.mDeviceProfilePrivilegesDescription) - && mCreationTime == that.mCreationTime; + && mCreationTime == that.mCreationTime + && mSkipPrompt == that.mSkipPrompt; } @Override @@ -368,6 +403,7 @@ public final class AssociationRequest implements Parcelable { _hash = 31 * _hash + Objects.hashCode(mCallingPackage); _hash = 31 * _hash + Objects.hashCode(mDeviceProfilePrivilegesDescription); _hash = 31 * _hash + Long.hashCode(mCreationTime); + _hash = 31 * _hash + Boolean.hashCode(mSkipPrompt); return _hash; } @@ -379,6 +415,7 @@ public final class AssociationRequest implements Parcelable { byte flg = 0; if (mSingleDevice) flg |= 0x1; + if (mSkipPrompt) flg |= 0x40; if (mDeviceProfile != null) flg |= 0x4; if (mCallingPackage != null) flg |= 0x8; if (mDeviceProfilePrivilegesDescription != null) flg |= 0x10; @@ -403,6 +440,7 @@ public final class AssociationRequest implements Parcelable { byte flg = in.readByte(); boolean singleDevice = (flg & 0x1) != 0; + boolean skipPrompt = (flg & 0x40) != 0; List<DeviceFilter<?>> deviceFilters = new ArrayList<>(); in.readParcelableList(deviceFilters, DeviceFilter.class.getClassLoader()); String deviceProfile = (flg & 0x4) == 0 ? null : in.readString(); @@ -420,6 +458,7 @@ public final class AssociationRequest implements Parcelable { this.mCallingPackage = callingPackage; this.mDeviceProfilePrivilegesDescription = deviceProfilePrivilegesDescription; this.mCreationTime = creationTime; + this.mSkipPrompt = skipPrompt; onConstructed(); } @@ -439,10 +478,10 @@ public final class AssociationRequest implements Parcelable { }; @DataClass.Generated( - time = 1614976943652L, + time = 1615252862756L, codegenVersion = "1.0.22", sourceFile = "frameworks/base/core/java/android/companion/AssociationRequest.java", - inputSignatures = "private static final java.lang.String LOG_TAG\npublic 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\nprivate @android.annotation.Nullable java.lang.String mDeviceProfilePrivilegesDescription\nprivate long mCreationTime\nprivate void onConstructed()\npublic void setCallingPackage(java.lang.String)\npublic void setDeviceProfilePrivilegesDescription(java.lang.String)\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)") + inputSignatures = "private static final java.lang.String LOG_TAG\npublic 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\nprivate @android.annotation.Nullable java.lang.String mDeviceProfilePrivilegesDescription\nprivate long mCreationTime\nprivate boolean mSkipPrompt\nprivate void onConstructed()\npublic void setCallingPackage(java.lang.String)\npublic void setDeviceProfilePrivilegesDescription(java.lang.String)\npublic void setSkipPrompt(boolean)\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() {} diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index d3ea52eb8888..62d08d0164cb 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -3727,6 +3727,23 @@ --> <string name="config_companionDeviceManagerPackage" translatable="false"></string> + <!-- A list of packages managing companion device(s) by the same manufacturers as the main + device. It will fall back to showing a prompt if the association has been called multiple + times in a short period. + Note that config_companionDeviceManagerPackage and config_companionDeviceCerts are + parallel arrays. + --> + <string-array name="config_companionDevicePackages" translatable="false"></string-array> + + <!-- A list of SHA256 Certificates managing companion device(s) by the same manufacturers as + the main device. It will fall back to showing a prompt if the association has been called + multiple times in a short period. + Note that config_companionDeviceCerts and config_companionDeviceManagerPackage are parallel + arrays. + Example: "1A:2B:3C:4D" + --> + <string-array name="config_companionDeviceCerts" translatable="false"></string-array> + <!-- The package name for the default wellbeing app. This package must be trusted, as it has the permissions to control other applications on the device. diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index b9f1e203552a..28ca4df35c8a 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -687,6 +687,8 @@ <java-symbol type="string" name="cfTemplateRegisteredTime" /> <java-symbol type="string" name="chooseActivity" /> <java-symbol type="string" name="checked" /> + <java-symbol type="array" name="config_companionDevicePackages" /> + <java-symbol type="array" name="config_companionDeviceCerts" /> <java-symbol type="string" name="config_default_dns_server" /> <java-symbol type="string" name="config_ethernet_iface_regex" /> <java-symbol type="string" name="not_checked" /> diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java index 91a6749ac42d..c1a0a9a92cc2 100644 --- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java +++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java @@ -100,6 +100,9 @@ public class CompanionDeviceActivity extends Activity { mPairButton.setOnClickListener(v -> onDeviceConfirmed(getService().mSelectedDevice)); getService().mSelectedDevice = selectedDevice; onSelectionUpdate(); + if (getRequest().isSkipPrompt()) { + onDeviceConfirmed(selectedDevice); + } } else { setContentView(R.layout.device_chooser); mPairButton = findViewById(R.id.button_pair); diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java index b1eae9edc3d4..ab85b5e5cdf6 100644 --- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java +++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java @@ -21,6 +21,7 @@ import static android.bluetooth.le.ScanSettings.CALLBACK_TYPE_ALL_MATCHES; import static android.bluetooth.le.ScanSettings.SCAN_MODE_BALANCED; import static android.content.Context.BIND_IMPORTANT; import static android.content.pm.PackageManager.MATCH_ALL; +import static android.content.pm.PackageManager.PERMISSION_GRANTED; import static com.android.internal.util.CollectionUtils.any; import static com.android.internal.util.CollectionUtils.emptyIfNull; @@ -75,6 +76,7 @@ import android.content.pm.PackageItemInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManagerInternal; import android.content.pm.ResolveInfo; +import android.content.pm.Signature; import android.content.pm.UserInfo; import android.net.NetworkPolicyManager; import android.os.Binder; @@ -101,6 +103,7 @@ import android.util.ArraySet; import android.util.AtomicFile; import android.util.ExceptionUtils; import android.util.Log; +import android.util.PackageUtils; import android.util.Slog; import android.util.SparseArray; import android.util.Xml; @@ -134,8 +137,10 @@ import java.io.IOException; import java.io.PrintWriter; import java.nio.charset.StandardCharsets; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.Date; +import java.util.HashSet; import java.util.List; import java.util.Objects; import java.util.Set; @@ -164,6 +169,9 @@ public class CompanionDeviceManagerService extends SystemService implements Bind private static final String PREF_FILE_NAME = "companion_device_preferences.xml"; private static final String PREF_KEY_AUTO_REVOKE_GRANTS_DONE = "auto_revoke_grants_done"; + private static final int ASSOCIATE_WITHOUT_PROMPT_MAX_PER_TIME_WINDOW = 5; + private static final long ASSOCIATE_WITHOUT_PROMPT_WINDOW_MS = 60 * 60 * 1000; // 60 min; + private static final String XML_TAG_ASSOCIATIONS = "associations"; private static final String XML_TAG_ASSOCIATION = "association"; private static final String XML_ATTR_PACKAGE = "package"; @@ -418,6 +426,11 @@ public class CompanionDeviceManagerService extends SystemService implements Bind mRequest = request; mCallingPackage = callingPackage; request.setCallingPackage(callingPackage); + + if (mayAssociateWithoutPrompt(callingPackage, userId)) { + Slog.i(LOG_TAG, "setSkipPrompt(true)"); + request.setSkipPrompt(true); + } callback.asBinder().linkToDeath(CompanionDeviceManagerService.this /* recipient */, 0); AndroidFuture<String> fetchProfileDescription = @@ -503,7 +516,7 @@ public class CompanionDeviceManagerService extends SystemService implements Bind private boolean callerCanManageCompanionDevices() { return getContext().checkCallingOrSelfPermission( android.Manifest.permission.MANAGE_COMPANION_DEVICES) - == PackageManager.PERMISSION_GRANTED; + == PERMISSION_GRANTED; } private void checkCallerIsSystemOr(String pkg) throws RemoteException { @@ -583,7 +596,7 @@ public class CompanionDeviceManagerService extends SystemService implements Bind boolean bypassMacPermission = getContext().getPackageManager().checkPermission( android.Manifest.permission.COMPANION_APPROVE_WIFI_CONNECTIONS, packageName) - == PackageManager.PERMISSION_GRANTED; + == PERMISSION_GRANTED; if (bypassMacPermission) { return true; } @@ -844,6 +857,72 @@ public class CompanionDeviceManagerService extends SystemService implements Bind } } + private Set<String> getSameOemPackageCerts( + String packageName, String[] oemPackages, String[] sameOemCerts) { + Set<String> sameOemPackageCerts = new HashSet<>(); + + // Assume OEM may enter same package name in the parallel string array with + // multiple ADK certs corresponding to it + for (int i = 0; i < oemPackages.length; i++) { + if (oemPackages[i].equals(packageName)) { + sameOemPackageCerts.add(sameOemCerts[i].replaceAll(":", "")); + } + } + + return sameOemPackageCerts; + } + + boolean mayAssociateWithoutPrompt(String packageName, int userId) { + String[] sameOemPackages = getContext() + .getResources() + .getStringArray(com.android.internal.R.array.config_companionDevicePackages); + if (!ArrayUtils.contains(sameOemPackages, packageName)) { + Slog.w(LOG_TAG, packageName + + " can not silently create associations due to no package found." + + " Packages from OEM: " + Arrays.toString(sameOemPackages) + ); + return false; + } + + // Throttle frequent associations + long now = System.currentTimeMillis(); + Set<Association> recentAssociations = filter( + getAllAssociations(userId, packageName), + a -> now - a.getTimeApprovedMs() < ASSOCIATE_WITHOUT_PROMPT_WINDOW_MS); + + if (recentAssociations.size() >= ASSOCIATE_WITHOUT_PROMPT_MAX_PER_TIME_WINDOW) { + Slog.w(LOG_TAG, "Too many associations. " + packageName + + " already associated " + recentAssociations.size() + + " devices within the last " + ASSOCIATE_WITHOUT_PROMPT_WINDOW_MS + + "ms: " + recentAssociations); + return false; + } + String[] sameOemCerts = getContext() + .getResources() + .getStringArray(com.android.internal.R.array.config_companionDeviceCerts); + + Signature[] signatures = mPackageManagerInternal + .getPackage(packageName).getSigningDetails().signatures; + String[] apkCerts = PackageUtils.computeSignaturesSha256Digests(signatures); + + Set<String> sameOemPackageCerts = + getSameOemPackageCerts(packageName, sameOemPackages, sameOemCerts); + + for (String cert : apkCerts) { + if (sameOemPackageCerts.contains(cert)) { + return true; + } + } + + Slog.w(LOG_TAG, packageName + + " can not silently create associations. " + packageName + + " has SHA256 certs from APK: " + Arrays.toString(apkCerts) + + " and from OEM: " + Arrays.toString(sameOemCerts) + ); + + return false; + } + private static <T> boolean containsEither(T[] array, T a, T b) { return ArrayUtils.contains(array, a) || ArrayUtils.contains(array, b); } |