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