Store CDM device profile and apply role when device is connected

Test: manual - ensure role privileges are granted/revoked when device is connected/disconnected
Bug: 165951651
Change-Id: Id24a4b3a3510781d9105763b1722f44583a7fd7c
This commit is contained in:
Eugene Susla
2020-12-02 12:21:36 -08:00
parent 9c3ddc9cbd
commit 151da5b2bc
4 changed files with 220 additions and 45 deletions

View File

@@ -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

View File

@@ -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() {}

View File

@@ -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() {

View File

@@ -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);
@@ -197,6 +218,18 @@ public class CompanionDeviceManagerService extends SystemService implements Bind
publishBinderService(Context.COMPANION_DEVICE_SERVICE, mImpl);
}
@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();
@@ -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());
}
}
}