1 /* 2 * Copyright (C) 2019 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.car.connecteddevice; 18 19 import static com.android.car.connecteddevice.util.SafeLog.logd; 20 import static com.android.car.connecteddevice.util.SafeLog.loge; 21 import static com.android.car.connecteddevice.util.SafeLog.logw; 22 23 import static java.lang.annotation.RetentionPolicy.SOURCE; 24 25 import android.annotation.CallbackExecutor; 26 import android.annotation.IntDef; 27 import android.annotation.NonNull; 28 import android.annotation.Nullable; 29 import android.content.Context; 30 31 import com.android.car.connecteddevice.ble.BleCentralManager; 32 import com.android.car.connecteddevice.ble.BlePeripheralManager; 33 import com.android.car.connecteddevice.ble.CarBleCentralManager; 34 import com.android.car.connecteddevice.ble.CarBleManager; 35 import com.android.car.connecteddevice.ble.CarBlePeripheralManager; 36 import com.android.car.connecteddevice.ble.DeviceMessage; 37 import com.android.car.connecteddevice.model.AssociatedDevice; 38 import com.android.car.connecteddevice.model.ConnectedDevice; 39 import com.android.car.connecteddevice.storage.ConnectedDeviceStorage; 40 import com.android.car.connecteddevice.storage.ConnectedDeviceStorage.AssociatedDeviceCallback; 41 import com.android.car.connecteddevice.util.ByteUtils; 42 import com.android.car.connecteddevice.util.EventLog; 43 import com.android.car.connecteddevice.util.ThreadSafeCallbacks; 44 import com.android.internal.annotations.VisibleForTesting; 45 46 import java.lang.annotation.Retention; 47 import java.util.ArrayList; 48 import java.util.HashMap; 49 import java.util.List; 50 import java.util.Map; 51 import java.util.Set; 52 import java.util.UUID; 53 import java.util.concurrent.ConcurrentHashMap; 54 import java.util.concurrent.CopyOnWriteArraySet; 55 import java.util.concurrent.Executor; 56 import java.util.concurrent.Executors; 57 import java.util.concurrent.atomic.AtomicBoolean; 58 import java.util.function.Consumer; 59 60 /** Manager of devices connected to the car. */ 61 public class ConnectedDeviceManager { 62 63 private static final String TAG = "ConnectedDeviceManager"; 64 65 // Device name length is limited by available bytes in BLE advertisement data packet. 66 // 67 // BLE advertisement limits data packet length to 31 68 // Currently we send: 69 // - 18 bytes for 16 chars UUID: 16 bytes + 2 bytes for header; 70 // - 3 bytes for advertisement being connectable; 71 // which leaves 10 bytes. 72 // Subtracting 2 bytes used by header, we have 8 bytes for device name. 73 private static final int DEVICE_NAME_LENGTH_LIMIT = 8; 74 75 private final ConnectedDeviceStorage mStorage; 76 77 private final CarBleCentralManager mCentralManager; 78 79 private final CarBlePeripheralManager mPeripheralManager; 80 81 private final ThreadSafeCallbacks<DeviceAssociationCallback> mDeviceAssociationCallbacks = 82 new ThreadSafeCallbacks<>(); 83 84 private final ThreadSafeCallbacks<ConnectionCallback> mActiveUserConnectionCallbacks = 85 new ThreadSafeCallbacks<>(); 86 87 private final ThreadSafeCallbacks<ConnectionCallback> mAllUserConnectionCallbacks = 88 new ThreadSafeCallbacks<>(); 89 90 // deviceId -> (recipientId -> callbacks) 91 private final Map<String, Map<UUID, ThreadSafeCallbacks<DeviceCallback>>> mDeviceCallbacks = 92 new ConcurrentHashMap<>(); 93 94 // deviceId -> device 95 private final Map<String, InternalConnectedDevice> mConnectedDevices = 96 new ConcurrentHashMap<>(); 97 98 // recipientId -> (deviceId -> message bytes) 99 private final Map<UUID, Map<String, byte[]>> mRecipientMissedMessages = 100 new ConcurrentHashMap<>(); 101 102 // Recipient ids that received multiple callback registrations indicate that the recipient id 103 // has been compromised. Another party now has access the messages intended for that recipient. 104 // As a safeguard, that recipient id will be added to this list and blocked from further 105 // callback notifications. 106 private final Set<UUID> mBlacklistedRecipients = new CopyOnWriteArraySet<>(); 107 108 private final AtomicBoolean mIsConnectingToUserDevice = new AtomicBoolean(false); 109 110 private final AtomicBoolean mHasStarted = new AtomicBoolean(false); 111 112 private final int mReconnectTimeoutSeconds; 113 114 private String mNameForAssociation; 115 116 private AssociationCallback mAssociationCallback; 117 118 private MessageDeliveryDelegate mMessageDeliveryDelegate; 119 120 @Retention(SOURCE) 121 @IntDef(prefix = { "DEVICE_ERROR_" }, 122 value = { 123 DEVICE_ERROR_INVALID_HANDSHAKE, 124 DEVICE_ERROR_INVALID_MSG, 125 DEVICE_ERROR_INVALID_DEVICE_ID, 126 DEVICE_ERROR_INVALID_VERIFICATION, 127 DEVICE_ERROR_INVALID_CHANNEL_STATE, 128 DEVICE_ERROR_INVALID_ENCRYPTION_KEY, 129 DEVICE_ERROR_STORAGE_FAILURE, 130 DEVICE_ERROR_INVALID_SECURITY_KEY, 131 DEVICE_ERROR_INSECURE_RECIPIENT_ID_DETECTED, 132 DEVICE_ERROR_UNEXPECTED_DISCONNECTION 133 } 134 ) 135 public @interface DeviceError {} 136 public static final int DEVICE_ERROR_INVALID_HANDSHAKE = 0; 137 public static final int DEVICE_ERROR_INVALID_MSG = 1; 138 public static final int DEVICE_ERROR_INVALID_DEVICE_ID = 2; 139 public static final int DEVICE_ERROR_INVALID_VERIFICATION = 3; 140 public static final int DEVICE_ERROR_INVALID_CHANNEL_STATE = 4; 141 public static final int DEVICE_ERROR_INVALID_ENCRYPTION_KEY = 5; 142 public static final int DEVICE_ERROR_STORAGE_FAILURE = 6; 143 public static final int DEVICE_ERROR_INVALID_SECURITY_KEY = 7; 144 public static final int DEVICE_ERROR_INSECURE_RECIPIENT_ID_DETECTED = 8; 145 public static final int DEVICE_ERROR_UNEXPECTED_DISCONNECTION = 9; 146 ConnectedDeviceManager(@onNull Context context)147 public ConnectedDeviceManager(@NonNull Context context) { 148 this(context, new ConnectedDeviceStorage(context), new BleCentralManager(context), 149 new BlePeripheralManager(context), 150 UUID.fromString(context.getString(R.string.car_service_uuid)), 151 UUID.fromString(context.getString(R.string.car_association_service_uuid)), 152 context.getString(R.string.car_bg_mask), 153 UUID.fromString(context.getString(R.string.car_secure_write_uuid)), 154 UUID.fromString(context.getString(R.string.car_secure_read_uuid)), 155 context.getResources().getInteger(R.integer.car_reconnect_timeout_sec)); 156 } 157 ConnectedDeviceManager( @onNull Context context, @NonNull ConnectedDeviceStorage storage, @NonNull BleCentralManager bleCentralManager, @NonNull BlePeripheralManager blePeripheralManager, @NonNull UUID serviceUuid, @NonNull UUID associationServiceUuid, @NonNull String bgMask, @NonNull UUID writeCharacteristicUuid, @NonNull UUID readCharacteristicUuid, int reconnectTimeoutSeconds)158 private ConnectedDeviceManager( 159 @NonNull Context context, 160 @NonNull ConnectedDeviceStorage storage, 161 @NonNull BleCentralManager bleCentralManager, 162 @NonNull BlePeripheralManager blePeripheralManager, 163 @NonNull UUID serviceUuid, 164 @NonNull UUID associationServiceUuid, 165 @NonNull String bgMask, 166 @NonNull UUID writeCharacteristicUuid, 167 @NonNull UUID readCharacteristicUuid, 168 int reconnectTimeoutSeconds) { 169 this(storage, 170 new CarBleCentralManager(context, bleCentralManager, storage, serviceUuid, bgMask, 171 writeCharacteristicUuid, readCharacteristicUuid), 172 new CarBlePeripheralManager(blePeripheralManager, storage, associationServiceUuid, 173 writeCharacteristicUuid, readCharacteristicUuid), reconnectTimeoutSeconds); 174 } 175 176 @VisibleForTesting ConnectedDeviceManager( @onNull ConnectedDeviceStorage storage, @NonNull CarBleCentralManager centralManager, @NonNull CarBlePeripheralManager peripheralManager, int reconnectTimeoutSeconds)177 ConnectedDeviceManager( 178 @NonNull ConnectedDeviceStorage storage, 179 @NonNull CarBleCentralManager centralManager, 180 @NonNull CarBlePeripheralManager peripheralManager, 181 int reconnectTimeoutSeconds) { 182 Executor callbackExecutor = Executors.newSingleThreadExecutor(); 183 mStorage = storage; 184 mCentralManager = centralManager; 185 mPeripheralManager = peripheralManager; 186 mCentralManager.registerCallback(generateCarBleCallback(centralManager), callbackExecutor); 187 mPeripheralManager.registerCallback(generateCarBleCallback(peripheralManager), 188 callbackExecutor); 189 mStorage.setAssociatedDeviceCallback(mAssociatedDeviceCallback); 190 mReconnectTimeoutSeconds = reconnectTimeoutSeconds; 191 } 192 193 /** 194 * Start internal processes and begin discovering devices. Must be called before any 195 * connections can be made using {@link #connectToActiveUserDevice()}. 196 */ start()197 public void start() { 198 if (mHasStarted.getAndSet(true)) { 199 reset(); 200 } else { 201 logd(TAG, "Starting ConnectedDeviceManager."); 202 EventLog.onConnectedDeviceManagerStarted(); 203 } 204 // TODO (b/141312136) Start central manager 205 mPeripheralManager.start(); 206 connectToActiveUserDevice(); 207 } 208 209 /** Reset internal processes and disconnect any active connections. */ reset()210 public void reset() { 211 logd(TAG, "Resetting ConnectedDeviceManager."); 212 for (InternalConnectedDevice device : mConnectedDevices.values()) { 213 removeConnectedDevice(device.mConnectedDevice.getDeviceId(), device.mCarBleManager); 214 } 215 mPeripheralManager.stop(); 216 // TODO (b/141312136) Stop central manager 217 mIsConnectingToUserDevice.set(false); 218 } 219 220 /** Returns {@link List<ConnectedDevice>} of devices currently connected. */ 221 @NonNull getActiveUserConnectedDevices()222 public List<ConnectedDevice> getActiveUserConnectedDevices() { 223 List<ConnectedDevice> activeUserConnectedDevices = new ArrayList<>(); 224 for (InternalConnectedDevice device : mConnectedDevices.values()) { 225 if (device.mConnectedDevice.isAssociatedWithActiveUser()) { 226 activeUserConnectedDevices.add(device.mConnectedDevice); 227 } 228 } 229 logd(TAG, "Returned " + activeUserConnectedDevices.size() + " active user devices."); 230 return activeUserConnectedDevices; 231 } 232 233 /** 234 * Register a callback for triggered associated device related events. 235 * 236 * @param callback {@link DeviceAssociationCallback} to register. 237 * @param executor {@link Executor} to execute triggers on. 238 */ registerDeviceAssociationCallback(@onNull DeviceAssociationCallback callback, @NonNull @CallbackExecutor Executor executor)239 public void registerDeviceAssociationCallback(@NonNull DeviceAssociationCallback callback, 240 @NonNull @CallbackExecutor Executor executor) { 241 mDeviceAssociationCallbacks.add(callback, executor); 242 } 243 244 /** 245 * Unregister a device association callback. 246 * 247 * @param callback {@link DeviceAssociationCallback} to unregister. 248 */ unregisterDeviceAssociationCallback(@onNull DeviceAssociationCallback callback)249 public void unregisterDeviceAssociationCallback(@NonNull DeviceAssociationCallback callback) { 250 mDeviceAssociationCallbacks.remove(callback); 251 } 252 253 /** 254 * Register a callback for manager triggered connection events for only the currently active 255 * user's devices. 256 * 257 * @param callback {@link ConnectionCallback} to register. 258 * @param executor {@link Executor} to execute triggers on. 259 */ registerActiveUserConnectionCallback(@onNull ConnectionCallback callback, @NonNull @CallbackExecutor Executor executor)260 public void registerActiveUserConnectionCallback(@NonNull ConnectionCallback callback, 261 @NonNull @CallbackExecutor Executor executor) { 262 mActiveUserConnectionCallbacks.add(callback, executor); 263 } 264 265 /** 266 * Unregister a connection callback from manager. 267 * 268 * @param callback {@link ConnectionCallback} to unregister. 269 */ unregisterConnectionCallback(ConnectionCallback callback)270 public void unregisterConnectionCallback(ConnectionCallback callback) { 271 mActiveUserConnectionCallbacks.remove(callback); 272 mAllUserConnectionCallbacks.remove(callback); 273 } 274 275 /** Connect to a device for the active user if available. */ 276 @VisibleForTesting connectToActiveUserDevice()277 void connectToActiveUserDevice() { 278 Executors.defaultThreadFactory().newThread(() -> { 279 logd(TAG, "Received request to connect to active user's device."); 280 connectToActiveUserDeviceInternal(); 281 }).start(); 282 } 283 connectToActiveUserDeviceInternal()284 private void connectToActiveUserDeviceInternal() { 285 try { 286 if (mIsConnectingToUserDevice.get()) { 287 logd(TAG, "A request has already been made to connect to this user's device. " 288 + "Ignoring redundant request."); 289 return; 290 } 291 List<AssociatedDevice> userDevices = mStorage.getActiveUserAssociatedDevices(); 292 if (userDevices.isEmpty()) { 293 logw(TAG, "No devices associated with active user. Ignoring."); 294 return; 295 } 296 297 // Only currently support one device per user for fast association, so take the 298 // first one. 299 AssociatedDevice userDevice = userDevices.get(0); 300 if (!userDevice.isConnectionEnabled()) { 301 logd(TAG, "Connection is disabled on device " + userDevice + "."); 302 return; 303 } 304 if (mConnectedDevices.containsKey(userDevice.getDeviceId())) { 305 logd(TAG, "Device has already been connected. No need to attempt connection " 306 + "again."); 307 return; 308 } 309 EventLog.onStartDeviceSearchStarted(); 310 mIsConnectingToUserDevice.set(true); 311 mPeripheralManager.connectToDevice(UUID.fromString(userDevice.getDeviceId()), 312 mReconnectTimeoutSeconds); 313 } catch (Exception e) { 314 loge(TAG, "Exception while attempting connection with active user's device.", e); 315 } 316 } 317 318 /** 319 * Start the association with a new device. 320 * 321 * @param callback Callback for association events. 322 */ startAssociation(@onNull AssociationCallback callback)323 public void startAssociation(@NonNull AssociationCallback callback) { 324 mAssociationCallback = callback; 325 Executors.defaultThreadFactory().newThread(() -> { 326 logd(TAG, "Received request to start association."); 327 mPeripheralManager.startAssociation(getNameForAssociation(), 328 mInternalAssociationCallback); 329 }).start(); 330 } 331 332 /** Stop the association with any device. */ stopAssociation(@onNull AssociationCallback callback)333 public void stopAssociation(@NonNull AssociationCallback callback) { 334 if (mAssociationCallback != callback) { 335 logd(TAG, "Stop association called with unrecognized callback. Ignoring."); 336 return; 337 } 338 mAssociationCallback = null; 339 mPeripheralManager.stopAssociation(mInternalAssociationCallback); 340 } 341 342 /** 343 * Get a list of associated devices for the given user. 344 * 345 * @return Associated device list. 346 */ 347 @NonNull getActiveUserAssociatedDevices()348 public List<AssociatedDevice> getActiveUserAssociatedDevices() { 349 return mStorage.getActiveUserAssociatedDevices(); 350 } 351 352 /** Notify that the user has accepted a pairing code or any out-of-band confirmation. */ notifyOutOfBandAccepted()353 public void notifyOutOfBandAccepted() { 354 mPeripheralManager.notifyOutOfBandAccepted(); 355 } 356 357 /** 358 * Remove the associated device with the given device identifier for the current user. 359 * 360 * @param deviceId Device identifier. 361 */ removeActiveUserAssociatedDevice(@onNull String deviceId)362 public void removeActiveUserAssociatedDevice(@NonNull String deviceId) { 363 mStorage.removeAssociatedDeviceForActiveUser(deviceId); 364 disconnectDevice(deviceId); 365 } 366 367 /** 368 * Enable connection on an associated device. 369 * 370 * @param deviceId Device identifier. 371 */ enableAssociatedDeviceConnection(@onNull String deviceId)372 public void enableAssociatedDeviceConnection(@NonNull String deviceId) { 373 logd(TAG, "enableAssociatedDeviceConnection() called on " + deviceId); 374 mStorage.updateAssociatedDeviceConnectionEnabled(deviceId, 375 /* isConnectionEnabled = */ true); 376 connectToActiveUserDevice(); 377 } 378 379 /** 380 * Disable connection on an associated device. 381 * 382 * @param deviceId Device identifier. 383 */ disableAssociatedDeviceConnection(@onNull String deviceId)384 public void disableAssociatedDeviceConnection(@NonNull String deviceId) { 385 logd(TAG, "disableAssociatedDeviceConnection() called on " + deviceId); 386 mStorage.updateAssociatedDeviceConnectionEnabled(deviceId, 387 /* isConnectionEnabled = */ false); 388 disconnectDevice(deviceId); 389 } 390 disconnectDevice(String deviceId)391 private void disconnectDevice(String deviceId) { 392 InternalConnectedDevice device = mConnectedDevices.get(deviceId); 393 if (device != null) { 394 device.mCarBleManager.disconnectDevice(deviceId); 395 removeConnectedDevice(deviceId, device.mCarBleManager); 396 } 397 } 398 399 /** 400 * Register a callback for a specific device and recipient. 401 * 402 * @param device {@link ConnectedDevice} to register triggers on. 403 * @param recipientId {@link UUID} to register as recipient of. 404 * @param callback {@link DeviceCallback} to register. 405 * @param executor {@link Executor} on which to execute callback. 406 */ registerDeviceCallback(@onNull ConnectedDevice device, @NonNull UUID recipientId, @NonNull DeviceCallback callback, @NonNull @CallbackExecutor Executor executor)407 public void registerDeviceCallback(@NonNull ConnectedDevice device, @NonNull UUID recipientId, 408 @NonNull DeviceCallback callback, @NonNull @CallbackExecutor Executor executor) { 409 if (isRecipientBlacklisted(recipientId)) { 410 notifyOfBlacklisting(device, recipientId, callback, executor); 411 return; 412 } 413 logd(TAG, "New callback registered on device " + device.getDeviceId() + " for recipient " 414 + recipientId); 415 String deviceId = device.getDeviceId(); 416 Map<UUID, ThreadSafeCallbacks<DeviceCallback>> recipientCallbacks = 417 mDeviceCallbacks.computeIfAbsent(deviceId, key -> new HashMap<>()); 418 419 // Device already has a callback registered with this recipient UUID. For the 420 // protection of the user, this UUID is now blacklisted from future subscriptions 421 // and the original subscription is notified and removed. 422 if (recipientCallbacks.containsKey(recipientId)) { 423 blacklistRecipient(deviceId, recipientId); 424 notifyOfBlacklisting(device, recipientId, callback, executor); 425 return; 426 } 427 428 ThreadSafeCallbacks<DeviceCallback> newCallbacks = new ThreadSafeCallbacks<>(); 429 newCallbacks.add(callback, executor); 430 recipientCallbacks.put(recipientId, newCallbacks); 431 432 byte[] message = popMissedMessage(recipientId, device.getDeviceId()); 433 if (message != null) { 434 newCallbacks.invoke(deviceCallback -> 435 deviceCallback.onMessageReceived(device, message)); 436 } 437 } 438 439 /** 440 * Set the delegate for message delivery operations. 441 * 442 * @param delegate The {@link MessageDeliveryDelegate} to set. {@code null} to unset. 443 */ setMessageDeliveryDelegate(@ullable MessageDeliveryDelegate delegate)444 public void setMessageDeliveryDelegate(@Nullable MessageDeliveryDelegate delegate) { 445 mMessageDeliveryDelegate = delegate; 446 } 447 notifyOfBlacklisting(@onNull ConnectedDevice device, @NonNull UUID recipientId, @NonNull DeviceCallback callback, @NonNull Executor executor)448 private void notifyOfBlacklisting(@NonNull ConnectedDevice device, @NonNull UUID recipientId, 449 @NonNull DeviceCallback callback, @NonNull Executor executor) { 450 loge(TAG, "Multiple callbacks registered for recipient " + recipientId + "! Your " 451 + "recipient id is no longer secure and has been blocked from future use."); 452 executor.execute(() -> 453 callback.onDeviceError(device, DEVICE_ERROR_INSECURE_RECIPIENT_ID_DETECTED)); 454 } 455 saveMissedMessage(@onNull String deviceId, @NonNull UUID recipientId, @NonNull byte[] message)456 private void saveMissedMessage(@NonNull String deviceId, @NonNull UUID recipientId, 457 @NonNull byte[] message) { 458 // Store last message in case recipient registers callbacks in the future. 459 logd(TAG, "No recipient registered for device " + deviceId + " and recipient " 460 + recipientId + " combination. Saving message."); 461 mRecipientMissedMessages.putIfAbsent(recipientId, new HashMap<>()); 462 mRecipientMissedMessages.get(recipientId).putIfAbsent(deviceId, message); 463 } 464 465 /** 466 * Remove the last message sent for this device prior to a {@link DeviceCallback} being 467 * registered. 468 * 469 * @param recipientId Recipient's id 470 * @param deviceId Device id 471 * @return The last missed {@code byte[]} of the message, or {@code null} if no messages were 472 * missed. 473 */ 474 @Nullable popMissedMessage(@onNull UUID recipientId, @NonNull String deviceId)475 private byte[] popMissedMessage(@NonNull UUID recipientId, @NonNull String deviceId) { 476 Map<String, byte[]> missedMessages = mRecipientMissedMessages.get(recipientId); 477 if (missedMessages == null) { 478 return null; 479 } 480 481 return missedMessages.remove(deviceId); 482 } 483 484 /** 485 * Unregister callback from device events. 486 * 487 * @param device {@link ConnectedDevice} callback was registered on. 488 * @param recipientId {@link UUID} callback was registered under. 489 * @param callback {@link DeviceCallback} to unregister. 490 */ unregisterDeviceCallback(@onNull ConnectedDevice device, @NonNull UUID recipientId, @NonNull DeviceCallback callback)491 public void unregisterDeviceCallback(@NonNull ConnectedDevice device, 492 @NonNull UUID recipientId, @NonNull DeviceCallback callback) { 493 logd(TAG, "Device callback unregistered on device " + device.getDeviceId() + " for " 494 + "recipient " + recipientId + "."); 495 496 Map<UUID, ThreadSafeCallbacks<DeviceCallback>> recipientCallbacks = 497 mDeviceCallbacks.get(device.getDeviceId()); 498 if (recipientCallbacks == null) { 499 return; 500 } 501 ThreadSafeCallbacks<DeviceCallback> callbacks = recipientCallbacks.get(recipientId); 502 if (callbacks == null) { 503 return; 504 } 505 506 callbacks.remove(callback); 507 if (callbacks.size() == 0) { 508 recipientCallbacks.remove(recipientId); 509 } 510 } 511 512 /** 513 * Securely send message to a device. 514 * 515 * @param device {@link ConnectedDevice} to send the message to. 516 * @param recipientId Recipient {@link UUID}. 517 * @param message Message to send. 518 * @throws IllegalStateException Secure channel has not been established. 519 */ sendMessageSecurely(@onNull ConnectedDevice device, @NonNull UUID recipientId, @NonNull byte[] message)520 public void sendMessageSecurely(@NonNull ConnectedDevice device, @NonNull UUID recipientId, 521 @NonNull byte[] message) throws IllegalStateException { 522 sendMessage(device, recipientId, message, /* isEncrypted = */ true); 523 } 524 525 /** 526 * Send an unencrypted message to a device. 527 * 528 * @param device {@link ConnectedDevice} to send the message to. 529 * @param recipientId Recipient {@link UUID}. 530 * @param message Message to send. 531 */ sendMessageUnsecurely(@onNull ConnectedDevice device, @NonNull UUID recipientId, @NonNull byte[] message)532 public void sendMessageUnsecurely(@NonNull ConnectedDevice device, @NonNull UUID recipientId, 533 @NonNull byte[] message) { 534 sendMessage(device, recipientId, message, /* isEncrypted = */ false); 535 } 536 sendMessage(@onNull ConnectedDevice device, @NonNull UUID recipientId, @NonNull byte[] message, boolean isEncrypted)537 private void sendMessage(@NonNull ConnectedDevice device, @NonNull UUID recipientId, 538 @NonNull byte[] message, boolean isEncrypted) throws IllegalStateException { 539 String deviceId = device.getDeviceId(); 540 logd(TAG, "Sending new message to device " + deviceId + " for " + recipientId 541 + " containing " + message.length + ". Message will be sent securely: " 542 + isEncrypted + "."); 543 544 InternalConnectedDevice connectedDevice = mConnectedDevices.get(deviceId); 545 if (connectedDevice == null) { 546 loge(TAG, "Attempted to send message to unknown device " + deviceId + ". Ignoring."); 547 return; 548 } 549 550 if (isEncrypted && !connectedDevice.mConnectedDevice.hasSecureChannel()) { 551 throw new IllegalStateException("Cannot send a message securely to device that has not " 552 + "established a secure channel."); 553 } 554 555 connectedDevice.mCarBleManager.sendMessage(deviceId, 556 new DeviceMessage(recipientId, isEncrypted, message)); 557 } 558 isRecipientBlacklisted(UUID recipientId)559 private boolean isRecipientBlacklisted(UUID recipientId) { 560 return mBlacklistedRecipients.contains(recipientId); 561 } 562 blacklistRecipient(@onNull String deviceId, @NonNull UUID recipientId)563 private void blacklistRecipient(@NonNull String deviceId, @NonNull UUID recipientId) { 564 Map<UUID, ThreadSafeCallbacks<DeviceCallback>> recipientCallbacks = 565 mDeviceCallbacks.get(deviceId); 566 if (recipientCallbacks == null) { 567 // Should never happen, but null-safety check. 568 return; 569 } 570 571 ThreadSafeCallbacks<DeviceCallback> existingCallback = recipientCallbacks.get(recipientId); 572 if (existingCallback == null) { 573 // Should never happen, but null-safety check. 574 return; 575 } 576 577 InternalConnectedDevice connectedDevice = mConnectedDevices.get(deviceId); 578 if (connectedDevice != null) { 579 recipientCallbacks.get(recipientId).invoke( 580 callback -> 581 callback.onDeviceError(connectedDevice.mConnectedDevice, 582 DEVICE_ERROR_INSECURE_RECIPIENT_ID_DETECTED) 583 ); 584 } 585 586 recipientCallbacks.remove(recipientId); 587 mBlacklistedRecipients.add(recipientId); 588 } 589 590 @VisibleForTesting addConnectedDevice(@onNull String deviceId, @NonNull CarBleManager bleManager)591 void addConnectedDevice(@NonNull String deviceId, @NonNull CarBleManager bleManager) { 592 if (mConnectedDevices.containsKey(deviceId)) { 593 // Device already connected. No-op until secure channel established. 594 return; 595 } 596 logd(TAG, "New device with id " + deviceId + " connected."); 597 ConnectedDevice connectedDevice = new ConnectedDevice( 598 deviceId, 599 /* deviceName = */ null, 600 mStorage.getActiveUserAssociatedDeviceIds().contains(deviceId), 601 /* hasSecureChannel = */ false 602 ); 603 604 mConnectedDevices.put(deviceId, new InternalConnectedDevice(connectedDevice, bleManager)); 605 invokeConnectionCallbacks(connectedDevice.isAssociatedWithActiveUser(), 606 callback -> callback.onDeviceConnected(connectedDevice)); 607 } 608 609 @VisibleForTesting removeConnectedDevice(@onNull String deviceId, @NonNull CarBleManager bleManager)610 void removeConnectedDevice(@NonNull String deviceId, @NonNull CarBleManager bleManager) { 611 logd(TAG, "Device " + deviceId + " disconnected from manager " + bleManager); 612 InternalConnectedDevice connectedDevice = getConnectedDeviceForManager(deviceId, 613 bleManager); 614 615 // If disconnect happened on peripheral, open for future requests to connect. 616 if (bleManager == mPeripheralManager) { 617 mIsConnectingToUserDevice.set(false); 618 } 619 620 if (connectedDevice == null) { 621 return; 622 } 623 624 mConnectedDevices.remove(deviceId); 625 boolean isAssociated = connectedDevice.mConnectedDevice.isAssociatedWithActiveUser(); 626 invokeConnectionCallbacks(isAssociated, 627 callback -> callback.onDeviceDisconnected(connectedDevice.mConnectedDevice)); 628 629 if (isAssociated || mConnectedDevices.isEmpty()) { 630 // Try to regain connection to active user's device. 631 connectToActiveUserDevice(); 632 } 633 } 634 635 @VisibleForTesting onSecureChannelEstablished(@onNull String deviceId, @NonNull CarBleManager bleManager)636 void onSecureChannelEstablished(@NonNull String deviceId, 637 @NonNull CarBleManager bleManager) { 638 if (mConnectedDevices.get(deviceId) == null) { 639 loge(TAG, "Secure channel established on unknown device " + deviceId + "."); 640 return; 641 } 642 ConnectedDevice connectedDevice = mConnectedDevices.get(deviceId).mConnectedDevice; 643 ConnectedDevice updatedConnectedDevice = new ConnectedDevice(connectedDevice.getDeviceId(), 644 connectedDevice.getDeviceName(), connectedDevice.isAssociatedWithActiveUser(), 645 /* hasSecureChannel = */ true); 646 647 boolean notifyCallbacks = getConnectedDeviceForManager(deviceId, bleManager) != null; 648 649 // TODO (b/143088482) Implement interrupt 650 // Ignore if central already holds the active device connection and interrupt the 651 // connection. 652 653 mConnectedDevices.put(deviceId, 654 new InternalConnectedDevice(updatedConnectedDevice, bleManager)); 655 logd(TAG, "Secure channel established to " + deviceId + " . Notifying callbacks: " 656 + notifyCallbacks + "."); 657 if (notifyCallbacks) { 658 notifyAllDeviceCallbacks(deviceId, 659 callback -> callback.onSecureChannelEstablished(updatedConnectedDevice)); 660 } 661 } 662 663 @VisibleForTesting onMessageReceived(@onNull String deviceId, @NonNull DeviceMessage message)664 void onMessageReceived(@NonNull String deviceId, @NonNull DeviceMessage message) { 665 logd(TAG, "New message received from device " + deviceId + " intended for " 666 + message.getRecipient() + " containing " + message.getMessage().length 667 + " bytes."); 668 669 InternalConnectedDevice connectedDevice = mConnectedDevices.get(deviceId); 670 if (connectedDevice == null) { 671 logw(TAG, "Received message from unknown device " + deviceId + "or to unknown " 672 + "recipient " + message.getRecipient() + "."); 673 return; 674 } 675 676 if (mMessageDeliveryDelegate != null 677 && !mMessageDeliveryDelegate.shouldDeliverMessageForDevice( 678 connectedDevice.mConnectedDevice)) { 679 logw(TAG, "The message delegate has rejected this message. It will not be " 680 + "delivered to the intended recipient."); 681 return; 682 } 683 684 UUID recipientId = message.getRecipient(); 685 Map<UUID, ThreadSafeCallbacks<DeviceCallback>> deviceCallbacks = 686 mDeviceCallbacks.get(deviceId); 687 if (deviceCallbacks == null) { 688 saveMissedMessage(deviceId, recipientId, message.getMessage()); 689 return; 690 } 691 ThreadSafeCallbacks<DeviceCallback> recipientCallbacks = 692 deviceCallbacks.get(recipientId); 693 if (recipientCallbacks == null) { 694 saveMissedMessage(deviceId, recipientId, message.getMessage()); 695 return; 696 } 697 698 recipientCallbacks.invoke( 699 callback -> callback.onMessageReceived(connectedDevice.mConnectedDevice, 700 message.getMessage())); 701 } 702 703 @VisibleForTesting deviceErrorOccurred(@onNull String deviceId)704 void deviceErrorOccurred(@NonNull String deviceId) { 705 InternalConnectedDevice connectedDevice = mConnectedDevices.get(deviceId); 706 if (connectedDevice == null) { 707 logw(TAG, "Failed to establish secure channel on unknown device " + deviceId + "."); 708 return; 709 } 710 711 notifyAllDeviceCallbacks(deviceId, 712 callback -> callback.onDeviceError(connectedDevice.mConnectedDevice, 713 DEVICE_ERROR_INVALID_SECURITY_KEY)); 714 } 715 716 @VisibleForTesting onAssociationCompleted(@onNull String deviceId)717 void onAssociationCompleted(@NonNull String deviceId) { 718 InternalConnectedDevice connectedDevice = 719 getConnectedDeviceForManager(deviceId, mPeripheralManager); 720 if (connectedDevice == null) { 721 return; 722 } 723 724 // The previous device is now obsolete and should be replaced with a new one properly 725 // reflecting the state of belonging to the active user and notify features. 726 if (connectedDevice.mConnectedDevice.isAssociatedWithActiveUser()) { 727 // Device was already marked as belonging to active user. No need to reissue callbacks. 728 return; 729 } 730 removeConnectedDevice(deviceId, mPeripheralManager); 731 addConnectedDevice(deviceId, mPeripheralManager); 732 } 733 734 @NonNull getActiveUserDeviceIds()735 private List<String> getActiveUserDeviceIds() { 736 return mStorage.getActiveUserAssociatedDeviceIds(); 737 } 738 739 @Nullable getConnectedDeviceForManager(@onNull String deviceId, @NonNull CarBleManager bleManager)740 private InternalConnectedDevice getConnectedDeviceForManager(@NonNull String deviceId, 741 @NonNull CarBleManager bleManager) { 742 InternalConnectedDevice connectedDevice = mConnectedDevices.get(deviceId); 743 if (connectedDevice != null && connectedDevice.mCarBleManager == bleManager) { 744 return connectedDevice; 745 } 746 747 return null; 748 } 749 invokeConnectionCallbacks(boolean belongsToActiveUser, @NonNull Consumer<ConnectionCallback> notification)750 private void invokeConnectionCallbacks(boolean belongsToActiveUser, 751 @NonNull Consumer<ConnectionCallback> notification) { 752 logd(TAG, "Notifying connection callbacks for device belonging to active user " 753 + belongsToActiveUser + "."); 754 if (belongsToActiveUser) { 755 mActiveUserConnectionCallbacks.invoke(notification); 756 } 757 mAllUserConnectionCallbacks.invoke(notification); 758 } 759 notifyAllDeviceCallbacks(@onNull String deviceId, @NonNull Consumer<DeviceCallback> notification)760 private void notifyAllDeviceCallbacks(@NonNull String deviceId, 761 @NonNull Consumer<DeviceCallback> notification) { 762 logd(TAG, "Notifying all device callbacks for device " + deviceId + "."); 763 Map<UUID, ThreadSafeCallbacks<DeviceCallback>> deviceCallbacks = 764 mDeviceCallbacks.get(deviceId); 765 if (deviceCallbacks == null) { 766 return; 767 } 768 769 for (ThreadSafeCallbacks<DeviceCallback> callbacks : deviceCallbacks.values()) { 770 callbacks.invoke(notification); 771 } 772 } 773 774 /** 775 * Returns the name that should be used for the device during enrollment of a trusted device. 776 * 777 * <p>The returned name will be a combination of a prefix sysprop and randomized digits. 778 */ 779 @NonNull getNameForAssociation()780 private String getNameForAssociation() { 781 if (mNameForAssociation == null) { 782 mNameForAssociation = ByteUtils.generateRandomNumberString(DEVICE_NAME_LENGTH_LIMIT); 783 } 784 return mNameForAssociation; 785 } 786 787 @NonNull generateCarBleCallback(@onNull CarBleManager carBleManager)788 private CarBleManager.Callback generateCarBleCallback(@NonNull CarBleManager carBleManager) { 789 return new CarBleManager.Callback() { 790 @Override 791 public void onDeviceConnected(String deviceId) { 792 EventLog.onDeviceIdReceived(); 793 addConnectedDevice(deviceId, carBleManager); 794 } 795 796 @Override 797 public void onDeviceDisconnected(String deviceId) { 798 removeConnectedDevice(deviceId, carBleManager); 799 } 800 801 @Override 802 public void onSecureChannelEstablished(String deviceId) { 803 EventLog.onSecureChannelEstablished(); 804 ConnectedDeviceManager.this.onSecureChannelEstablished(deviceId, carBleManager); 805 } 806 807 @Override 808 public void onMessageReceived(String deviceId, DeviceMessage message) { 809 ConnectedDeviceManager.this.onMessageReceived(deviceId, message); 810 } 811 812 @Override 813 public void onSecureChannelError(String deviceId) { 814 deviceErrorOccurred(deviceId); 815 } 816 }; 817 } 818 819 private final AssociationCallback mInternalAssociationCallback = new AssociationCallback() { 820 @Override 821 public void onAssociationStartSuccess(String deviceName) { 822 if (mAssociationCallback != null) { 823 mAssociationCallback.onAssociationStartSuccess(deviceName); 824 } 825 } 826 827 @Override 828 public void onAssociationStartFailure() { 829 if (mAssociationCallback != null) { 830 mAssociationCallback.onAssociationStartFailure(); 831 } 832 } 833 834 @Override 835 public void onAssociationError(int error) { 836 if (mAssociationCallback != null) { 837 mAssociationCallback.onAssociationError(error); 838 } 839 } 840 841 @Override 842 public void onVerificationCodeAvailable(String code) { 843 if (mAssociationCallback != null) { 844 mAssociationCallback.onVerificationCodeAvailable(code); 845 } 846 } 847 848 @Override 849 public void onAssociationCompleted(String deviceId) { 850 if (mAssociationCallback != null) { 851 mAssociationCallback.onAssociationCompleted(deviceId); 852 } 853 ConnectedDeviceManager.this.onAssociationCompleted(deviceId); 854 } 855 }; 856 857 private final AssociatedDeviceCallback mAssociatedDeviceCallback = 858 new AssociatedDeviceCallback() { 859 @Override 860 public void onAssociatedDeviceAdded( 861 AssociatedDevice device) { 862 mDeviceAssociationCallbacks.invoke(callback -> 863 callback.onAssociatedDeviceAdded(device)); 864 } 865 866 @Override 867 public void onAssociatedDeviceRemoved(AssociatedDevice device) { 868 mDeviceAssociationCallbacks.invoke(callback -> 869 callback.onAssociatedDeviceRemoved(device)); 870 logd(TAG, "Successfully removed associated device " + device + "."); 871 } 872 873 @Override 874 public void onAssociatedDeviceUpdated(AssociatedDevice device) { 875 mDeviceAssociationCallbacks.invoke(callback -> 876 callback.onAssociatedDeviceUpdated(device)); 877 } 878 }; 879 880 /** Callback for triggered connection events from {@link ConnectedDeviceManager}. */ 881 public interface ConnectionCallback { 882 /** Triggered when a new device has connected. */ 883 void onDeviceConnected(@NonNull ConnectedDevice device); 884 885 /** Triggered when a device has disconnected. */ 886 void onDeviceDisconnected(@NonNull ConnectedDevice device); 887 } 888 889 /** Triggered device events for a connected device from {@link ConnectedDeviceManager}. */ 890 public interface DeviceCallback { 891 /** 892 * Triggered when secure channel has been established on a device. Encrypted messaging now 893 * available. 894 */ 895 void onSecureChannelEstablished(@NonNull ConnectedDevice device); 896 897 /** Triggered when a new message is received from a device. */ 898 void onMessageReceived(@NonNull ConnectedDevice device, @NonNull byte[] message); 899 900 /** Triggered when an error has occurred for a device. */ 901 void onDeviceError(@NonNull ConnectedDevice device, @DeviceError int error); 902 } 903 904 /** Callback for association device related events. */ 905 public interface DeviceAssociationCallback { 906 907 /** Triggered when an associated device has been added. */ 908 void onAssociatedDeviceAdded(@NonNull AssociatedDevice device); 909 910 /** Triggered when an associated device has been removed. */ 911 void onAssociatedDeviceRemoved(@NonNull AssociatedDevice device); 912 913 /** Triggered when the name of an associated device has been updated. */ 914 void onAssociatedDeviceUpdated(@NonNull AssociatedDevice device); 915 } 916 917 /** Delegate for message delivery operations. */ 918 public interface MessageDeliveryDelegate { 919 920 /** Indicate whether a message should be delivered for the specified device. */ 921 boolean shouldDeliverMessageForDevice(@NonNull ConnectedDevice device); 922 } 923 924 private static class InternalConnectedDevice { 925 private final ConnectedDevice mConnectedDevice; 926 private final CarBleManager mCarBleManager; 927 928 InternalConnectedDevice(@NonNull ConnectedDevice connectedDevice, 929 @NonNull CarBleManager carBleManager) { 930 mConnectedDevice = connectedDevice; 931 mCarBleManager = carBleManager; 932 } 933 } 934 } 935