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
diff --git a/core/java/android/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java
index c07cd52..1713a0c 100644
--- a/core/java/android/bluetooth/BluetoothAdapter.java
+++ b/core/java/android/bluetooth/BluetoothAdapter.java
@@ -3689,7 +3689,7 @@
      *
      * @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 06a3f2f..17bf11b 100644
--- a/core/java/android/companion/Association.java
+++ b/core/java/android/companion/Association.java
@@ -37,6 +37,8 @@
     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 @@
 
 
 
-    // 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 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 @@
         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 @@
         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 @@
         return "Association { " +
                 "userId = " + mUserId + ", " +
                 "deviceMacAddress = " + mDeviceMacAddress + ", " +
-                "packageName = " + mPackageName +
+                "packageName = " + mPackageName + ", " +
+                "deviceProfile = " + mDeviceProfile + ", " +
+                "keepProfilePrivilegesWhenDeviceAway = " + mKeepProfilePrivilegesWhenDeviceAway +
         " }";
     }
 
@@ -119,7 +137,9 @@
         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 @@
         _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 @@
         // 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 @@
         // 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 @@
         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 @@
     };
 
     @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 2fe351e..fd71670 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceDiscoveryService.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceDiscoveryService.java
@@ -321,7 +321,8 @@
     }
 
     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 5fedf9f..0a80b02 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 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.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.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.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 @@
 //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 @@
     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 @@
     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 @@
             @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 @@
     }
 
     @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 @@
 
             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 @@
         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 @@
         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 @@
             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 @@
                     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 @@
     @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 @@
                     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 @@
         }
     }
 
+    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 @@
                 } 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 @@
         }
     }
 
+
+    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());
+        }
+    }
 }