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