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