• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2021 HIMSA II K/S - www.himsa.com.
3  * Represented by EHIMA - www.ehima.com
4  *
5  * Licensed under the Apache License, Version 2.0 (the "License");
6  * you may not use this file except in compliance with the License.
7  * You may obtain a copy of the License at
8  *
9  *      http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  */
17 
18 package com.android.bluetooth.csip;
19 
20 import static android.Manifest.permission.BLUETOOTH_CONNECT;
21 
22 import static com.android.bluetooth.Utils.enforceBluetoothPrivilegedPermission;
23 
24 import android.annotation.CallbackExecutor;
25 import android.annotation.NonNull;
26 import android.annotation.Nullable;
27 import android.bluetooth.BluetoothCsipSetCoordinator;
28 import android.bluetooth.BluetoothDevice;
29 import android.bluetooth.BluetoothProfile;
30 import android.bluetooth.BluetoothStatusCodes;
31 import android.bluetooth.BluetoothUuid;
32 import android.bluetooth.IBluetoothCsipSetCoordinator;
33 import android.bluetooth.IBluetoothCsipSetCoordinatorCallback;
34 import android.bluetooth.IBluetoothCsipSetCoordinatorLockCallback;
35 import android.content.AttributionSource;
36 import android.content.BroadcastReceiver;
37 import android.content.Context;
38 import android.content.Intent;
39 import android.content.IntentFilter;
40 import android.os.HandlerThread;
41 import android.os.ParcelUuid;
42 import android.os.RemoteException;
43 import android.sysprop.BluetoothProperties;
44 import android.util.Log;
45 import android.util.Pair;
46 
47 import com.android.bluetooth.Utils;
48 import com.android.bluetooth.btservice.AdapterService;
49 import com.android.bluetooth.btservice.ProfileService;
50 import com.android.bluetooth.btservice.storage.DatabaseManager;
51 import com.android.internal.annotations.VisibleForTesting;
52 import com.android.modules.utils.SynchronousResultReceiver;
53 
54 import java.util.ArrayList;
55 import java.util.Collections;
56 import java.util.HashMap;
57 import java.util.List;
58 import java.util.Map;
59 import java.util.Objects;
60 import java.util.UUID;
61 import java.util.concurrent.ConcurrentHashMap;
62 import java.util.concurrent.Executor;
63 import java.util.stream.Collectors;
64 
65 /**
66  * Provides Bluetooth CSIP Set Coordinator profile, as a service.
67  * @hide
68  */
69 public class CsipSetCoordinatorService extends ProfileService {
70     private static final boolean DBG = false;
71     private static final String TAG = "CsipSetCoordinatorService";
72 
73     // Timeout for state machine thread join, to prevent potential ANR.
74     private static final int SM_THREAD_JOIN_TIMEOUT_MS = 1000;
75 
76     // Upper limit of all CSIP devices: Bonded or Connected
77     private static final int MAX_CSIS_STATE_MACHINES = 10;
78     private static CsipSetCoordinatorService sCsipSetCoordinatorService;
79 
80     private AdapterService mAdapterService;
81     private DatabaseManager mDatabaseManager;
82     private HandlerThread mStateMachinesThread;
83     private BluetoothDevice mPreviousAudioDevice;
84 
85     @VisibleForTesting CsipSetCoordinatorNativeInterface mCsipSetCoordinatorNativeInterface;
86 
87     private final Map<BluetoothDevice, CsipSetCoordinatorStateMachine> mStateMachines =
88             new HashMap<>();
89 
90     private final Map<Integer, ParcelUuid> mGroupIdToUuidMap = new HashMap<>();
91     private final Map<BluetoothDevice, Map<Integer, Integer>> mDeviceGroupIdRankMap =
92             new ConcurrentHashMap<>();
93     private final Map<Integer, Integer> mGroupIdToGroupSize = new HashMap<>();
94     private final Map<ParcelUuid, Map<Executor, IBluetoothCsipSetCoordinatorCallback>> mCallbacks =
95             new HashMap<>();
96     private final Map<Integer, Pair<UUID, IBluetoothCsipSetCoordinatorLockCallback>> mLocks =
97             new ConcurrentHashMap<>();
98 
99     private BroadcastReceiver mBondStateChangedReceiver;
100     private BroadcastReceiver mConnectionStateChangedReceiver;
101 
isEnabled()102     public static boolean isEnabled() {
103         return BluetoothProperties.isProfileCsipSetCoordinatorEnabled().orElse(false);
104     }
105 
106     @Override
initBinder()107     protected IProfileServiceBinder initBinder() {
108         return new BluetoothCsisBinder(this);
109     }
110 
111     @Override
create()112     protected void create() {
113         if (DBG) {
114             Log.d(TAG, "create()");
115         }
116     }
117 
118     @Override
start()119     protected boolean start() {
120         if (DBG) {
121             Log.d(TAG, "start()");
122         }
123         if (sCsipSetCoordinatorService != null) {
124             throw new IllegalStateException("start() called twice");
125         }
126 
127         // Get AdapterService, DatabaseManager, CsipSetCoordinatorNativeInterface.
128         // None of them can be null.
129         mAdapterService = Objects.requireNonNull(AdapterService.getAdapterService(),
130                 "AdapterService cannot be null when CsipSetCoordinatorService starts");
131         mDatabaseManager = Objects.requireNonNull(mAdapterService.getDatabase(),
132                 "DatabaseManager cannot be null when CsipSetCoordinatorService starts");
133         mCsipSetCoordinatorNativeInterface = Objects.requireNonNull(
134                 CsipSetCoordinatorNativeInterface.getInstance(),
135                 "CsipSetCoordinatorNativeInterface cannot be null when"
136                 .concat("CsipSetCoordinatorService starts"));
137 
138         // Start handler thread for state machines
139         mStateMachines.clear();
140         mStateMachinesThread = new HandlerThread("CsipSetCoordinatorService.StateMachines");
141         mStateMachinesThread.start();
142 
143         // Setup broadcast receivers
144         IntentFilter filter = new IntentFilter();
145         filter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
146         mBondStateChangedReceiver = new BondStateChangedReceiver();
147         registerReceiver(mBondStateChangedReceiver, filter);
148         filter = new IntentFilter();
149         filter.addAction(BluetoothCsipSetCoordinator.ACTION_CSIS_CONNECTION_STATE_CHANGED);
150         mConnectionStateChangedReceiver = new ConnectionStateChangedReceiver();
151         registerReceiver(mConnectionStateChangedReceiver, filter);
152 
153         // Mark service as started
154         setCsipSetCoordinatorService(this);
155 
156         // Initialize native interface
157         mCsipSetCoordinatorNativeInterface.init();
158         mAdapterService.notifyActivityAttributionInfo(getAttributionSource(),
159                 AdapterService.ACTIVITY_ATTRIBUTION_NO_ACTIVE_DEVICE_ADDRESS);
160 
161         return true;
162     }
163 
164     @Override
stop()165     protected boolean stop() {
166         if (DBG) {
167             Log.d(TAG, "stop()");
168         }
169         if (sCsipSetCoordinatorService == null) {
170             Log.w(TAG, "stop() called before start()");
171             return true;
172         }
173 
174         mAdapterService.notifyActivityAttributionInfo(getAttributionSource(),
175                 AdapterService.ACTIVITY_ATTRIBUTION_NO_ACTIVE_DEVICE_ADDRESS);
176         // Cleanup native interface
177         mCsipSetCoordinatorNativeInterface.cleanup();
178         mCsipSetCoordinatorNativeInterface = null;
179 
180         // Mark service as stopped
181         setCsipSetCoordinatorService(null);
182 
183         // Unregister broadcast receivers
184         unregisterReceiver(mBondStateChangedReceiver);
185         mBondStateChangedReceiver = null;
186         unregisterReceiver(mConnectionStateChangedReceiver);
187         mConnectionStateChangedReceiver = null;
188 
189         // Destroy state machines and stop handler thread
190         synchronized (mStateMachines) {
191             for (CsipSetCoordinatorStateMachine sm : mStateMachines.values()) {
192                 sm.doQuit();
193                 sm.cleanup();
194             }
195             mStateMachines.clear();
196         }
197 
198         if (mStateMachinesThread != null) {
199             try {
200                 mStateMachinesThread.quitSafely();
201                 mStateMachinesThread.join(SM_THREAD_JOIN_TIMEOUT_MS);
202                 mStateMachinesThread = null;
203             } catch (InterruptedException e) {
204                 // Do not rethrow as we are shutting down anyway
205             }
206         }
207 
208         mDeviceGroupIdRankMap.clear();
209         mCallbacks.clear();
210         mGroupIdToGroupSize.clear();
211         mGroupIdToUuidMap.clear();
212 
213         mLocks.clear();
214 
215         // Clear AdapterService, CsipSetCoordinatorNativeInterface
216         mCsipSetCoordinatorNativeInterface = null;
217         mAdapterService = null;
218 
219         return true;
220     }
221 
222     @Override
cleanup()223     protected void cleanup() {
224         if (DBG) {
225             Log.d(TAG, "cleanup()");
226         }
227     }
228 
229     /**
230      * Get the CsipSetCoordinatorService instance
231      * @return CsipSetCoordinatorService instance
232      */
getCsipSetCoordinatorService()233     public static synchronized CsipSetCoordinatorService getCsipSetCoordinatorService() {
234         if (sCsipSetCoordinatorService == null) {
235             Log.w(TAG, "getCsipSetCoordinatorService(): service is NULL");
236             return null;
237         }
238 
239         if (!sCsipSetCoordinatorService.isAvailable()) {
240             Log.w(TAG, "getCsipSetCoordinatorService(): service is not available");
241             return null;
242         }
243         return sCsipSetCoordinatorService;
244     }
245 
setCsipSetCoordinatorService( CsipSetCoordinatorService instance)246     private static synchronized void setCsipSetCoordinatorService(
247             CsipSetCoordinatorService instance) {
248         if (DBG) {
249             Log.d(TAG, "setCsipSetCoordinatorService(): set to: " + instance);
250         }
251         sCsipSetCoordinatorService = instance;
252     }
253 
254     /**
255      * Connect the given Bluetooth device.
256      *
257      * @return true if connection is successful, false otherwise.
258      */
connect(BluetoothDevice device)259     public boolean connect(BluetoothDevice device) {
260         enforceCallingOrSelfPermission(BLUETOOTH_PRIVILEGED, "Need BLUETOOTH_PRIVILEGED permission");
261         if (DBG) {
262             Log.d(TAG, "connect(): " + device);
263         }
264         if (device == null) {
265             return false;
266         }
267 
268         if (getConnectionPolicy(device) == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN) {
269             return false;
270         }
271         ParcelUuid[] featureUuids = mAdapterService.getRemoteUuids(device);
272         if (!Utils.arrayContains(featureUuids, BluetoothUuid.COORDINATED_SET)) {
273             Log.e(TAG, "Cannot connect to " + device + " : Remote does not have CSIS UUID");
274             return false;
275         }
276 
277         synchronized (mStateMachines) {
278             CsipSetCoordinatorStateMachine smConnect = getOrCreateStateMachine(device);
279             if (smConnect == null) {
280                 Log.e(TAG, "Cannot connect to " + device + " : no state machine");
281                 return false;
282             }
283             smConnect.sendMessage(CsipSetCoordinatorStateMachine.CONNECT);
284         }
285 
286         return true;
287     }
288 
289     /**
290      * Disconnect the given Bluetooth device.
291      *
292      * @return true if disconnect is successful, false otherwise.
293      */
disconnect(BluetoothDevice device)294     public boolean disconnect(BluetoothDevice device) {
295         enforceCallingOrSelfPermission(BLUETOOTH_PRIVILEGED, "Need BLUETOOTH_PRIVILEGED permission");
296         if (DBG) {
297             Log.d(TAG, "disconnect(): " + device);
298         }
299         if (device == null) {
300             return false;
301         }
302         synchronized (mStateMachines) {
303             CsipSetCoordinatorStateMachine sm = getOrCreateStateMachine(device);
304             if (sm != null) {
305                 sm.sendMessage(CsipSetCoordinatorStateMachine.DISCONNECT);
306             }
307         }
308 
309         return true;
310     }
311 
getConnectedDevices()312     public List<BluetoothDevice> getConnectedDevices() {
313         enforceCallingOrSelfPermission(BLUETOOTH_CONNECT, "Need BLUETOOTH_CONNECT permission");
314         synchronized (mStateMachines) {
315             List<BluetoothDevice> devices = new ArrayList<>();
316             for (CsipSetCoordinatorStateMachine sm : mStateMachines.values()) {
317                 if (sm.isConnected()) {
318                     devices.add(sm.getDevice());
319                 }
320             }
321             return devices;
322         }
323     }
324 
325     /**
326      * Check whether can connect to a peer device.
327      * The check considers a number of factors during the evaluation.
328      *
329      * @param device the peer device to connect to
330      * @return true if connection is allowed, otherwise false
331      */
332     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
okToConnect(BluetoothDevice device)333     public boolean okToConnect(BluetoothDevice device) {
334         // Check if this is an incoming connection in Quiet mode.
335         if (mAdapterService.isQuietModeEnabled()) {
336             Log.e(TAG, "okToConnect: cannot connect to " + device + " : quiet mode enabled");
337             return false;
338         }
339         // Check connectionPolicy and accept or reject the connection.
340         int connectionPolicy = getConnectionPolicy(device);
341         int bondState = mAdapterService.getBondState(device);
342         // Allow this connection only if the device is bonded. Any attempt to connect while
343         // bonding would potentially lead to an unauthorized connection.
344         if (bondState != BluetoothDevice.BOND_BONDED) {
345             Log.w(TAG, "okToConnect: return false, bondState=" + bondState);
346             return false;
347         } else if (connectionPolicy != BluetoothProfile.CONNECTION_POLICY_UNKNOWN
348                 && connectionPolicy != BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
349             // Otherwise, reject the connection if connectionPolicy is not valid.
350             Log.w(TAG, "okToConnect: return false, connectionPolicy=" + connectionPolicy);
351             return false;
352         }
353         return true;
354     }
355 
getDevicesMatchingConnectionStates(int[] states)356     List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
357         enforceCallingOrSelfPermission(BLUETOOTH_CONNECT, "Need BLUETOOTH_CONNECT permission");
358         ArrayList<BluetoothDevice> devices = new ArrayList<>();
359         if (states == null) {
360             return devices;
361         }
362         final BluetoothDevice[] bondedDevices = mAdapterService.getBondedDevices();
363         if (bondedDevices == null) {
364             return devices;
365         }
366         synchronized (mStateMachines) {
367             for (BluetoothDevice device : bondedDevices) {
368                 final ParcelUuid[] featureUuids = device.getUuids();
369                 if (!Utils.arrayContains(featureUuids, BluetoothUuid.COORDINATED_SET)) {
370                     continue;
371                 }
372                 int connectionState = BluetoothProfile.STATE_DISCONNECTED;
373                 CsipSetCoordinatorStateMachine sm = mStateMachines.get(device);
374                 if (sm != null) {
375                     connectionState = sm.getConnectionState();
376                 }
377                 for (int state : states) {
378                     if (connectionState == state) {
379                         devices.add(device);
380                         break;
381                     }
382                 }
383             }
384             return devices;
385         }
386     }
387 
388     /**
389      * Register for CSIS
390      */
registerCsisMemberObserver(@allbackExecutor Executor executor, ParcelUuid uuid, IBluetoothCsipSetCoordinatorCallback callback)391     public void registerCsisMemberObserver(@CallbackExecutor Executor executor, ParcelUuid uuid,
392             IBluetoothCsipSetCoordinatorCallback callback) {
393         Map<Executor, IBluetoothCsipSetCoordinatorCallback> entries =
394                 mCallbacks.getOrDefault(uuid, null);
395         if (entries == null) {
396             entries = new HashMap<>();
397             entries.put(executor, callback);
398             Log.d(TAG, " Csis adding new callback for " + uuid);
399             mCallbacks.put(uuid, entries);
400             return;
401         }
402 
403         if (entries.containsKey(executor)) {
404             if (entries.get(executor) == callback) {
405                 Log.d(TAG, " Execute and callback already added " + uuid);
406                 return;
407             }
408         }
409 
410         Log.d(TAG, " Csis adding callback " + uuid);
411         entries.put(executor, callback);
412     }
413 
414     /**
415      * Get the list of devices that have state machines.
416      *
417      * @return the list of devices that have state machines
418      */
419     @VisibleForTesting
getDevices()420     List<BluetoothDevice> getDevices() {
421         List<BluetoothDevice> devices = new ArrayList<>();
422         synchronized (mStateMachines) {
423             for (CsipSetCoordinatorStateMachine sm : mStateMachines.values()) {
424                 devices.add(sm.getDevice());
425             }
426             return devices;
427         }
428     }
429 
430     /**
431      * Get the current connection state of the profile
432      *
433      * @param device is the remote bluetooth device
434      * @return {@link BluetoothProfile#STATE_DISCONNECTED} if this profile is disconnected,
435      * {@link BluetoothProfile#STATE_CONNECTING} if this profile is being connected,
436      * {@link BluetoothProfile#STATE_CONNECTED} if this profile is connected, or
437      * {@link BluetoothProfile#STATE_DISCONNECTING} if this profile is being disconnected
438      */
getConnectionState(BluetoothDevice device)439     public int getConnectionState(BluetoothDevice device) {
440         enforceCallingOrSelfPermission(BLUETOOTH_CONNECT, "Need BLUETOOTH_CONNECT permission");
441         synchronized (mStateMachines) {
442             CsipSetCoordinatorStateMachine sm = mStateMachines.get(device);
443             if (sm == null) {
444                 return BluetoothProfile.STATE_DISCONNECTED;
445             }
446             return sm.getConnectionState();
447         }
448     }
449 
450     /**
451      * Set connection policy of the profile and connects it if connectionPolicy is
452      * {@link BluetoothProfile#CONNECTION_POLICY_ALLOWED} or disconnects if connectionPolicy is
453      * {@link BluetoothProfile#CONNECTION_POLICY_FORBIDDEN}
454      *
455      * <p> The device should already be paired.
456      * Connection policy can be one of:
457      * {@link BluetoothProfile#CONNECTION_POLICY_ALLOWED},
458      * {@link BluetoothProfile#CONNECTION_POLICY_FORBIDDEN},
459      * {@link BluetoothProfile#CONNECTION_POLICY_UNKNOWN}
460      *
461      * @param device the remote device
462      * @param connectionPolicy is the connection policy to set to for this profile
463      * @return true on success, otherwise false
464      */
setConnectionPolicy(BluetoothDevice device, int connectionPolicy)465     public boolean setConnectionPolicy(BluetoothDevice device, int connectionPolicy) {
466         enforceCallingOrSelfPermission(BLUETOOTH_PRIVILEGED, "Need BLUETOOTH_PRIVILEGED permission");
467         if (DBG) {
468             Log.d(TAG, "Saved connectionPolicy " + device + " = " + connectionPolicy);
469         }
470         mDatabaseManager.setProfileConnectionPolicy(
471                 device, BluetoothProfile.CSIP_SET_COORDINATOR, connectionPolicy);
472         if (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
473             connect(device);
474         } else if (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN) {
475             disconnect(device);
476         }
477         return true;
478     }
479 
480     /**
481      * Get the connection policy of the profile.
482      *
483      * @param device the remote device
484      * @return connection policy of the specified device
485      */
getConnectionPolicy(BluetoothDevice device)486     public int getConnectionPolicy(BluetoothDevice device) {
487         enforceCallingOrSelfPermission(BLUETOOTH_PRIVILEGED, "Need BLUETOOTH_PRIVILEGED permission");
488         return mDatabaseManager.getProfileConnectionPolicy(
489                 device, BluetoothProfile.CSIP_SET_COORDINATOR);
490     }
491 
492     /**
493      * Lock a given group.
494      * @param groupId group ID to lock
495      * @param callback callback with the lock request result
496      * @return unique lock identifier used for unlocking
497      *
498      * @hide
499      */
lockGroup( int groupId, @NonNull IBluetoothCsipSetCoordinatorLockCallback callback)500     public @Nullable UUID lockGroup(
501             int groupId, @NonNull IBluetoothCsipSetCoordinatorLockCallback callback) {
502         if (callback == null) {
503             if (DBG) {
504                 Log.d(TAG, "lockGroup(): " + groupId + ", callback not provided ");
505             }
506             return null;
507         }
508 
509         synchronized (mGroupIdToUuidMap) {
510             if (!mGroupIdToUuidMap.containsKey(groupId)) {
511                 try {
512                     callback.onGroupLockSet(groupId,
513                             BluetoothStatusCodes.ERROR_CSIP_INVALID_GROUP_ID,
514                             false);
515                 } catch (RemoteException e) {
516                     throw e.rethrowFromSystemServer();
517                 }
518                 return null;
519             }
520         }
521 
522         UUID uuid = UUID.randomUUID();
523         synchronized (mLocks) {
524             if (mLocks.containsKey(groupId)) {
525                 try {
526                     callback.onGroupLockSet(groupId,
527                             BluetoothStatusCodes.ERROR_CSIP_GROUP_LOCKED_BY_OTHER,
528                             true);
529                 } catch (RemoteException e) {
530                     throw e.rethrowFromSystemServer();
531                 }
532                 if (DBG) {
533                     Log.d(TAG, "lockGroup(): " + groupId + ", ERROR_CSIP_GROUP_LOCKED_BY_OTHER ");
534                 }
535                 return null;
536             }
537 
538             mLocks.put(groupId, new Pair<>(uuid, callback));
539         }
540 
541         if (DBG) {
542             Log.d(TAG, "lockGroup(): locking group: " + groupId);
543         }
544         mCsipSetCoordinatorNativeInterface.groupLockSet(groupId, true);
545         return uuid;
546     }
547 
548     /**
549      * Unlock a given group.
550      * @param lockUuid unique lock identifier used for unlocking
551      *
552      * @hide
553      */
unlockGroup(@onNull UUID lockUuid)554     public void unlockGroup(@NonNull UUID lockUuid) {
555         if (lockUuid == null) {
556             if (DBG) {
557                 Log.d(TAG, "unlockGroup(): lockUuid is null");
558             }
559             return;
560         }
561 
562         synchronized (mLocks) {
563             for (Map.Entry<Integer, Pair<UUID, IBluetoothCsipSetCoordinatorLockCallback>> entry :
564                     mLocks.entrySet()) {
565                 Pair<UUID, IBluetoothCsipSetCoordinatorLockCallback> uuidCbPair = entry.getValue();
566                 if (uuidCbPair.first.equals(lockUuid)) {
567                     if (DBG) {
568                         Log.d(TAG, "unlockGroup(): unlocking ... " + lockUuid);
569                     }
570                     mCsipSetCoordinatorNativeInterface.groupLockSet(entry.getKey(), false);
571                     return;
572                 }
573             }
574         }
575     }
576 
577     /**
578      * Check whether a given group is currently locked.
579      * @param groupId unique group identifier
580      * @return true if group is currently locked, otherwise false.
581      *
582      * @hide
583      */
isGroupLocked(int groupId)584     public boolean isGroupLocked(int groupId) {
585         return mLocks.containsKey(groupId);
586     }
587 
588     /**
589      * Get collection of group IDs for a given UUID
590      * @param uuid profile context UUID
591      * @return list of group IDs
592      */
getAllGroupIds(ParcelUuid uuid)593     public List<Integer> getAllGroupIds(ParcelUuid uuid) {
594         return mGroupIdToUuidMap.entrySet()
595                 .stream()
596                 .filter(e -> uuid.equals(e.getValue()))
597                 .map(Map.Entry::getKey)
598                 .collect(Collectors.toList());
599     }
600 
601     /**
602      * Get group ID for a given device and UUID
603      * @param device potential group member
604      * @param uuid profile context UUID
605      * @return group ID
606      */
getGroupId(BluetoothDevice device, ParcelUuid uuid)607     public Integer getGroupId(BluetoothDevice device, ParcelUuid uuid) {
608         Map<Integer, Integer> device_groups =
609                 mDeviceGroupIdRankMap.getOrDefault(device, new HashMap<>());
610         return mGroupIdToUuidMap.entrySet()
611                 .stream()
612                 .filter(e -> (device_groups.containsKey(e.getKey())
613                         && e.getValue().equals(uuid)))
614                 .map(Map.Entry::getKey)
615                 .findFirst()
616                 .orElse(IBluetoothCsipSetCoordinator.CSIS_GROUP_ID_INVALID);
617     }
618 
619     /**
620      * Get device's groups/
621      * @param device group member device
622      * @return map of group id and related uuids.
623      */
getGroupUuidMapByDevice(BluetoothDevice device)624     public Map<Integer, ParcelUuid> getGroupUuidMapByDevice(BluetoothDevice device) {
625         Map<Integer, Integer> device_groups =
626                 mDeviceGroupIdRankMap.getOrDefault(device, new HashMap<>());
627         return mGroupIdToUuidMap.entrySet()
628                 .stream()
629                 .filter(e -> device_groups.containsKey(e.getKey()))
630                 .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
631     }
632 
633     /**
634      * Get grouped devices
635      * @param groupId group ID
636      * @return related list of devices sorted from the lowest to the highest rank value.
637      */
getGroupDevicesOrdered(int groupId)638     public @NonNull List<BluetoothDevice> getGroupDevicesOrdered(int groupId) {
639         final Map<BluetoothDevice, Integer> deviceRankMap = new HashMap();
640         for (Map.Entry<BluetoothDevice, ?> entry : mDeviceGroupIdRankMap.entrySet()) {
641             Map<Integer, Integer> rankMap = (Map<Integer, Integer>) entry.getValue();
642             BluetoothDevice device = entry.getKey();
643             if (rankMap.containsKey(groupId)) {
644                 deviceRankMap.put(device, rankMap.get(groupId));
645             }
646         }
647 
648         // Return device list sorted by descending rank order
649         return deviceRankMap.entrySet()
650                 .stream()
651                 .sorted(Map.Entry.comparingByValue())
652                 .map(e -> e.getKey())
653                 .collect(Collectors.toList());
654     }
655 
656     /**
657      * Get grouped devices
658      * @param device group member device
659      * @param uuid profile context UUID
660      * @return related list of devices sorted from the lowest to the highest rank value.
661      */
getGroupDevicesOrdered(BluetoothDevice device, ParcelUuid uuid)662     public @NonNull List<BluetoothDevice> getGroupDevicesOrdered(BluetoothDevice device,
663             ParcelUuid uuid) {
664         List<Integer> groupIds = getAllGroupIds(uuid);
665         for (Integer id : groupIds) {
666             List<BluetoothDevice> devices = getGroupDevicesOrdered(id);
667             if (devices.contains(device)) {
668                 return devices;
669             }
670         }
671         return Collections.emptyList();
672     }
673 
674     /**
675      * Get group desired size
676      * @param groupId group ID
677      * @return the number of group members
678      */
getDesiredGroupSize(int groupId)679     public int getDesiredGroupSize(int groupId) {
680         return mGroupIdToGroupSize.getOrDefault(groupId,
681                 IBluetoothCsipSetCoordinator.CSIS_GROUP_SIZE_UNKNOWN);
682     }
683 
handleDeviceAvailable(BluetoothDevice device, int groupId, int rank, UUID uuid)684     private void handleDeviceAvailable(BluetoothDevice device, int groupId, int rank, UUID uuid) {
685         ParcelUuid parcel_uuid = new ParcelUuid(uuid);
686         if (!getAllGroupIds(parcel_uuid).contains(groupId)) {
687             mGroupIdToUuidMap.put(groupId, parcel_uuid);
688         }
689 
690         if (!mDeviceGroupIdRankMap.containsKey(device)) {
691             mDeviceGroupIdRankMap.put(device, new HashMap<Integer, Integer>());
692         }
693 
694         Map<Integer, Integer> all_device_groups = mDeviceGroupIdRankMap.get(device);
695         all_device_groups.put(groupId, rank);
696     }
697 
executeCallback(Executor exec, IBluetoothCsipSetCoordinatorCallback callback, BluetoothDevice device, int groupId)698     private void executeCallback(Executor exec, IBluetoothCsipSetCoordinatorCallback callback,
699             BluetoothDevice device, int groupId) throws RemoteException {
700         exec.execute(() -> {
701             try {
702                 callback.onCsisSetMemberAvailable(device, groupId);
703             } catch (RemoteException e) {
704                 throw e.rethrowFromSystemServer();
705             }
706         });
707     }
708 
handleSetMemberAvailable(BluetoothDevice device, int groupId)709     private void handleSetMemberAvailable(BluetoothDevice device, int groupId) {
710         if (!mGroupIdToUuidMap.containsKey(groupId)) {
711             Log.e(TAG, " UUID not found for group id " + groupId);
712             return;
713         }
714 
715         if (mCallbacks.isEmpty()) {
716             return;
717         }
718 
719         ParcelUuid uuid = mGroupIdToUuidMap.get(groupId);
720         if (mCallbacks.get(uuid) == null) {
721             Log.e(TAG, " There is no clients for uuid: " + uuid);
722             return;
723         }
724 
725         for (Map.Entry<Executor, IBluetoothCsipSetCoordinatorCallback> entry :
726                 mCallbacks.get(uuid).entrySet()) {
727             if (DBG) {
728                 Log.d(TAG, " executing " + uuid + " " + entry.getKey());
729             }
730             try {
731                 executeCallback(entry.getKey(), entry.getValue(), device, groupId);
732             } catch (RemoteException e) {
733                 throw e.rethrowFromSystemServer();
734             }
735         }
736     }
737 
getApiStatusCode(int nativeResult)738     int getApiStatusCode(int nativeResult) {
739         switch (nativeResult) {
740             case IBluetoothCsipSetCoordinator.CSIS_GROUP_LOCK_SUCCESS:
741                 return BluetoothStatusCodes.SUCCESS;
742             case IBluetoothCsipSetCoordinator.CSIS_GROUP_LOCK_FAILED_INVALID_GROUP:
743                 return BluetoothStatusCodes.ERROR_CSIP_INVALID_GROUP_ID;
744             case IBluetoothCsipSetCoordinator.CSIS_GROUP_LOCK_FAILED_GROUP_NOT_CONNECTED:
745                 return BluetoothStatusCodes.ERROR_DEVICE_NOT_CONNECTED;
746             case IBluetoothCsipSetCoordinator.CSIS_GROUP_LOCK_FAILED_LOCKED_BY_OTHER:
747                 return BluetoothStatusCodes.ERROR_CSIP_GROUP_LOCKED_BY_OTHER;
748             case IBluetoothCsipSetCoordinator.CSIS_LOCKED_GROUP_MEMBER_LOST:
749                 return BluetoothStatusCodes.ERROR_CSIP_LOCKED_GROUP_MEMBER_LOST;
750             case IBluetoothCsipSetCoordinator.CSIS_GROUP_LOCK_FAILED_OTHER_REASON:
751             default:
752                 Log.e(TAG, " Unknown status code: " + nativeResult);
753                 return BluetoothStatusCodes.ERROR_UNKNOWN;
754         }
755     }
756 
handleGroupLockChanged(int groupId, int status, boolean isLocked)757     void handleGroupLockChanged(int groupId, int status, boolean isLocked) {
758         synchronized (mLocks) {
759             if (!mLocks.containsKey(groupId)) {
760                 return;
761             }
762 
763             IBluetoothCsipSetCoordinatorLockCallback cb = mLocks.get(groupId).second;
764             try {
765                 cb.onGroupLockSet(groupId, getApiStatusCode(status), isLocked);
766             } catch (RemoteException e) {
767                 throw e.rethrowFromSystemServer();
768             }
769 
770             // Unlocking invalidates the existing lock if exist
771             if (!isLocked) {
772                 mLocks.remove(groupId);
773             }
774         }
775     }
776 
messageFromNative(CsipSetCoordinatorStackEvent stackEvent)777     void messageFromNative(CsipSetCoordinatorStackEvent stackEvent) {
778         BluetoothDevice device = stackEvent.device;
779         Log.d(TAG, "Message from native: " + stackEvent);
780 
781         Intent intent = null;
782         int groupId = stackEvent.valueInt1;
783         if (stackEvent.type == CsipSetCoordinatorStackEvent.EVENT_TYPE_DEVICE_AVAILABLE) {
784             Objects.requireNonNull(device, "Device should never be null, event: " + stackEvent);
785 
786             intent = new Intent(BluetoothCsipSetCoordinator.ACTION_CSIS_DEVICE_AVAILABLE);
787             intent.putExtra(BluetoothDevice.EXTRA_DEVICE, stackEvent.device);
788             intent.putExtra(BluetoothCsipSetCoordinator.EXTRA_CSIS_GROUP_ID, groupId);
789             intent.putExtra(
790                     BluetoothCsipSetCoordinator.EXTRA_CSIS_GROUP_SIZE, stackEvent.valueInt2);
791             intent.putExtra(
792                     BluetoothCsipSetCoordinator.EXTRA_CSIS_GROUP_TYPE_UUID, stackEvent.valueUuid1);
793 
794             handleDeviceAvailable(device, groupId, stackEvent.valueInt3, stackEvent.valueUuid1);
795         } else if (stackEvent.type
796                 == CsipSetCoordinatorStackEvent.EVENT_TYPE_SET_MEMBER_AVAILABLE) {
797             Objects.requireNonNull(device, "Device should never be null, event: " + stackEvent);
798             /* Notify registered parties */
799             handleSetMemberAvailable(device, stackEvent.valueInt1);
800 
801             /* Sent intent as well */
802             intent = new Intent(BluetoothCsipSetCoordinator.ACTION_CSIS_SET_MEMBER_AVAILABLE);
803             intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
804             intent.putExtra(BluetoothCsipSetCoordinator.EXTRA_CSIS_GROUP_ID, groupId);
805         } else if (stackEvent.type == CsipSetCoordinatorStackEvent.EVENT_TYPE_GROUP_LOCK_CHANGED) {
806             int lock_status = stackEvent.valueInt2;
807             boolean lock_state = stackEvent.valueBool1;
808             handleGroupLockChanged(groupId, lock_status, lock_state);
809         }
810 
811         if (intent != null) {
812             intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT
813                     | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
814             sendBroadcast(intent, BLUETOOTH_PRIVILEGED);
815         }
816 
817         synchronized (mStateMachines) {
818             CsipSetCoordinatorStateMachine sm = mStateMachines.get(device);
819 
820             if (stackEvent.type
821                     == CsipSetCoordinatorStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED) {
822                 if (sm == null) {
823                     switch (stackEvent.valueInt1) {
824                         case CsipSetCoordinatorStackEvent.CONNECTION_STATE_CONNECTED:
825                         case CsipSetCoordinatorStackEvent.CONNECTION_STATE_CONNECTING:
826                             sm = getOrCreateStateMachine(device);
827                             break;
828                         default:
829                             break;
830                     }
831                 }
832 
833                 if (sm == null) {
834                     Log.e(TAG, "Cannot process stack event: no state machine: " + stackEvent);
835                     return;
836                 }
837                 sm.sendMessage(CsipSetCoordinatorStateMachine.STACK_EVENT, stackEvent);
838             }
839         }
840     }
841 
getOrCreateStateMachine(BluetoothDevice device)842     private CsipSetCoordinatorStateMachine getOrCreateStateMachine(BluetoothDevice device) {
843         if (device == null) {
844             Log.e(TAG, "getOrCreateStateMachine failed: device cannot be null");
845             return null;
846         }
847         synchronized (mStateMachines) {
848             CsipSetCoordinatorStateMachine sm = mStateMachines.get(device);
849             if (sm != null) {
850                 return sm;
851             }
852             // Limit the maximum number of state machines to avoid DoS attack
853             if (mStateMachines.size() >= MAX_CSIS_STATE_MACHINES) {
854                 Log.e(TAG,
855                         "Maximum number of CSIS state machines reached: "
856                                 + MAX_CSIS_STATE_MACHINES);
857                 return null;
858             }
859             if (DBG) {
860                 Log.d(TAG, "Creating a new state machine for " + device);
861             }
862             sm = CsipSetCoordinatorStateMachine.make(device, this,
863                     mCsipSetCoordinatorNativeInterface, mStateMachinesThread.getLooper());
864             mStateMachines.put(device, sm);
865             return sm;
866         }
867     }
868 
869     // Remove state machine if the bonding for a device is removed
870     private class BondStateChangedReceiver extends BroadcastReceiver {
871         @Override
onReceive(Context context, Intent intent)872         public void onReceive(Context context, Intent intent) {
873             if (!BluetoothDevice.ACTION_BOND_STATE_CHANGED.equals(intent.getAction())) {
874                 return;
875             }
876             int state = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, BluetoothDevice.ERROR);
877             BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
878             Objects.requireNonNull(device, "ACTION_BOND_STATE_CHANGED with no EXTRA_DEVICE");
879             bondStateChanged(device, state);
880         }
881     }
882 
883     /**
884      * Process a change in the bonding state for a device.
885      *
886      * @param device the device whose bonding state has changed
887      * @param bondState the new bond state for the device. Possible values are:
888      * {@link BluetoothDevice#BOND_NONE},
889      * {@link BluetoothDevice#BOND_BONDING},
890      * {@link BluetoothDevice#BOND_BONDED}.
891      */
892     @VisibleForTesting
bondStateChanged(BluetoothDevice device, int bondState)893     void bondStateChanged(BluetoothDevice device, int bondState) {
894         if (DBG) {
895             Log.d(TAG, "Bond state changed for device: " + device + " state: " + bondState);
896         }
897         // Remove state machine if the bonding for a device is removed
898         if (bondState != BluetoothDevice.BOND_NONE) {
899             return;
900         }
901 
902         mDeviceGroupIdRankMap.remove(device);
903 
904         synchronized (mStateMachines) {
905             CsipSetCoordinatorStateMachine sm = mStateMachines.get(device);
906             if (sm == null) {
907                 return;
908             }
909             if (sm.getConnectionState() != BluetoothProfile.STATE_DISCONNECTED) {
910                 Log.i(TAG, "Disconnecting device because it was unbonded.");
911                 disconnect(device);
912                 return;
913             }
914             removeStateMachine(device);
915         }
916     }
917 
removeStateMachine(BluetoothDevice device)918     private void removeStateMachine(BluetoothDevice device) {
919         synchronized (mStateMachines) {
920             CsipSetCoordinatorStateMachine sm = mStateMachines.get(device);
921             if (sm == null) {
922                 Log.w(TAG,
923                         "removeStateMachine: device " + device + " does not have a state machine");
924                 return;
925             }
926             Log.i(TAG, "removeStateMachine: removing state machine for device: " + device);
927             sm.doQuit();
928             sm.cleanup();
929             mStateMachines.remove(device);
930         }
931     }
932 
933     @VisibleForTesting
connectionStateChanged(BluetoothDevice device, int fromState, int toState)934     synchronized void connectionStateChanged(BluetoothDevice device, int fromState, int toState) {
935         if ((device == null) || (fromState == toState)) {
936             Log.e(TAG,
937                     "connectionStateChanged: unexpected invocation. device=" + device
938                             + " fromState=" + fromState + " toState=" + toState);
939             return;
940         }
941 
942         // Check if the device is disconnected - if unbond, remove the state machine
943         if (toState == BluetoothProfile.STATE_DISCONNECTED) {
944             int bondState = mAdapterService.getBondState(device);
945             if (bondState == BluetoothDevice.BOND_NONE) {
946                 if (DBG) {
947                     Log.d(TAG, device + " is unbond. Remove state machine");
948                 }
949                 removeStateMachine(device);
950             }
951         }
952     }
953 
954     private class ConnectionStateChangedReceiver extends BroadcastReceiver {
955         @Override
onReceive(Context context, Intent intent)956         public void onReceive(Context context, Intent intent) {
957             if (!BluetoothCsipSetCoordinator.ACTION_CSIS_CONNECTION_STATE_CHANGED.equals(
958                         intent.getAction())) {
959                 return;
960             }
961             BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
962             int toState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1);
963             int fromState = intent.getIntExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, -1);
964             connectionStateChanged(device, fromState, toState);
965         }
966     }
967 
968     /**
969      * Binder object: must be a static class or memory leak may occur
970      */
971     @VisibleForTesting
972     static class BluetoothCsisBinder
973             extends IBluetoothCsipSetCoordinator.Stub implements IProfileServiceBinder {
974         private CsipSetCoordinatorService mService;
975 
getService(AttributionSource source)976         private CsipSetCoordinatorService getService(AttributionSource source) {
977             if (Utils.isInstrumentationTestMode()) {
978                 return mService;
979             }
980             if (!Utils.checkServiceAvailable(mService, TAG)
981                     || !Utils.checkCallerIsSystemOrActiveOrManagedUser(mService, TAG)) {
982                 return null;
983             }
984 
985             return mService;
986         }
987 
BluetoothCsisBinder(CsipSetCoordinatorService svc)988         BluetoothCsisBinder(CsipSetCoordinatorService svc) {
989             mService = svc;
990         }
991 
992         @Override
cleanup()993         public void cleanup() {
994             mService = null;
995         }
996 
997         @Override
connect(BluetoothDevice device, AttributionSource source, SynchronousResultReceiver receiver)998         public void connect(BluetoothDevice device, AttributionSource source,
999                 SynchronousResultReceiver receiver) {
1000             try {
1001                 Objects.requireNonNull(device, "device cannot be null");
1002                 Objects.requireNonNull(source, "source cannot be null");
1003                 Objects.requireNonNull(receiver, "receiver cannot be null");
1004 
1005                 boolean defaultValue = false;
1006                 CsipSetCoordinatorService service = getService(source);
1007                 if (service == null) {
1008                     throw new IllegalStateException("service is null");
1009                 }
1010 
1011                 enforceBluetoothPrivilegedPermission(service);
1012 
1013                 defaultValue = service.connect(device);
1014                 receiver.send(defaultValue);
1015             } catch (RuntimeException e) {
1016                 receiver.propagateException(e);
1017             }
1018         }
1019 
1020         @Override
disconnect(BluetoothDevice device, AttributionSource source, SynchronousResultReceiver receiver)1021         public void disconnect(BluetoothDevice device, AttributionSource source,
1022                 SynchronousResultReceiver receiver) {
1023             try {
1024                 Objects.requireNonNull(device, "device cannot be null");
1025                 Objects.requireNonNull(source, "source cannot be null");
1026                 Objects.requireNonNull(receiver, "receiver cannot be null");
1027 
1028                 boolean defaultValue = false;
1029                 CsipSetCoordinatorService service = getService(source);
1030                 if (service == null) {
1031                     throw new IllegalStateException("service is null");
1032                 }
1033 
1034                 enforceBluetoothPrivilegedPermission(service);
1035 
1036                 defaultValue = service.disconnect(device);
1037                 receiver.send(defaultValue);
1038             } catch (RuntimeException e) {
1039                 receiver.propagateException(e);
1040             }
1041         }
1042 
1043         @Override
getConnectedDevices(AttributionSource source, SynchronousResultReceiver receiver)1044         public void getConnectedDevices(AttributionSource source,
1045                 SynchronousResultReceiver receiver) {
1046             try {
1047                 Objects.requireNonNull(source, "source cannot be null");
1048                 Objects.requireNonNull(receiver, "receiver cannot be null");
1049 
1050                 List<BluetoothDevice> defaultValue = new ArrayList<>();
1051                 CsipSetCoordinatorService service = getService(source);
1052                 if (service == null) {
1053                     throw new IllegalStateException("service is null");
1054                 }
1055 
1056                 enforceBluetoothPrivilegedPermission(service);
1057 
1058                 defaultValue = service.getConnectedDevices();
1059                 receiver.send(defaultValue);
1060             } catch (RuntimeException e) {
1061                 receiver.propagateException(e);
1062             }
1063         }
1064 
1065         @Override
getDevicesMatchingConnectionStates(int[] states, AttributionSource source, SynchronousResultReceiver receiver)1066         public void getDevicesMatchingConnectionStates(int[] states,
1067                 AttributionSource source, SynchronousResultReceiver receiver) {
1068             try {
1069                 Objects.requireNonNull(source, "source cannot be null");
1070                 Objects.requireNonNull(receiver, "receiver cannot be null");
1071 
1072                 List<BluetoothDevice> defaultValue = new ArrayList<>();
1073                 CsipSetCoordinatorService service = getService(source);
1074                 if (service == null) {
1075                     throw new IllegalStateException("service is null");
1076                 }
1077 
1078                 enforceBluetoothPrivilegedPermission(service);
1079 
1080                 defaultValue = service.getDevicesMatchingConnectionStates(states);
1081                 receiver.send(defaultValue);
1082             } catch (RuntimeException e) {
1083                 receiver.propagateException(e);
1084             }
1085         }
1086 
1087         @Override
getConnectionState(BluetoothDevice device, AttributionSource source, SynchronousResultReceiver receiver)1088         public void getConnectionState(BluetoothDevice device, AttributionSource source,
1089                 SynchronousResultReceiver receiver) {
1090             try {
1091                 Objects.requireNonNull(device, "device cannot be null");
1092                 Objects.requireNonNull(source, "source cannot be null");
1093                 Objects.requireNonNull(receiver, "receiver cannot be null");
1094 
1095                 int defaultValue = BluetoothProfile.STATE_DISCONNECTED;
1096                 CsipSetCoordinatorService service = getService(source);
1097                 if (service == null) {
1098                     throw new IllegalStateException("service is null");
1099                 }
1100 
1101                 enforceBluetoothPrivilegedPermission(service);
1102                 defaultValue = service.getConnectionState(device);
1103                 receiver.send(defaultValue);
1104             } catch (RuntimeException e) {
1105                 receiver.propagateException(e);
1106             }
1107         }
1108 
1109         @Override
setConnectionPolicy(BluetoothDevice device, int connectionPolicy, AttributionSource source, SynchronousResultReceiver receiver)1110         public void setConnectionPolicy(BluetoothDevice device, int connectionPolicy,
1111                 AttributionSource source, SynchronousResultReceiver receiver) {
1112             try {
1113                 Objects.requireNonNull(device, "device cannot be null");
1114                 Objects.requireNonNull(source, "source cannot be null");
1115                 Objects.requireNonNull(receiver, "receiver cannot be null");
1116 
1117                 boolean defaultValue = false;
1118                 CsipSetCoordinatorService service = getService(source);
1119                 if (service == null) {
1120                     throw new IllegalStateException("service is null");
1121                 }
1122 
1123                 enforceBluetoothPrivilegedPermission(service);
1124 
1125                 defaultValue = service.setConnectionPolicy(device, connectionPolicy);
1126                 receiver.send(defaultValue);
1127             } catch (RuntimeException e) {
1128                 receiver.propagateException(e);
1129             }
1130         }
1131 
1132         @Override
getConnectionPolicy(BluetoothDevice device, AttributionSource source, SynchronousResultReceiver receiver)1133         public void getConnectionPolicy(BluetoothDevice device, AttributionSource source,
1134                 SynchronousResultReceiver receiver) {
1135             try {
1136                 Objects.requireNonNull(device, "device cannot be null");
1137                 Objects.requireNonNull(source, "source cannot be null");
1138                 Objects.requireNonNull(receiver, "receiver cannot be null");
1139 
1140                 int defaultValue = BluetoothProfile.CONNECTION_POLICY_UNKNOWN;
1141                 CsipSetCoordinatorService service = getService(source);
1142                 if (service == null) {
1143                     throw new IllegalStateException("service is null");
1144                 }
1145 
1146                 enforceBluetoothPrivilegedPermission(service);
1147 
1148                 defaultValue = service.getConnectionPolicy(device);
1149                 receiver.send(defaultValue);
1150             } catch (RuntimeException e) {
1151                 receiver.propagateException(e);
1152             }
1153         }
1154 
1155         @Override
lockGroup( int groupId, @NonNull IBluetoothCsipSetCoordinatorLockCallback callback, AttributionSource source, SynchronousResultReceiver receiver)1156         public void lockGroup(
1157                 int groupId, @NonNull IBluetoothCsipSetCoordinatorLockCallback callback,
1158                 AttributionSource source, SynchronousResultReceiver receiver) {
1159             try {
1160                 Objects.requireNonNull(callback, "callback cannot be null");
1161                 Objects.requireNonNull(source, "source cannot be null");
1162                 Objects.requireNonNull(receiver, "receiver cannot be null");
1163 
1164                 ParcelUuid defaultValue = null;
1165 
1166                 CsipSetCoordinatorService service = getService(source);
1167                 if (service == null) {
1168                     throw new IllegalStateException("service is null");
1169                 }
1170 
1171                 enforceBluetoothPrivilegedPermission(service);
1172 
1173                 UUID lockUuid = service.lockGroup(groupId, callback);
1174                 defaultValue = lockUuid == null ? null : new ParcelUuid(lockUuid);
1175                 receiver.send(defaultValue);
1176             } catch (RuntimeException e) {
1177                 receiver.propagateException(e);
1178             }
1179         }
1180 
1181         @Override
unlockGroup(@onNull ParcelUuid lockUuid, AttributionSource source, SynchronousResultReceiver receiver)1182         public void unlockGroup(@NonNull ParcelUuid lockUuid, AttributionSource source,
1183                 SynchronousResultReceiver receiver) {
1184             try {
1185                 Objects.requireNonNull(lockUuid, "lockUuid cannot be null");
1186                 Objects.requireNonNull(source, "source cannot be null");
1187                 Objects.requireNonNull(receiver, "receiver cannot be null");
1188 
1189                 CsipSetCoordinatorService service = getService(source);
1190                 if (service == null) {
1191                     throw new IllegalStateException("service is null");
1192                 }
1193 
1194                 enforceBluetoothPrivilegedPermission(service);
1195 
1196                 service.unlockGroup(lockUuid.getUuid());
1197                 receiver.send(null);
1198             } catch (RuntimeException e) {
1199                 receiver.propagateException(e);
1200             }
1201         }
1202 
1203         @Override
getAllGroupIds(ParcelUuid uuid, AttributionSource source, SynchronousResultReceiver receiver)1204         public void getAllGroupIds(ParcelUuid uuid, AttributionSource source,
1205                 SynchronousResultReceiver receiver) {
1206             try {
1207                 Objects.requireNonNull(uuid, "uuid cannot be null");
1208                 Objects.requireNonNull(source, "source cannot be null");
1209                 Objects.requireNonNull(receiver, "receiver cannot be null");
1210 
1211                 List<Integer> defaultValue = new ArrayList<Integer>();
1212                 CsipSetCoordinatorService service = getService(source);
1213                 if (service == null) {
1214                     throw new IllegalStateException("service is null");
1215                 }
1216                 enforceBluetoothPrivilegedPermission(service);
1217                 defaultValue = service.getAllGroupIds(uuid);
1218                 receiver.send(defaultValue);
1219             } catch (RuntimeException e) {
1220                 receiver.propagateException(e);
1221             }
1222         }
1223 
1224         @Override
getGroupUuidMapByDevice(BluetoothDevice device, AttributionSource source, SynchronousResultReceiver receiver)1225         public void getGroupUuidMapByDevice(BluetoothDevice device,
1226                 AttributionSource source, SynchronousResultReceiver receiver) {
1227             try {
1228                 Map<Integer, ParcelUuid> defaultValue = null;
1229                 CsipSetCoordinatorService service = getService(source);
1230                 if (service == null) {
1231                     throw new IllegalStateException("service is null");
1232                 }
1233 
1234                 enforceBluetoothPrivilegedPermission(service);
1235 
1236                 defaultValue = service.getGroupUuidMapByDevice(device);
1237                 receiver.send(defaultValue);
1238             } catch (RuntimeException e) {
1239                 receiver.propagateException(e);
1240             }
1241         }
1242 
1243         @Override
getDesiredGroupSize(int groupId, AttributionSource source, SynchronousResultReceiver receiver)1244         public void getDesiredGroupSize(int groupId, AttributionSource source,
1245                 SynchronousResultReceiver receiver) {
1246             try {
1247                 int defaultValue = IBluetoothCsipSetCoordinator.CSIS_GROUP_SIZE_UNKNOWN;
1248                 CsipSetCoordinatorService service = getService(source);
1249                 if (service == null) {
1250                     throw new IllegalStateException("service is null");
1251                 }
1252 
1253                 enforceBluetoothPrivilegedPermission(service);
1254 
1255                 defaultValue = service.getDesiredGroupSize(groupId);
1256                 receiver.send(defaultValue);
1257             } catch (RuntimeException e) {
1258                 receiver.propagateException(e);
1259             }
1260         }
1261     }
1262 
1263     @Override
dump(StringBuilder sb)1264     public void dump(StringBuilder sb) {
1265         super.dump(sb);
1266         for (CsipSetCoordinatorStateMachine sm : mStateMachines.values()) {
1267             sm.dump(sb);
1268         }
1269     }
1270 }
1271