Loading core/java/android/companion/AssociationRequest.java +45 −6 Original line number Diff line number Diff line Loading @@ -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(); } Loading @@ -137,6 +146,11 @@ public final class AssociationRequest implements Parcelable { mDeviceProfilePrivilegesDescription = desc; } /** @hide */ public void setSkipPrompt(boolean value) { mSkipPrompt = true; } /** @hide */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) public boolean isSingleDevice() { Loading Loading @@ -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! Loading Loading @@ -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 Loading @@ -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( Loading @@ -270,6 +290,7 @@ public final class AssociationRequest implements Parcelable { this.mCallingPackage = callingPackage; this.mDeviceProfilePrivilegesDescription = deviceProfilePrivilegesDescription; this.mCreationTime = creationTime; this.mSkipPrompt = skipPrompt; onConstructed(); } Loading Loading @@ -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() { Loading @@ -330,7 +363,8 @@ public final class AssociationRequest implements Parcelable { "deviceProfile = " + mDeviceProfile + ", " + "callingPackage = " + mCallingPackage + ", " + "deviceProfilePrivilegesDescription = " + mDeviceProfilePrivilegesDescription + ", " + "creationTime = " + mCreationTime + "creationTime = " + mCreationTime + ", " + "skipPrompt = " + mSkipPrompt + " }"; } Loading @@ -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 Loading @@ -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; } Loading @@ -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; Loading @@ -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(); Loading @@ -420,6 +458,7 @@ public final class AssociationRequest implements Parcelable { this.mCallingPackage = callingPackage; this.mDeviceProfilePrivilegesDescription = deviceProfilePrivilegesDescription; this.mCreationTime = creationTime; this.mSkipPrompt = skipPrompt; onConstructed(); } Loading @@ -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() {} Loading core/res/res/values/config.xml +17 −0 Original line number Diff line number Diff line Loading @@ -3774,6 +3774,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. Loading core/res/res/values/symbols.xml +2 −0 Original line number Diff line number Diff line Loading @@ -692,6 +692,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" /> Loading packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java +3 −0 Original line number Diff line number Diff line Loading @@ -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); Loading services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java +81 −2 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -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"; Loading Loading @@ -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 = Loading Loading @@ -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 { Loading Loading @@ -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; } Loading Loading @@ -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); } Loading Loading
core/java/android/companion/AssociationRequest.java +45 −6 Original line number Diff line number Diff line Loading @@ -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(); } Loading @@ -137,6 +146,11 @@ public final class AssociationRequest implements Parcelable { mDeviceProfilePrivilegesDescription = desc; } /** @hide */ public void setSkipPrompt(boolean value) { mSkipPrompt = true; } /** @hide */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) public boolean isSingleDevice() { Loading Loading @@ -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! Loading Loading @@ -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 Loading @@ -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( Loading @@ -270,6 +290,7 @@ public final class AssociationRequest implements Parcelable { this.mCallingPackage = callingPackage; this.mDeviceProfilePrivilegesDescription = deviceProfilePrivilegesDescription; this.mCreationTime = creationTime; this.mSkipPrompt = skipPrompt; onConstructed(); } Loading Loading @@ -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() { Loading @@ -330,7 +363,8 @@ public final class AssociationRequest implements Parcelable { "deviceProfile = " + mDeviceProfile + ", " + "callingPackage = " + mCallingPackage + ", " + "deviceProfilePrivilegesDescription = " + mDeviceProfilePrivilegesDescription + ", " + "creationTime = " + mCreationTime + "creationTime = " + mCreationTime + ", " + "skipPrompt = " + mSkipPrompt + " }"; } Loading @@ -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 Loading @@ -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; } Loading @@ -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; Loading @@ -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(); Loading @@ -420,6 +458,7 @@ public final class AssociationRequest implements Parcelable { this.mCallingPackage = callingPackage; this.mDeviceProfilePrivilegesDescription = deviceProfilePrivilegesDescription; this.mCreationTime = creationTime; this.mSkipPrompt = skipPrompt; onConstructed(); } Loading @@ -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() {} Loading
core/res/res/values/config.xml +17 −0 Original line number Diff line number Diff line Loading @@ -3774,6 +3774,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. Loading
core/res/res/values/symbols.xml +2 −0 Original line number Diff line number Diff line Loading @@ -692,6 +692,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" /> Loading
packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java +3 −0 Original line number Diff line number Diff line Loading @@ -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); Loading
services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java +81 −2 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -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"; Loading Loading @@ -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 = Loading Loading @@ -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 { Loading Loading @@ -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; } Loading Loading @@ -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); } Loading