diff options
4 files changed, 220 insertions, 45 deletions
diff --git a/core/java/android/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java index c07cd52c581e..1713a0c158c3 100644 --- a/core/java/android/bluetooth/BluetoothAdapter.java +++ b/core/java/android/bluetooth/BluetoothAdapter.java @@ -3689,7 +3689,7 @@ public final class BluetoothAdapter { * * @hide */ - public abstract class BluetoothConnectionCallback { + public abstract static class BluetoothConnectionCallback { /** * Callback triggered when a bluetooth device (classic or BLE) is connected * @param device is the connected bluetooth device diff --git a/core/java/android/companion/Association.java b/core/java/android/companion/Association.java index 06a3f2f08bab..17bf11b3d4ef 100644 --- a/core/java/android/companion/Association.java +++ b/core/java/android/companion/Association.java @@ -37,6 +37,8 @@ public final class Association implements Parcelable { private final @UserIdInt int mUserId; private final @NonNull String mDeviceMacAddress; private final @NonNull String mPackageName; + private final @Nullable String mDeviceProfile; + private final boolean mKeepProfilePrivilegesWhenDeviceAway; /** @hide */ public int getUserId() { @@ -45,7 +47,7 @@ public final class Association implements Parcelable { - // Code below generated by codegen v1.0.15. + // Code below generated by codegen v1.0.21. // // DO NOT MODIFY! // CHECKSTYLE:OFF Generated code @@ -67,7 +69,9 @@ public final class Association implements Parcelable { public Association( @UserIdInt int userId, @NonNull String deviceMacAddress, - @NonNull String packageName) { + @NonNull String packageName, + @Nullable String deviceProfile, + boolean keepProfilePrivilegesWhenDeviceAway) { this.mUserId = userId; com.android.internal.util.AnnotationValidations.validate( UserIdInt.class, null, mUserId); @@ -77,6 +81,8 @@ public final class Association implements Parcelable { this.mPackageName = packageName; com.android.internal.util.AnnotationValidations.validate( NonNull.class, null, mPackageName); + this.mDeviceProfile = deviceProfile; + this.mKeepProfilePrivilegesWhenDeviceAway = keepProfilePrivilegesWhenDeviceAway; // onConstructed(); // You can define this method to get a callback } @@ -91,6 +97,16 @@ public final class Association implements Parcelable { return mPackageName; } + @DataClass.Generated.Member + public @Nullable String getDeviceProfile() { + return mDeviceProfile; + } + + @DataClass.Generated.Member + public boolean isKeepProfilePrivilegesWhenDeviceAway() { + return mKeepProfilePrivilegesWhenDeviceAway; + } + @Override @DataClass.Generated.Member public String toString() { @@ -100,7 +116,9 @@ public final class Association implements Parcelable { return "Association { " + "userId = " + mUserId + ", " + "deviceMacAddress = " + mDeviceMacAddress + ", " + - "packageName = " + mPackageName + + "packageName = " + mPackageName + ", " + + "deviceProfile = " + mDeviceProfile + ", " + + "keepProfilePrivilegesWhenDeviceAway = " + mKeepProfilePrivilegesWhenDeviceAway + " }"; } @@ -119,7 +137,9 @@ public final class Association implements Parcelable { return true && mUserId == that.mUserId && Objects.equals(mDeviceMacAddress, that.mDeviceMacAddress) - && Objects.equals(mPackageName, that.mPackageName); + && Objects.equals(mPackageName, that.mPackageName) + && Objects.equals(mDeviceProfile, that.mDeviceProfile) + && mKeepProfilePrivilegesWhenDeviceAway == that.mKeepProfilePrivilegesWhenDeviceAway; } @Override @@ -132,6 +152,8 @@ public final class Association implements Parcelable { _hash = 31 * _hash + mUserId; _hash = 31 * _hash + Objects.hashCode(mDeviceMacAddress); _hash = 31 * _hash + Objects.hashCode(mPackageName); + _hash = 31 * _hash + Objects.hashCode(mDeviceProfile); + _hash = 31 * _hash + Boolean.hashCode(mKeepProfilePrivilegesWhenDeviceAway); return _hash; } @@ -141,9 +163,14 @@ public final class Association implements Parcelable { // You can override field parcelling by defining methods like: // void parcelFieldName(Parcel dest, int flags) { ... } + byte flg = 0; + if (mKeepProfilePrivilegesWhenDeviceAway) flg |= 0x10; + if (mDeviceProfile != null) flg |= 0x8; + dest.writeByte(flg); dest.writeInt(mUserId); dest.writeString(mDeviceMacAddress); dest.writeString(mPackageName); + if (mDeviceProfile != null) dest.writeString(mDeviceProfile); } @Override @@ -157,9 +184,12 @@ public final class Association implements Parcelable { // You can override field unparcelling by defining methods like: // static FieldType unparcelFieldName(Parcel in) { ... } + byte flg = in.readByte(); + boolean keepProfilePrivilegesWhenDeviceAway = (flg & 0x10) != 0; int userId = in.readInt(); String deviceMacAddress = in.readString(); String packageName = in.readString(); + String deviceProfile = (flg & 0x8) == 0 ? null : in.readString(); this.mUserId = userId; com.android.internal.util.AnnotationValidations.validate( @@ -170,6 +200,8 @@ public final class Association implements Parcelable { this.mPackageName = packageName; com.android.internal.util.AnnotationValidations.validate( NonNull.class, null, mPackageName); + this.mDeviceProfile = deviceProfile; + this.mKeepProfilePrivilegesWhenDeviceAway = keepProfilePrivilegesWhenDeviceAway; // onConstructed(); // You can define this method to get a callback } @@ -189,10 +221,10 @@ public final class Association implements Parcelable { }; @DataClass.Generated( - time = 1599083149942L, - codegenVersion = "1.0.15", + time = 1606940835778L, + codegenVersion = "1.0.21", sourceFile = "frameworks/base/core/java/android/companion/Association.java", - inputSignatures = "private final @android.annotation.UserIdInt int mUserId\nprivate final @android.annotation.NonNull java.lang.String mDeviceMacAddress\nprivate final @android.annotation.NonNull java.lang.String mPackageName\npublic int getUserId()\nclass Association extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genToString=true, genHiddenConstructor=true)") + inputSignatures = "private final @android.annotation.UserIdInt int mUserId\nprivate final @android.annotation.NonNull java.lang.String mDeviceMacAddress\nprivate final @android.annotation.NonNull java.lang.String mPackageName\nprivate final @android.annotation.Nullable java.lang.String mDeviceProfile\nprivate final boolean mKeepProfilePrivilegesWhenDeviceAway\npublic int getUserId()\nclass Association extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genToString=true, genHiddenConstructor=true)") @Deprecated private void __metadata() {} diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceDiscoveryService.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceDiscoveryService.java index 2fe351edb080..fd71670ee404 100644 --- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceDiscoveryService.java +++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceDiscoveryService.java @@ -321,7 +321,8 @@ public class DeviceDiscoveryService extends Service { } void onDeviceSelected(String callingPackage, String deviceAddress) { - mServiceCallback.complete(new Association(getUserId(), deviceAddress, callingPackage)); + mServiceCallback.complete(new Association( + getUserId(), deviceAddress, callingPackage, mRequest.getDeviceProfile(), false)); } void onCancel() { diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java index 5fedf9f89fc1..0a80b02a964c 100644 --- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java +++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java @@ -31,8 +31,12 @@ import static java.util.concurrent.TimeUnit.MINUTES; import android.annotation.CheckResult; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.SuppressLint; import android.app.AppOpsManager; import android.app.PendingIntent; +import android.app.role.RoleManager; +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; import android.companion.Association; import android.companion.AssociationRequest; import android.companion.CompanionDeviceManager; @@ -47,6 +51,7 @@ import android.content.pm.FeatureInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageItemInfo; import android.content.pm.PackageManager; +import android.content.pm.UserInfo; import android.net.NetworkPolicyManager; import android.os.Binder; import android.os.Environment; @@ -62,6 +67,7 @@ import android.os.ServiceManager; import android.os.ShellCallback; import android.os.ShellCommand; import android.os.UserHandle; +import android.os.UserManager; import android.provider.Settings; import android.provider.SettingsStringUtil.ComponentNameSet; import android.text.BidiFormatter; @@ -70,6 +76,7 @@ import android.util.AtomicFile; import android.util.ExceptionUtils; import android.util.Log; import android.util.Slog; +import android.util.SparseArray; import android.util.Xml; import com.android.internal.annotations.GuardedBy; @@ -116,12 +123,16 @@ import java.util.function.Function; //TODO on associate called again after configuration change -> replace old callback with new //TODO avoid leaking calling activity in IFindDeviceCallback (see PrintManager#print for example) /** @hide */ +@SuppressLint("LongLogTag") public class CompanionDeviceManagerService extends SystemService implements Binder.DeathRecipient { private static final ComponentName SERVICE_TO_BIND_TO = ComponentName.createRelative( CompanionDeviceManager.COMPANION_DEVICE_DISCOVERY_PACKAGE_NAME, ".DeviceDiscoveryService"); + // 10 min + public static final int DEVICE_DISCONNECT_PROFILE_REVOKE_DELAY_MS = 10 * 60 * 1000; + private static final boolean DEBUG = false; private static final String LOG_TAG = "CompanionDeviceManagerService"; @@ -132,6 +143,8 @@ public class CompanionDeviceManagerService extends SystemService implements Bind private static final String XML_TAG_ASSOCIATION = "association"; private static final String XML_ATTR_PACKAGE = "package"; private static final String XML_ATTR_DEVICE = "device"; + private static final String XML_ATTR_PROFILE = "profile"; + private static final String XML_ATTR_PERSISTENT_PROFILE_GRANTS = "persistent_profile_grants"; private static final String XML_FILE_NAME = "companion_device_manager_associations.xml"; private final CompanionDeviceManagerImpl mImpl; @@ -139,21 +152,29 @@ public class CompanionDeviceManagerService extends SystemService implements Bind private PowerWhitelistManager mPowerWhitelistManager; private PerUser<ServiceConnector<ICompanionDeviceDiscoveryService>> mServiceConnectors; private IAppOpsService mAppOpsManager; + private RoleManager mRoleManager; + private BluetoothAdapter mBluetoothAdapter; private IFindDeviceCallback mFindDeviceCallback; private AssociationRequest mRequest; private String mCallingPackage; private AndroidFuture<Association> mOngoingDeviceDiscovery; + private BluetoothDeviceConnectedListener mBluetoothDeviceConnectedListener = + new BluetoothDeviceConnectedListener(); + private List<String> mCurrentlyConnectedDevices = new ArrayList<>(); + private final Object mLock = new Object(); + /** userId -> [association] */ @GuardedBy("mLock") - private @Nullable Set<Association> mCachedAssociations = null; + private @Nullable SparseArray<Set<Association>> mCachedAssociations = new SparseArray<>(); public CompanionDeviceManagerService(Context context) { super(context); mImpl = new CompanionDeviceManagerImpl(); mPowerWhitelistManager = context.getSystemService(PowerWhitelistManager.class); + mRoleManager = context.getSystemService(RoleManager.class); mAppOpsManager = IAppOpsService.Stub.asInterface( ServiceManager.getService(Context.APP_OPS_SERVICE)); @@ -184,9 +205,9 @@ public class CompanionDeviceManagerService extends SystemService implements Bind @Override public void onPackageModified(String packageName) { int userId = getChangingUserId(); - if (!ArrayUtils.isEmpty(getAllAssociations(userId, packageName))) { - updateSpecialAccessPermissionForAssociatedPackage(packageName, userId); - } + forEach(getAllAssociations(userId, packageName), association -> { + updateSpecialAccessPermissionForAssociatedPackage(association); + }); } }.register(getContext(), FgThread.get().getLooper(), UserHandle.ALL, true); @@ -198,6 +219,18 @@ public class CompanionDeviceManagerService extends SystemService implements Bind } @Override + public void onBootPhase(int phase) { + if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) { + mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); + if (mBluetoothAdapter != null) { + mBluetoothAdapter.registerBluetoothConnectionCallback( + getContext().getMainExecutor(), + mBluetoothDeviceConnectedListener); + } + } + } + + @Override public void onUserUnlocking(@NonNull TargetUser user) { int userHandle = user.getUserIdentifier(); Set<Association> associations = getAllAssociations(userHandle); @@ -495,12 +528,14 @@ public class CompanionDeviceManagerService extends SystemService implements Bind fout.append("Companion Device Associations:").append('\n'); synchronized (mLock) { - forEach(mCachedAssociations, a -> { - fout.append(" ") - .append("u").append("" + a.getUserId()).append(": ") - .append(a.getPackageName()).append(" - ") - .append(a.getDeviceMacAddress()).append('\n'); - }); + for (UserInfo user : getAllUsers()) { + forEach(mCachedAssociations.get(user.id), a -> { + fout.append(" ") + .append("u").append("" + a.getUserId()).append(": ") + .append(a.getPackageName()).append(" - ") + .append(a.getDeviceMacAddress()).append('\n'); + }); + } } } } @@ -513,32 +548,34 @@ public class CompanionDeviceManagerService extends SystemService implements Bind return Binder.getCallingUid() == Process.SYSTEM_UID; } - void addAssociation(int userId, String packageName, String deviceAddress) { - addAssociation(new Association(userId, deviceAddress, packageName)); - } - void addAssociation(Association association) { - updateSpecialAccessPermissionForAssociatedPackage( - association.getPackageName(), association.getUserId()); + updateSpecialAccessPermissionForAssociatedPackage(association); recordAssociation(association); } void removeAssociation(int userId, String pkg, String deviceMacAddress) { - updateAssociations(associations -> CollectionUtils.remove(associations, - new Association(userId, deviceMacAddress, pkg))); + updateAssociations(associations -> CollectionUtils.filter(associations, association -> { + return association.getUserId() != userId + || !Objects.equals(association.getDeviceMacAddress(), deviceMacAddress) + || !Objects.equals(association.getPackageName(), pkg); + })); } - private void updateSpecialAccessPermissionForAssociatedPackage(String packageName, int userId) { - PackageInfo packageInfo = getPackageInfo(packageName, userId); + private void updateSpecialAccessPermissionForAssociatedPackage(Association association) { + PackageInfo packageInfo = getPackageInfo( + association.getPackageName(), + association.getUserId()); if (packageInfo == null) { return; } Binder.withCleanCallingIdentity(obtainRunnable(CompanionDeviceManagerService:: - updateSpecialAccessPermissionAsSystem, this, packageInfo).recycleOnUse()); + updateSpecialAccessPermissionAsSystem, this, association, packageInfo) + .recycleOnUse()); } - private void updateSpecialAccessPermissionAsSystem(PackageInfo packageInfo) { + private void updateSpecialAccessPermissionAsSystem( + Association association, PackageInfo packageInfo) { if (containsEither(packageInfo.requestedPermissions, android.Manifest.permission.RUN_IN_BACKGROUND, android.Manifest.permission.REQUEST_COMPANION_RUN_IN_BACKGROUND)) { @@ -602,10 +639,6 @@ public class CompanionDeviceManagerService extends SystemService implements Bind updateAssociations(associations -> CollectionUtils.add(associations, association)); } - private void recordAssociation(String privilegedPackage, String deviceAddress) { - recordAssociation(new Association(getCallingUserId(), deviceAddress, privilegedPackage)); - } - private void updateAssociations(Function<Set<Association>, Set<Association>> update) { updateAssociations(update, getCallingUserId()); } @@ -625,7 +658,7 @@ public class CompanionDeviceManagerService extends SystemService implements Bind if (DEBUG) { Slog.i(LOG_TAG, "Updating associations: " + old + " --> " + associations); } - mCachedAssociations = Collections.unmodifiableSet(associations); + mCachedAssociations.put(userId, Collections.unmodifiableSet(associations)); BackgroundThread.getHandler().sendMessage(PooledLambda.obtainMessage( CompanionDeviceManagerService::persistAssociations, this, associations, userId)); @@ -651,10 +684,17 @@ public class CompanionDeviceManagerService extends SystemService implements Bind xml.startTag(null, XML_TAG_ASSOCIATIONS); forEach(associations, association -> { - xml.startTag(null, XML_TAG_ASSOCIATION) + XmlSerializer tag = xml.startTag(null, XML_TAG_ASSOCIATION) .attribute(null, XML_ATTR_PACKAGE, association.getPackageName()) - .attribute(null, XML_ATTR_DEVICE, association.getDeviceMacAddress()) - .endTag(null, XML_TAG_ASSOCIATION); + .attribute(null, XML_ATTR_DEVICE, + association.getDeviceMacAddress()); + if (association.getDeviceProfile() != null) { + tag.attribute(null, XML_ATTR_PROFILE, association.getDeviceProfile()); + tag.attribute(null, XML_ATTR_PERSISTENT_PROFILE_GRANTS, + Boolean.toString( + association.isKeepProfilePrivilegesWhenDeviceAway())); + } + tag.endTag(null, XML_TAG_ASSOCIATION); }); xml.endTag(null, XML_TAG_ASSOCIATIONS); @@ -678,17 +718,21 @@ public class CompanionDeviceManagerService extends SystemService implements Bind @Nullable private Set<Association> getAllAssociations(int userId) { synchronized (mLock) { - if (mCachedAssociations == null) { - mCachedAssociations = Collections.unmodifiableSet( - emptyIfNull(readAllAssociations(userId))); + if (mCachedAssociations.get(userId) == null) { + mCachedAssociations.put(userId, Collections.unmodifiableSet( + emptyIfNull(readAllAssociations(userId)))); if (DEBUG) { Slog.i(LOG_TAG, "Read associations from disk: " + mCachedAssociations); } } - return mCachedAssociations; + return mCachedAssociations.get(userId); } } + private List<UserInfo> getAllUsers() { + return getContext().getSystemService(UserManager.class).getUsers(); + } + @Nullable private Set<Association> getAllAssociations(int userId, @Nullable String packageFilter) { return CollectionUtils.filter( @@ -714,10 +758,15 @@ public class CompanionDeviceManagerService extends SystemService implements Bind final String appPackage = parser.getAttributeValue(null, XML_ATTR_PACKAGE); final String deviceAddress = parser.getAttributeValue(null, XML_ATTR_DEVICE); + final String profile = parser.getAttributeValue(null, XML_ATTR_PROFILE); + final boolean persistentGrants = Boolean.valueOf( + parser.getAttributeValue(null, XML_ATTR_PERSISTENT_PROFILE_GRANTS)); + if (appPackage == null || deviceAddress == null) continue; result = ArrayUtils.add(result, - new Association(userId, deviceAddress, appPackage)); + new Association(userId, deviceAddress, appPackage, + profile, persistentGrants)); } return result; } catch (XmlPullParserException | IOException e) { @@ -727,6 +776,77 @@ public class CompanionDeviceManagerService extends SystemService implements Bind } } + void onDeviceConnected(String address) { + mCurrentlyConnectedDevices.add(address); + + Handler.getMain().removeCallbacksAndMessages(getDisconnectJobHandlerId(address)); + + for (UserInfo user : getAllUsers()) { + for (Association association : getAllAssociations(user.id)) { + if (Objects.equals(address, association.getDeviceMacAddress())) { + if (association.getDeviceProfile() != null) { + Log.i(LOG_TAG, "Granting role " + association.getDeviceProfile() + + " to " + association.getPackageName() + + " due to device connected: " + address); + mRoleManager.addRoleHolderAsUser( + association.getDeviceProfile(), + association.getPackageName(), + RoleManager.MANAGE_HOLDERS_FLAG_DONT_KILL_APP, + UserHandle.of(association.getUserId()), + getContext().getMainExecutor(), + success -> { + if (!success) { + Log.e(LOG_TAG, "Failed to grant device profile role " + + association.getDeviceProfile() + + " to " + association.getPackageName() + + " for user " + association.getUserId()); + } + }); + } + } + } + } + } + + void onDeviceDisconnected(String address) { + mCurrentlyConnectedDevices.remove(address); + + Handler.getMain().postDelayed(() -> { + if (!mCurrentlyConnectedDevices.contains(address)) { + for (UserInfo user : getAllUsers()) { + for (Association association : getAllAssociations(user.id)) { + if (association.getDeviceProfile() != null + && Objects.equals(address, association.getDeviceMacAddress()) + && !association.isKeepProfilePrivilegesWhenDeviceAway()) { + Log.i(LOG_TAG, "Revoking role " + association.getDeviceProfile() + + " to " + association.getPackageName() + + " due to device disconnected: " + address); + mRoleManager.removeRoleHolderAsUser( + association.getDeviceProfile(), + association.getPackageName(), + RoleManager.MANAGE_HOLDERS_FLAG_DONT_KILL_APP, + UserHandle.of(association.getUserId()), + getContext().getMainExecutor(), + success -> { + if (!success) { + Log.e(LOG_TAG, "Failed to revoke device profile role " + + association.getDeviceProfile() + + " to " + association.getPackageName() + + " for user " + association.getUserId()); + } + }); + } + } + } + } + }, getDisconnectJobHandlerId(address), DEVICE_DISCONNECT_PROFILE_REVOKE_DELAY_MS); + } + + @NonNull + private String getDisconnectJobHandlerId(String address) { + return "CDM_onDisconnected_" + address; + } + private class ShellCmd extends ShellCommand { public static final String USAGE = "help\n" + "list USER_ID\n" @@ -749,13 +869,22 @@ public class CompanionDeviceManagerService extends SystemService implements Bind } break; case "associate": { - addAssociation(getNextArgInt(), getNextArgRequired(), getNextArgRequired()); + addAssociation(new Association(getNextArgInt(), getNextArgRequired(), + getNextArgRequired(), null, false)); } break; case "disassociate": { removeAssociation(getNextArgInt(), getNextArgRequired(), getNextArgRequired()); } break; + case "simulate_connect": { + onDeviceConnected(getNextArgRequired()); + } break; + + case "simulate_disconnect": { + onDeviceDisconnected(getNextArgRequired()); + } break; + default: return handleDefaultCommands(cmd); } return 0; @@ -771,4 +900,17 @@ public class CompanionDeviceManagerService extends SystemService implements Bind } } + + private class BluetoothDeviceConnectedListener + extends BluetoothAdapter.BluetoothConnectionCallback { + @Override + public void onDeviceConnected(BluetoothDevice device) { + CompanionDeviceManagerService.this.onDeviceConnected(device.getAddress()); + } + + @Override + public void onDeviceDisconnected(BluetoothDevice device) { + CompanionDeviceManagerService.this.onDeviceDisconnected(device.getAddress()); + } + } } |