DO NOT MERGE Migration with latest changes

Bug: 149587783
Test: Association and trusted device still function
Change-Id: I961592562c5231d28a2652c8c08e3908b767413e
diff --git a/connected-device-lib/src/com/android/car/connecteddevice/ConnectedDeviceManager.java b/connected-device-lib/src/com/android/car/connecteddevice/ConnectedDeviceManager.java
index 20a86ac..52c7c5a 100644
--- a/connected-device-lib/src/com/android/car/connecteddevice/ConnectedDeviceManager.java
+++ b/connected-device-lib/src/com/android/car/connecteddevice/ConnectedDeviceManager.java
@@ -39,6 +39,7 @@
 import com.android.car.connecteddevice.storage.ConnectedDeviceStorage;
 import com.android.car.connecteddevice.storage.ConnectedDeviceStorage.AssociatedDeviceCallback;
 import com.android.car.connecteddevice.util.ByteUtils;
+import com.android.car.connecteddevice.util.EventLog;
 import com.android.car.connecteddevice.util.ThreadSafeCallbacks;
 import com.android.internal.annotations.VisibleForTesting;
 
@@ -183,6 +184,7 @@
      */
     public void start() {
         logd(TAG, "Starting ConnectedDeviceManager.");
+        EventLog.onConnectedDeviceManagerStarted();
         //mCentralManager.start();
         mPeripheralManager.start();
         connectToActiveUserDevice();
@@ -286,6 +288,7 @@
                         + "again.");
                 return;
             }
+            EventLog.onStartDeviceSearchStarted();
             mIsConnectingToUserDevice.set(true);
             mPeripheralManager.connectToDevice(UUID.fromString(userDeviceId));
         } catch (Exception e) {
@@ -334,11 +337,12 @@
      * @param deviceId Device identifier.
      */
     public void removeActiveUserAssociatedDevice(@NonNull String deviceId) {
-        if (mConnectedDevices.containsKey(deviceId)) {
-            removeConnectedDevice(deviceId, mPeripheralManager);
-            mPeripheralManager.stop();
-        }
         mStorage.removeAssociatedDeviceForActiveUser(deviceId);
+        InternalConnectedDevice device = mConnectedDevices.get(deviceId);
+        if (device != null) {
+            device.mCarBleManager.disconnectDevice(deviceId);
+            removeConnectedDevice(deviceId, device.mCarBleManager);
+        }
         logd(TAG, "Successfully removed associated device " + deviceId + ".");
     }
 
@@ -717,6 +721,7 @@
         return new CarBleManager.Callback() {
             @Override
             public void onDeviceConnected(String deviceId) {
+                EventLog.onDeviceIdReceived();
                 addConnectedDevice(deviceId, carBleManager);
             }
 
@@ -727,6 +732,7 @@
 
             @Override
             public void onSecureChannelEstablished(String deviceId) {
+                EventLog.onSecureChannelEstablished();
                 ConnectedDeviceManager.this.onSecureChannelEstablished(deviceId, carBleManager);
             }
 
diff --git a/connected-device-lib/src/com/android/car/connecteddevice/ble/BlePeripheralManager.java b/connected-device-lib/src/com/android/car/connecteddevice/ble/BlePeripheralManager.java
index 6a064d5..6d50f63 100644
--- a/connected-device-lib/src/com/android/car/connecteddevice/ble/BlePeripheralManager.java
+++ b/connected-device-lib/src/com/android/car/connecteddevice/ble/BlePeripheralManager.java
@@ -16,8 +16,6 @@
 
 package com.android.car.connecteddevice.ble;
 
-import static android.bluetooth.BluetoothProfile.GATT_SERVER;
-
 import static com.android.car.connecteddevice.util.SafeLog.logd;
 import static com.android.car.connecteddevice.util.SafeLog.loge;
 import static com.android.car.connecteddevice.util.SafeLog.logw;
@@ -49,6 +47,7 @@
 import java.util.Set;
 import java.util.UUID;
 import java.util.concurrent.CopyOnWriteArraySet;
+import java.util.concurrent.atomic.AtomicReference;
 
 /**
  * A generic class that manages BLE peripheral operations like start/stop advertising, notifying
@@ -79,13 +78,13 @@
     private final Set<Callback> mCallbacks = new CopyOnWriteArraySet<>();
     private final Set<OnCharacteristicWriteListener> mWriteListeners = new HashSet<>();
     private final Set<OnCharacteristicReadListener> mReadListeners = new HashSet<>();
+    private final AtomicReference<BluetoothGattServer> mGattServer = new AtomicReference<>();
+    private final AtomicReference<BluetoothGatt> mBluetoothGatt = new AtomicReference<>();
 
     private int mMtuSize = 20;
 
     private BluetoothManager mBluetoothManager;
     private BluetoothLeAdvertiser mAdvertiser;
-    private BluetoothGattServer mGattServer;
-    private BluetoothGatt mBluetoothGatt;
     private int mAdvertiserStartCount;
     private int mGattServerRetryStartCount;
     private BluetoothGattService mBluetoothGattService;
@@ -185,7 +184,7 @@
         mAdvertiseData = data;
         mGattServerRetryStartCount = 0;
         mBluetoothManager = (BluetoothManager) mContext.getSystemService(Context.BLUETOOTH_SERVICE);
-        mGattServer = mBluetoothManager.openGattServer(mContext, mGattServerCallback);
+        mGattServer.set(mBluetoothManager.openGattServer(mContext, mGattServerCallback));
         openGattServer();
     }
 
@@ -208,11 +207,12 @@
             @NonNull BluetoothDevice device,
             @NonNull BluetoothGattCharacteristic characteristic,
             boolean confirm) {
-        if (mGattServer == null) {
+        BluetoothGattServer gattServer = mGattServer.get();
+        if (gattServer == null) {
             return;
         }
 
-        if (!mGattServer.notifyCharacteristicChanged(device, characteristic, confirm)) {
+        if (!gattServer.notifyCharacteristicChanged(device, characteristic, confirm)) {
             loge(TAG, "notifyCharacteristicChanged failed");
         }
     }
@@ -221,17 +221,7 @@
      * Connect the Gatt server of the remote device to retrieve device name.
      */
     final void retrieveDeviceName(BluetoothDevice device) {
-        mBluetoothGatt = device.connectGatt(mContext, false, mGattCallback);
-    }
-
-    /**
-     * Returns the currently opened GATT server within this manager.
-     *
-     * @return An opened GATT server or {@code null} if none have been opened.
-     */
-    @Nullable
-    BluetoothGattServer getGattServer() {
-        return mGattServer;
+        mBluetoothGatt.compareAndSet(null, device.connectGatt(mContext, false, mGattCallback));
     }
 
     /**
@@ -247,45 +237,28 @@
         mWriteListeners.clear();
         mAdvertiser = null;
 
-        if (mGattServer == null) {
+        BluetoothGattServer gattServer = mGattServer.getAndSet(null);
+        if (gattServer == null) {
             return;
         }
-        mGattServer.clearServices();
-        try {
-            for (BluetoothDevice d : mBluetoothManager.getConnectedDevices(GATT_SERVER)) {
-                logd(TAG, "Disconnecting from " + d.getAddress());
-                mGattServer.cancelConnection(d);
-            }
-        } catch (UnsupportedOperationException e) {
-            loge(TAG, "Error getting connected devices", e);
-        } finally {
-            stopGattServer();
-        }
-    }
 
-    /**
-     * Close the GATT Server
-     */
-    void stopGattServer() {
-        if (mGattServer == null) {
-            return;
-        }
         logd(TAG, "stopGattServer");
-        if (mBluetoothGatt != null) {
-            mGattServer.cancelConnection(mBluetoothGatt.getDevice());
-            mBluetoothGatt.disconnect();
+        BluetoothGatt bluetoothGatt = mBluetoothGatt.getAndSet(null);
+        if (bluetoothGatt != null) {
+            gattServer.cancelConnection(bluetoothGatt.getDevice());
+            bluetoothGatt.disconnect();
         }
-        mGattServer.clearServices();
-        mGattServer.close();
-        mGattServer = null;
+        gattServer.clearServices();
+        gattServer.close();
     }
 
     private void openGattServer() {
         // Only open one Gatt server.
-        if (mGattServer != null) {
+        BluetoothGattServer gattServer = mGattServer.get();
+        if (gattServer != null) {
             logd(TAG, "Gatt Server created, retry count: " + mGattServerRetryStartCount);
-            mGattServer.clearServices();
-            mGattServer.addService(mBluetoothGattService);
+            gattServer.clearServices();
+            gattServer.addService(mBluetoothGattService);
             AdvertiseSettings settings =
                     new AdvertiseSettings.Builder()
                             .setAdvertiseMode(AdvertiseSettings.ADVERTISE_MODE_LOW_LATENCY)
@@ -296,7 +269,7 @@
             startAdvertisingInternally(settings, mAdvertiseData, mAdvertiseCallback);
             mGattServerRetryStartCount = 0;
         } else if (mGattServerRetryStartCount < GATT_SERVER_RETRY_LIMIT) {
-            mGattServer = mBluetoothManager.openGattServer(mContext, mGattServerCallback);
+            mGattServer.set(mBluetoothManager.openGattServer(mContext, mGattServerCallback));
             mGattServerRetryStartCount++;
             mHandler.postDelayed(() -> openGattServer(), GATT_SERVER_RETRY_DELAY_MS);
         } else {
@@ -320,11 +293,8 @@
                     BLE_RETRY_INTERVAL_MS);
             mAdvertiserStartCount += 1;
         } else {
-            loge(
-                    TAG,
-                    "Cannot start BLE Advertisement. Advertise Retry count: "
-                            + mAdvertiserStartCount,
-                    null);
+            loge(TAG, "Cannot start BLE Advertisement. Advertise Retry count: "
+                            + mAdvertiserStartCount);
         }
     }
 
@@ -365,7 +335,11 @@
                         boolean responseNeeded,
                         int offset,
                         byte[] value) {
-                    mGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, offset,
+                    BluetoothGattServer gattServer = mGattServer.get();
+                    if (gattServer == null) {
+                        return;
+                    }
+                    gattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, offset,
                             value);
                     for (OnCharacteristicWriteListener listener : mWriteListeners) {
                         listener.onCharacteristicWrite(device, characteristic, value);
@@ -385,8 +359,11 @@
                             + descriptor.getUuid()
                             + "; value: "
                             + ByteUtils.byteArrayToHexString(value));
-
-                    mGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, offset,
+                    BluetoothGattServer gattServer = mGattServer.get();
+                    if (gattServer == null) {
+                        return;
+                    }
+                    gattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, offset,
                             value);
                 }
 
@@ -425,7 +402,11 @@
                     switch (newState) {
                         case BluetoothProfile.STATE_CONNECTED:
                             logd(TAG, "Gatt connected");
-                            mBluetoothGatt.discoverServices();
+                            BluetoothGatt bluetoothGatt = mBluetoothGatt.get();
+                            if (bluetoothGatt == null) {
+                                break;
+                            }
+                            bluetoothGatt.discoverServices();
                             break;
                         case BluetoothProfile.STATE_DISCONNECTED:
                             logd(TAG, "Gatt Disconnected");
@@ -439,7 +420,11 @@
                 @Override
                 public void onServicesDiscovered(BluetoothGatt gatt, int status) {
                     logd(TAG, "Gatt Services Discovered");
-                    BluetoothGattService gapService = mBluetoothGatt.getService(
+                    BluetoothGatt bluetoothGatt = mBluetoothGatt.get();
+                    if (bluetoothGatt == null) {
+                        return;
+                    }
+                    BluetoothGattService gapService = bluetoothGatt.getService(
                             GENERIC_ACCESS_PROFILE_UUID);
                     if (gapService == null) {
                         loge(TAG, "Generic Access Service is null.");
@@ -451,7 +436,7 @@
                         loge(TAG, "Device Name Characteristic is null.");
                         return;
                     }
-                    mBluetoothGatt.readCharacteristic(deviceNameCharacteristic);
+                    bluetoothGatt.readCharacteristic(deviceNameCharacteristic);
                 }
 
                 @Override
diff --git a/connected-device-lib/src/com/android/car/connecteddevice/ble/CarBleCentralManager.java b/connected-device-lib/src/com/android/car/connecteddevice/ble/CarBleCentralManager.java
index 00b8113..a9168a8 100644
--- a/connected-device-lib/src/com/android/car/connecteddevice/ble/CarBleCentralManager.java
+++ b/connected-device-lib/src/com/android/car/connecteddevice/ble/CarBleCentralManager.java
@@ -57,6 +57,8 @@
     private static final UUID CHARACTERISTIC_CONFIG =
             UUID.fromString("00002902-0000-1000-8000-00805f9b34fb");
 
+    private static final int STATUS_FORCED_DISCONNECT = -1;
+
     private final ScanSettings mScanSettings = new ScanSettings.Builder()
             .setCallbackType(ScanSettings.CALLBACK_TYPE_ALL_MATCHES)
             .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)
@@ -117,6 +119,17 @@
         mBleCentralManager.stopScanning();
     }
 
+    @Override
+    public void disconnectDevice(String deviceId) {
+        logd(TAG, "Request to disconnect from device " + deviceId + ".");
+        BleDevice device = getConnectedDevice(deviceId);
+        if (device == null) {
+            return;
+        }
+
+        deviceDisconnected(device, STATUS_FORCED_DISCONNECT);
+    }
+
     private void ignoreDevice(@NonNull BleDevice device) {
         mIgnoredDevices.add(device);
     }
diff --git a/connected-device-lib/src/com/android/car/connecteddevice/ble/CarBleManager.java b/connected-device-lib/src/com/android/car/connecteddevice/ble/CarBleManager.java
index d649d10..a00ff18 100644
--- a/connected-device-lib/src/com/android/car/connecteddevice/ble/CarBleManager.java
+++ b/connected-device-lib/src/com/android/car/connecteddevice/ble/CarBleManager.java
@@ -181,6 +181,9 @@
         mConnectedDevices.remove(device);
     }
 
+    /** Disconnect the provided device from this manager. */
+    public abstract void disconnectDevice(@NonNull String deviceId);
+
     /** State for a connected device. */
     enum BleDeviceState {
         CONNECTING,
diff --git a/connected-device-lib/src/com/android/car/connecteddevice/ble/CarBlePeripheralManager.java b/connected-device-lib/src/com/android/car/connecteddevice/ble/CarBlePeripheralManager.java
index 4745fdb..289ca68 100644
--- a/connected-device-lib/src/com/android/car/connecteddevice/ble/CarBlePeripheralManager.java
+++ b/connected-device-lib/src/com/android/car/connecteddevice/ble/CarBlePeripheralManager.java
@@ -35,6 +35,7 @@
 import com.android.car.connecteddevice.AssociationCallback;
 import com.android.car.connecteddevice.model.AssociatedDevice;
 import com.android.car.connecteddevice.storage.ConnectedDeviceStorage;
+import com.android.car.connecteddevice.util.EventLog;
 import com.android.internal.annotations.VisibleForTesting;
 
 import java.util.UUID;
@@ -123,6 +124,15 @@
         reset();
     }
 
+    @Override
+    public void disconnectDevice(@NonNull String deviceId) {
+        BleDevice connectedDevice = getConnectedDevice();
+        if (connectedDevice == null || !deviceId.equals(connectedDevice.mDeviceId)) {
+            return;
+        }
+        reset();
+    }
+
     private void reset() {
         resetBluetoothAdapterName();
         mClientDeviceAddress = null;
@@ -136,6 +146,7 @@
     public void connectToDevice(@NonNull UUID deviceId) {
         for (BleDevice device : mConnectedDevices) {
             if (UUID.fromString(device.mDeviceId).equals(deviceId)) {
+                logd(TAG, "Already connected to device " + deviceId + ".");
                 // Already connected to this device. Ignore requests to connect again.
                 return;
             }
@@ -151,6 +162,7 @@
                 logd(TAG, "Successfully started advertising for device " + deviceId + ".");
             }
         };
+        mBlePeripheralManager.unregisterCallback(mAssociationPeripheralCallback);
         mBlePeripheralManager.registerCallback(mReconnectPeripheralCallback);
         startAdvertising(deviceId, mAdvertiseCallback, /* includeDeviceName = */ false);
     }
@@ -180,6 +192,7 @@
         adapter.setName(nameForAssociation);
         logd(TAG, "Changing bluetooth adapter name from " + mOriginalBluetoothName + " to "
                 + nameForAssociation + ".");
+        mBlePeripheralManager.unregisterCallback(mReconnectPeripheralCallback);
         mBlePeripheralManager.registerCallback(mAssociationPeripheralCallback);
         mAdvertiseCallback = new AdvertiseCallback() {
             @Override
@@ -298,6 +311,7 @@
     }
 
     private void addConnectedDevice(BluetoothDevice device, boolean isReconnect) {
+        EventLog.onDeviceConnected();
         mBlePeripheralManager.stopAdvertising(mAdvertiseCallback);
         mClientDeviceAddress = device.getAddress();
         mClientDeviceName = device.getName();
@@ -354,14 +368,17 @@
                 public void onRemoteDeviceDisconnected(BluetoothDevice device) {
                     String deviceId = null;
                     BleDevice connectedDevice = getConnectedDevice(device);
+                    // Reset before invoking callbacks to avoid a race condition with reconnect
+                    // logic.
+                    reset();
                     if (connectedDevice != null) {
                         deviceId = connectedDevice.mDeviceId;
                     }
                     final String finalDeviceId = deviceId;
                     if (finalDeviceId != null) {
+                        logd(TAG, "Connected device " + finalDeviceId + " disconnected.");
                         mCallbacks.invoke(callback -> callback.onDeviceDisconnected(finalDeviceId));
                     }
-                    reset();
                 }
             };
 
@@ -406,11 +423,13 @@
                 @Override
                 public void onRemoteDeviceDisconnected(BluetoothDevice device) {
                     BleDevice connectedDevice = getConnectedDevice(device);
+                    // Reset before invoking callbacks to avoid a race condition with reconnect
+                    // logic.
+                    reset();
                     if (connectedDevice != null && connectedDevice.mDeviceId != null) {
                         mCallbacks.invoke(callback -> callback.onDeviceDisconnected(
                                 connectedDevice.mDeviceId));
                     }
-                    reset();
                 }
             };
 
diff --git a/connected-device-lib/src/com/android/car/connecteddevice/util/EventLog.java b/connected-device-lib/src/com/android/car/connecteddevice/util/EventLog.java
new file mode 100644
index 0000000..5dcd829
--- /dev/null
+++ b/connected-device-lib/src/com/android/car/connecteddevice/util/EventLog.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.connecteddevice.util;
+
+import static com.android.car.connecteddevice.util.SafeLog.logi;
+
+import com.android.car.connecteddevice.ConnectedDeviceManager;
+
+/** Logging class for collecting metrics. */
+public class EventLog {
+
+    private static final String TAG = "ConnectedDeviceEvent";
+
+    private EventLog() { }
+
+    /** Mark in log that the service has started. */
+    public static void onServiceStarted() {
+        logi(TAG, "SERVICE_STARTED");
+    }
+
+    /** Mark in log that the {@link ConnectedDeviceManager} has started. */
+    public static void onConnectedDeviceManagerStarted() {
+        logi(TAG, "CONNECTED_DEVICE_MANAGER_STARTED");
+    }
+
+    /** Mark in the log that BLE is on. */
+    public static void onBleOn() {
+        logi(TAG, "BLE_ON");
+    }
+
+    /** Mark in the log that a search for the user's device has started. */
+    public static void onStartDeviceSearchStarted() {
+        logi(TAG, "SEARCHING_FOR_DEVICE");
+    }
+
+
+    /** Mark in the log that a device connected. */
+    public static void onDeviceConnected() {
+        logi(TAG, "DEVICE_CONNECTED");
+    }
+
+    /** Mark in the log that the device has sent its id. */
+    public static void onDeviceIdReceived() {
+        logi(TAG, "RECEIVED_DEVICE_ID");
+    }
+
+    /** Mark in the log that a secure channel has been established with a device. */
+    public static void onSecureChannelEstablished() {
+        logi(TAG, "SECURE_CHANNEL_ESTABLISHED");
+    }
+}
diff --git a/connected-device-lib/src/com/android/car/connecteddevice/util/IdManager.kt b/connected-device-lib/src/com/android/car/connecteddevice/util/IdManager.kt
deleted file mode 100644
index 91d06b7..0000000
--- a/connected-device-lib/src/com/android/car/connecteddevice/util/IdManager.kt
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.car.connecteddevice.util
-
-import com.android.internal.annotations.GuardedBy
-
-/** Class for managing unique numeric ids. */
-internal class IdManager {
-
-    private val lock = object
-    @Volatile
-    @GuardedBy("lock")
-    private var nextVal = 0
-
-    private var openVals = 0
-
-    /**
-     * Returns the next available id from the pool and reserves it from future use until released.
-     */
-    fun reserve(): Int {
-        synchronized(lock) {
-            return nextVal++
-        }
-    }
-
-    /** Release the [value] back to id pool. */
-    fun releaseReservation(value: Int) {
-        synchronized(lock) {
-            if (value == nextVal - 1) {
-                nextVal = value
-            } else {
-                openVals++
-            }
-            if (nextVal == openVals) {
-                nextVal = 0
-                openVals = 0
-            }
-        }
-    }
-}
\ No newline at end of file
diff --git a/connected-device-lib/tests/unit/src/com/android/car/connecteddevice/ConnectedDeviceManagerTest.java b/connected-device-lib/tests/unit/src/com/android/car/connecteddevice/ConnectedDeviceManagerTest.java
index c335852..b365332 100644
--- a/connected-device-lib/tests/unit/src/com/android/car/connecteddevice/ConnectedDeviceManagerTest.java
+++ b/connected-device-lib/tests/unit/src/com/android/car/connecteddevice/ConnectedDeviceManagerTest.java
@@ -23,9 +23,9 @@
 
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.atMost;
 import static org.mockito.Mockito.mockitoSession;
 import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.timeout;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
@@ -542,7 +542,8 @@
         mConnectedDeviceManager.removeConnectedDevice(deviceId, mMockPeripheralManager);
         Thread.sleep(100);  // Async process so need to allow it time to complete.
         // ConnectedDeviceManager.start() also invokes connectToDevice(), so expect # of calls = 2.
-        verify(mMockPeripheralManager, atMost(2)).connectToDevice(eq(UUID.fromString(deviceId)));
+        verify(mMockPeripheralManager, timeout(1000).times(2))
+                .connectToDevice(eq(UUID.fromString(deviceId)));
     }
 
     @Test
@@ -554,9 +555,23 @@
                 Collections.singletonList(userDeviceId));
         mConnectedDeviceManager.addConnectedDevice(deviceId, mMockPeripheralManager);
         mConnectedDeviceManager.removeConnectedDevice(deviceId, mMockPeripheralManager);
-        Thread.sleep(100);  // Async process so need to allow it time to complete.
         // ConnectedDeviceManager.start() invokes connectToDevice(), so expect # of calls = 1.
-        verify(mMockPeripheralManager).connectToDevice(eq(UUID.fromString(userDeviceId)));
+        verify(mMockPeripheralManager, timeout(1000))
+                .connectToDevice(eq(UUID.fromString(userDeviceId)));
+    }
+
+    @Test
+    public void removeActiveUserAssociatedDevice_deletesAssociatedDeviceFromStorage() {
+        String deviceId = UUID.randomUUID().toString();
+        mConnectedDeviceManager.removeActiveUserAssociatedDevice(deviceId);
+        verify(mMockStorage).removeAssociatedDeviceForActiveUser(deviceId);
+    }
+
+    @Test
+    public void removeActiveUserAssociatedDevice_disconnectsIfConnected() {
+        String deviceId = connectNewDevice(mMockPeripheralManager);
+        mConnectedDeviceManager.removeActiveUserAssociatedDevice(deviceId);
+        verify(mMockPeripheralManager).disconnectDevice(deviceId);
     }
 
     @NonNull