• 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.hap;
19 
20 import static android.Manifest.permission.BLUETOOTH_CONNECT;
21 import static android.Manifest.permission.BLUETOOTH_PRIVILEGED;
22 
23 import static com.android.bluetooth.Utils.enforceBluetoothPrivilegedPermission;
24 
25 import android.annotation.Nullable;
26 import android.bluetooth.BluetoothCsipSetCoordinator;
27 import android.bluetooth.BluetoothDevice;
28 import android.bluetooth.BluetoothHapClient;
29 import android.bluetooth.BluetoothHapPresetInfo;
30 import android.bluetooth.BluetoothLeAudio;
31 import android.bluetooth.BluetoothProfile;
32 import android.bluetooth.BluetoothStatusCodes;
33 import android.bluetooth.BluetoothUuid;
34 import android.bluetooth.IBluetoothHapClient;
35 import android.bluetooth.IBluetoothHapClientCallback;
36 import android.content.AttributionSource;
37 import android.content.BroadcastReceiver;
38 import android.content.Context;
39 import android.content.Intent;
40 import android.content.IntentFilter;
41 import android.os.HandlerThread;
42 import android.os.ParcelUuid;
43 import android.os.RemoteCallbackList;
44 import android.os.RemoteException;
45 import android.sysprop.BluetoothProperties;
46 import android.util.Log;
47 
48 import com.android.bluetooth.Utils;
49 import com.android.bluetooth.btservice.AdapterService;
50 import com.android.bluetooth.btservice.ProfileService;
51 import com.android.bluetooth.btservice.ServiceFactory;
52 import com.android.bluetooth.btservice.storage.DatabaseManager;
53 import com.android.bluetooth.csip.CsipSetCoordinatorService;
54 import com.android.internal.annotations.VisibleForTesting;
55 import com.android.modules.utils.SynchronousResultReceiver;
56 
57 import java.math.BigInteger;
58 import java.util.ArrayList;
59 import java.util.Collections;
60 import java.util.HashMap;
61 import java.util.List;
62 import java.util.ListIterator;
63 import java.util.Map;
64 import java.util.Objects;
65 
66 /**
67  * Provides Bluetooth Hearing Access profile, as a service.
68  * @hide
69  */
70 public class HapClientService extends ProfileService {
71     private static final boolean DBG = true;
72     private static final String TAG = "HapClientService";
73 
74     // Upper limit of all HearingAccess devices: Bonded or Connected
75     private static final int MAX_HEARING_ACCESS_STATE_MACHINES = 10;
76     private static final int SM_THREAD_JOIN_TIMEOUT_MS = 1000;
77     private static HapClientService sHapClient;
78     private final Map<BluetoothDevice, HapClientStateMachine> mStateMachines =
79             new HashMap<>();
80     @VisibleForTesting
81     HapClientNativeInterface mHapClientNativeInterface;
82     private AdapterService mAdapterService;
83     private DatabaseManager mDatabaseManager;
84     private HandlerThread mStateMachinesThread;
85     private BroadcastReceiver mBondStateChangedReceiver;
86     private BroadcastReceiver mConnectionStateChangedReceiver;
87 
88     private final Map<BluetoothDevice, Integer> mDeviceCurrentPresetMap = new HashMap<>();
89     private final Map<BluetoothDevice, Integer> mDeviceFeaturesMap = new HashMap<>();
90     private final Map<BluetoothDevice, List<BluetoothHapPresetInfo>> mPresetsMap =
91             new HashMap<>();
92 
93     @VisibleForTesting
94     RemoteCallbackList<IBluetoothHapClientCallback> mCallbacks;
95 
96     @VisibleForTesting
97     ServiceFactory mFactory = new ServiceFactory();
98 
isEnabled()99     public static boolean isEnabled() {
100         return BluetoothProperties.isProfileHapClientEnabled().orElse(false);
101     }
102 
103     @VisibleForTesting
setHapClient(HapClientService instance)104     static synchronized void setHapClient(HapClientService instance) {
105         if (DBG) {
106             Log.d(TAG, "setHapClient(): set to: " + instance);
107         }
108         sHapClient = instance;
109     }
110 
111     /**
112      * Get the HapClientService instance
113      * @return HapClientService instance
114      */
getHapClientService()115     public static synchronized HapClientService getHapClientService() {
116         if (sHapClient == null) {
117             Log.w(TAG, "getHapClientService(): service is NULL");
118             return null;
119         }
120 
121         if (!sHapClient.isAvailable()) {
122             Log.w(TAG, "getHapClientService(): service is not available");
123             return null;
124         }
125         return sHapClient;
126     }
127 
128     @Override
create()129     protected void create() {
130         if (DBG) {
131             Log.d(TAG, "create()");
132         }
133     }
134 
135     @Override
cleanup()136     protected void cleanup() {
137         if (DBG) {
138             Log.d(TAG, "cleanup()");
139         }
140     }
141 
142     @Override
initBinder()143     protected IProfileServiceBinder initBinder() {
144         return new BluetoothHapClientBinder(this);
145     }
146 
147     @Override
start()148     protected boolean start() {
149         if (DBG) {
150             Log.d(TAG, "start()");
151         }
152 
153         if (sHapClient != null) {
154             throw new IllegalStateException("start() called twice");
155         }
156 
157         // Get AdapterService, HapClientNativeInterface, DatabaseManager, AudioManager.
158         // None of them can be null.
159         mAdapterService = Objects.requireNonNull(AdapterService.getAdapterService(),
160                 "AdapterService cannot be null when HapClientService starts");
161         mDatabaseManager = Objects.requireNonNull(mAdapterService.getDatabase(),
162                 "DatabaseManager cannot be null when HapClientService starts");
163         mHapClientNativeInterface = Objects.requireNonNull(
164                 HapClientNativeInterface.getInstance(),
165                 "HapClientNativeInterface cannot be null when HapClientService starts");
166 
167         // Start handler thread for state machines
168         mStateMachines.clear();
169         mStateMachinesThread = new HandlerThread("HapClientService.StateMachines");
170         mStateMachinesThread.start();
171 
172         // Setup broadcast receivers
173         IntentFilter filter = new IntentFilter();
174         filter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
175         filter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
176         mBondStateChangedReceiver = new BondStateChangedReceiver();
177         registerReceiver(mBondStateChangedReceiver, filter);
178         filter = new IntentFilter();
179         filter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
180         filter.addAction(BluetoothHapClient.ACTION_HAP_CONNECTION_STATE_CHANGED);
181         mConnectionStateChangedReceiver = new ConnectionStateChangedReceiver();
182         registerReceiver(mConnectionStateChangedReceiver, filter, Context.RECEIVER_NOT_EXPORTED);
183 
184         mCallbacks = new RemoteCallbackList<IBluetoothHapClientCallback>();
185 
186         // Initialize native interface
187         mHapClientNativeInterface.init();
188 
189         // Mark service as started
190         setHapClient(this);
191 
192         return true;
193     }
194 
195     @Override
stop()196     protected boolean stop() {
197         if (DBG) {
198             Log.d(TAG, "stop()");
199         }
200         if (sHapClient == null) {
201             Log.w(TAG, "stop() called before start()");
202             return true;
203         }
204 
205         // Marks service as stopped
206         setHapClient(null);
207 
208         // Unregister broadcast receivers
209         unregisterReceiver(mBondStateChangedReceiver);
210         mBondStateChangedReceiver = null;
211         unregisterReceiver(mConnectionStateChangedReceiver);
212         mConnectionStateChangedReceiver = null;
213 
214         // Destroy state machines and stop handler thread
215         synchronized (mStateMachines) {
216             for (HapClientStateMachine sm : mStateMachines.values()) {
217                 sm.doQuit();
218                 sm.cleanup();
219             }
220             mStateMachines.clear();
221         }
222 
223         if (mStateMachinesThread != null) {
224             try {
225                 mStateMachinesThread.quitSafely();
226                 mStateMachinesThread.join(SM_THREAD_JOIN_TIMEOUT_MS);
227                 mStateMachinesThread = null;
228             } catch (InterruptedException e) {
229                 // Do not rethrow as we are shutting down anyway
230             }
231         }
232 
233         // Cleanup GATT interface
234         mHapClientNativeInterface.cleanup();
235         mHapClientNativeInterface = null;
236 
237         // Cleanup the internals
238         mDeviceCurrentPresetMap.clear();
239         mDeviceFeaturesMap.clear();
240         mPresetsMap.clear();
241 
242         if (mCallbacks != null) {
243             mCallbacks.kill();
244         }
245 
246         // Clear AdapterService
247         mAdapterService = null;
248 
249         return true;
250     }
251 
252     @VisibleForTesting
bondStateChanged(BluetoothDevice device, int bondState)253     void bondStateChanged(BluetoothDevice device, int bondState) {
254         if (DBG) {
255             Log.d(TAG, "Bond state changed for device: " + device + " state: " + bondState);
256         }
257 
258         // Remove state machine if the bonding for a device is removed
259         if (bondState != BluetoothDevice.BOND_NONE) {
260             return;
261         }
262 
263         mDeviceCurrentPresetMap.remove(device);
264         mDeviceFeaturesMap.remove(device);
265         mPresetsMap.remove(device);
266 
267         synchronized (mStateMachines) {
268             HapClientStateMachine sm = mStateMachines.get(device);
269             if (sm == null) {
270                 return;
271             }
272             if (sm.getConnectionState() != BluetoothProfile.STATE_DISCONNECTED) {
273                 Log.i(TAG, "Disconnecting device because it was unbonded.");
274                 disconnect(device);
275                 return;
276             }
277             removeStateMachine(device);
278         }
279     }
280 
removeStateMachine(BluetoothDevice device)281     private void removeStateMachine(BluetoothDevice device) {
282         synchronized (mStateMachines) {
283             HapClientStateMachine sm = mStateMachines.get(device);
284             if (sm == null) {
285                 Log.w(TAG, "removeStateMachine: device " + device
286                         + " does not have a state machine");
287                 return;
288             }
289             Log.i(TAG, "removeStateMachine: removing state machine for device: " + device);
290             sm.doQuit();
291             sm.cleanup();
292             mStateMachines.remove(device);
293         }
294     }
295 
getDevicesMatchingConnectionStates(int[] states)296     List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
297         enforceCallingOrSelfPermission(BLUETOOTH_CONNECT, "Need BLUETOOTH_CONNECT permission");
298         ArrayList<BluetoothDevice> devices = new ArrayList<>();
299         if (states == null) {
300             return devices;
301         }
302         final BluetoothDevice[] bondedDevices = mAdapterService.getBondedDevices();
303         if (bondedDevices == null) {
304             return devices;
305         }
306         synchronized (mStateMachines) {
307             for (BluetoothDevice device : bondedDevices) {
308                 final ParcelUuid[] featureUuids = device.getUuids();
309                 if (!Utils.arrayContains(featureUuids, BluetoothUuid.HAS)) {
310                     continue;
311                 }
312                 int connectionState = BluetoothProfile.STATE_DISCONNECTED;
313                 HapClientStateMachine sm = mStateMachines.get(device);
314                 if (sm != null) {
315                     connectionState = sm.getConnectionState();
316                 }
317                 for (int state : states) {
318                     if (connectionState == state) {
319                         devices.add(device);
320                         break;
321                     }
322                 }
323             }
324             return devices;
325         }
326     }
327 
getConnectedDevices()328     List<BluetoothDevice> getConnectedDevices() {
329         synchronized (mStateMachines) {
330             List<BluetoothDevice> devices = new ArrayList<>();
331             for (HapClientStateMachine sm : mStateMachines.values()) {
332                 if (sm.isConnected()) {
333                     devices.add(sm.getDevice());
334                 }
335             }
336             return devices;
337         }
338     }
339 
340     /**
341      * Get the current connection state of the profile
342      *
343      * @param device is the remote bluetooth device
344      * @return {@link BluetoothProfile#STATE_DISCONNECTED} if this profile is disconnected,
345      * {@link BluetoothProfile#STATE_CONNECTING} if this profile is being connected,
346      * {@link BluetoothProfile#STATE_CONNECTED} if this profile is connected, or
347      * {@link BluetoothProfile#STATE_DISCONNECTING} if this profile is being disconnected
348      */
getConnectionState(BluetoothDevice device)349     public int getConnectionState(BluetoothDevice device) {
350         enforceCallingOrSelfPermission(BLUETOOTH_CONNECT, "Need BLUETOOTH_CONNECT permission");
351         synchronized (mStateMachines) {
352             HapClientStateMachine sm = mStateMachines.get(device);
353             if (sm == null) {
354                 return BluetoothProfile.STATE_DISCONNECTED;
355             }
356             return sm.getConnectionState();
357         }
358     }
359 
360     /**
361      * Set connection policy of the profile and connects it if connectionPolicy is
362      * {@link BluetoothProfile#CONNECTION_POLICY_ALLOWED} or disconnects if connectionPolicy is
363      * {@link BluetoothProfile#CONNECTION_POLICY_FORBIDDEN}
364      *
365      * <p> The device should already be paired.
366      * Connection policy can be one of:
367      * {@link BluetoothProfile#CONNECTION_POLICY_ALLOWED},
368      * {@link BluetoothProfile#CONNECTION_POLICY_FORBIDDEN},
369      * {@link BluetoothProfile#CONNECTION_POLICY_UNKNOWN}
370      *
371      * @param device           the remote device
372      * @param connectionPolicy is the connection policy to set to for this profile
373      * @return true on success, otherwise false
374      */
setConnectionPolicy(BluetoothDevice device, int connectionPolicy)375     public boolean setConnectionPolicy(BluetoothDevice device, int connectionPolicy) {
376         enforceBluetoothPrivilegedPermission(this);
377         if (DBG) {
378             Log.d(TAG, "Saved connectionPolicy " + device + " = " + connectionPolicy);
379         }
380         mDatabaseManager.setProfileConnectionPolicy(device, BluetoothProfile.HAP_CLIENT,
381                         connectionPolicy);
382         if (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
383             connect(device);
384         } else if (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN) {
385             disconnect(device);
386         }
387         return true;
388     }
389 
390     /**
391      * Get the connection policy of the profile.
392      *
393      * <p> The connection policy can be any of:
394      * {@link BluetoothProfile#CONNECTION_POLICY_ALLOWED},
395      * {@link BluetoothProfile#CONNECTION_POLICY_FORBIDDEN},
396      * {@link BluetoothProfile#CONNECTION_POLICY_UNKNOWN}
397      *
398      * @param device Bluetooth device
399      * @return connection policy of the device
400      * @hide
401      */
getConnectionPolicy(BluetoothDevice device)402     public int getConnectionPolicy(BluetoothDevice device) {
403         return mDatabaseManager.getProfileConnectionPolicy(device, BluetoothProfile.HAP_CLIENT);
404     }
405 
406     /**
407      * Check whether can connect to a peer device.
408      * The check considers a number of factors during the evaluation.
409      *
410      * @param device the peer device to connect to
411      * @return true if connection is allowed, otherwise false
412      */
413     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
okToConnect(BluetoothDevice device)414     public boolean okToConnect(BluetoothDevice device) {
415         // Check if this is an incoming connection in Quiet mode.
416         if (mAdapterService.isQuietModeEnabled()) {
417             Log.e(TAG, "okToConnect: cannot connect to " + device + " : quiet mode enabled");
418             return false;
419         }
420         // Check connection policy and accept or reject the connection.
421         int connectionPolicy = getConnectionPolicy(device);
422         int bondState = mAdapterService.getBondState(device);
423         // Allow this connection only if the device is bonded. Any attempt to connect while
424         // bonding would potentially lead to an unauthorized connection.
425         if (bondState != BluetoothDevice.BOND_BONDED) {
426             Log.w(TAG, "okToConnect: return false, bondState=" + bondState);
427             return false;
428         } else if (connectionPolicy != BluetoothProfile.CONNECTION_POLICY_UNKNOWN
429                 && connectionPolicy != BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
430             // Otherwise, reject the connection if connectionPolicy is not valid.
431             Log.w(TAG, "okToConnect: return false, connectionPolicy=" + connectionPolicy);
432             return false;
433         }
434         return true;
435     }
436 
437     @VisibleForTesting
connectionStateChanged(BluetoothDevice device, int fromState, int toState)438     synchronized void connectionStateChanged(BluetoothDevice device, int fromState,
439                                              int toState) {
440         if ((device == null) || (fromState == toState)) {
441             Log.e(TAG, "connectionStateChanged: unexpected invocation. device=" + device
442                     + " fromState=" + fromState + " toState=" + toState);
443             return;
444         }
445 
446         // Check if the device is disconnected - if unbond, remove the state machine
447         if (toState == BluetoothProfile.STATE_DISCONNECTED) {
448             int bondState = mAdapterService.getBondState(device);
449             if (bondState == BluetoothDevice.BOND_NONE) {
450                 if (DBG) {
451                     Log.d(TAG, device + " is unbond. Remove state machine");
452                 }
453                 removeStateMachine(device);
454             }
455         }
456     }
457 
458     /**
459      * Connects the hearing access service client to the passed in device
460      *
461      * @param device is the device with which we will connect the hearing access service client
462      * @return true if hearing access service client successfully connected, false otherwise
463      */
connect(BluetoothDevice device)464     public boolean connect(BluetoothDevice device) {
465         enforceBluetoothPrivilegedPermission(this);
466         if (DBG) {
467             Log.d(TAG, "connect(): " + device);
468         }
469         if (device == null) {
470             return false;
471         }
472 
473         if (getConnectionPolicy(device) == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN) {
474             return false;
475         }
476         ParcelUuid[] featureUuids = mAdapterService.getRemoteUuids(device);
477         if (!Utils.arrayContains(featureUuids, BluetoothUuid.HAS)) {
478             Log.e(TAG, "Cannot connect to " + device
479                     + " : Remote does not have Hearing Access Service UUID");
480             return false;
481         }
482         synchronized (mStateMachines) {
483             HapClientStateMachine smConnect = getOrCreateStateMachine(device);
484             if (smConnect == null) {
485                 Log.e(TAG, "Cannot connect to " + device + " : no state machine");
486             }
487             smConnect.sendMessage(HapClientStateMachine.CONNECT);
488         }
489 
490         return true;
491     }
492 
493     /**
494      * Disconnects hearing access service client for the passed in device
495      *
496      * @param device is the device with which we want to disconnect the hearing access service
497      * client
498      * @return true if hearing access service client successfully disconnected, false otherwise
499      */
disconnect(BluetoothDevice device)500     public boolean disconnect(BluetoothDevice device) {
501         enforceBluetoothPrivilegedPermission(this);
502         if (DBG) {
503             Log.d(TAG, "disconnect(): " + device);
504         }
505         if (device == null) {
506             return false;
507         }
508         synchronized (mStateMachines) {
509             HapClientStateMachine sm = mStateMachines.get(device);
510             if (sm != null) {
511                 sm.sendMessage(HapClientStateMachine.DISCONNECT);
512             }
513         }
514 
515         return true;
516     }
517 
getOrCreateStateMachine(BluetoothDevice device)518     private HapClientStateMachine getOrCreateStateMachine(BluetoothDevice device) {
519         if (device == null) {
520             Log.e(TAG, "getOrCreateStateMachine failed: device cannot be null");
521             return null;
522         }
523         synchronized (mStateMachines) {
524             HapClientStateMachine sm = mStateMachines.get(device);
525             if (sm != null) {
526                 return sm;
527             }
528             // Limit the maximum number of state machines to avoid DoS attack
529             if (mStateMachines.size() >= MAX_HEARING_ACCESS_STATE_MACHINES) {
530                 Log.e(TAG, "Maximum number of HearingAccess state machines reached: "
531                         + MAX_HEARING_ACCESS_STATE_MACHINES);
532                 return null;
533             }
534             if (DBG) {
535                 Log.d(TAG, "Creating a new state machine for " + device);
536             }
537             sm = HapClientStateMachine.make(device, this,
538                     mHapClientNativeInterface, mStateMachinesThread.getLooper());
539             mStateMachines.put(device, sm);
540             return sm;
541         }
542     }
543 
544     /**
545      * Gets the hearing access device group of the passed device
546      *
547      * @param device is the device with which we want to get the group identifier for
548      * @return group ID if device is part of the coordinated group, 0 otherwise
549      */
getHapGroup(BluetoothDevice device)550     public int getHapGroup(BluetoothDevice device) {
551         CsipSetCoordinatorService csipClient = mFactory.getCsipSetCoordinatorService();
552 
553         if (csipClient != null) {
554             Map<Integer, ParcelUuid> groups = csipClient.getGroupUuidMapByDevice(device);
555             for (Map.Entry<Integer, ParcelUuid> entry : groups.entrySet()) {
556                 if (entry.getValue().equals(BluetoothUuid.CAP)) {
557                     return entry.getKey();
558                 }
559             }
560         }
561         return BluetoothCsipSetCoordinator.GROUP_ID_INVALID;
562     }
563 
564     /**
565      * Gets the currently active preset index for a HA device
566      *
567      * @param device is the device for which we want to get the currently active preset
568      * @return active preset index
569      */
getActivePresetIndex(BluetoothDevice device)570     public int getActivePresetIndex(BluetoothDevice device) {
571         return mDeviceCurrentPresetMap.getOrDefault(device,
572                 BluetoothHapClient.PRESET_INDEX_UNAVAILABLE);
573     }
574 
575     /**
576      * Gets the currently active preset info for a HA device
577      *
578      * @param device is the device for which we want to get the currently active preset info
579      * @return active preset info or null if not available
580      */
getActivePresetInfo(BluetoothDevice device)581     public @Nullable BluetoothHapPresetInfo getActivePresetInfo(BluetoothDevice device) {
582         int index = getActivePresetIndex(device);
583         if (index == BluetoothHapClient.PRESET_INDEX_UNAVAILABLE) return null;
584 
585         List<BluetoothHapPresetInfo> current_presets = mPresetsMap.get(device);
586         if (current_presets != null) {
587             for (BluetoothHapPresetInfo preset : current_presets) {
588                 if (preset.getIndex() == index) {
589                     return preset;
590                 }
591             }
592         }
593 
594         return null;
595     }
596 
597     /**
598      * Selects the currently active preset for a HA device
599      *
600      * @param device is the device for which we want to set the active preset
601      * @param presetIndex is an index of one of the available presets
602      */
selectPreset(BluetoothDevice device, int presetIndex)603     public void selectPreset(BluetoothDevice device, int presetIndex) {
604         if (presetIndex == BluetoothHapClient.PRESET_INDEX_UNAVAILABLE) {
605             if (mCallbacks != null) {
606                 int n = mCallbacks.beginBroadcast();
607                 for (int i = 0; i < n; i++) {
608                     try {
609                         mCallbacks.getBroadcastItem(i).onPresetSelectionFailed(device,
610                                 BluetoothStatusCodes.ERROR_HAP_INVALID_PRESET_INDEX);
611                     } catch (RemoteException e) {
612                         continue;
613                     }
614                 }
615                 mCallbacks.finishBroadcast();
616             }
617             return;
618         }
619 
620         mHapClientNativeInterface.selectActivePreset(device, presetIndex);
621     }
622 
623     /**
624      * Selects the currently active preset for a HA device group.
625      *
626      * @param groupId is the device group identifier for which want to set the active preset
627      * @param presetIndex is an index of one of the available presets
628      */
selectPresetForGroup(int groupId, int presetIndex)629     public void selectPresetForGroup(int groupId, int presetIndex) {
630         int status = BluetoothStatusCodes.SUCCESS;
631 
632         if (!isGroupIdValid(groupId)) {
633             status = BluetoothStatusCodes.ERROR_CSIP_INVALID_GROUP_ID;
634         } else if (!isPresetIndexValid(groupId, presetIndex)) {
635             status = BluetoothStatusCodes.ERROR_HAP_INVALID_PRESET_INDEX;
636         }
637 
638         if (status != BluetoothStatusCodes.SUCCESS) {
639             if (mCallbacks != null) {
640                 int n = mCallbacks.beginBroadcast();
641                 for (int i = 0; i < n; i++) {
642                     try {
643                         mCallbacks.getBroadcastItem(i)
644                                 .onPresetSelectionForGroupFailed(groupId, status);
645                     } catch (RemoteException e) {
646                         continue;
647                     }
648                 }
649                 mCallbacks.finishBroadcast();
650             }
651             return;
652         }
653 
654         mHapClientNativeInterface.groupSelectActivePreset(groupId, presetIndex);
655     }
656 
657     /**
658      * Sets the next preset as a currently active preset for a HA device
659      *
660      * @param device is the device for which we want to set the active preset
661      */
switchToNextPreset(BluetoothDevice device)662     public void switchToNextPreset(BluetoothDevice device) {
663         mHapClientNativeInterface.nextActivePreset(device);
664     }
665 
666     /**
667      * Sets the next preset as a currently active preset for a HA device group
668      *
669      * @param groupId is the device group identifier for which want to set the active preset
670      */
switchToNextPresetForGroup(int groupId)671     public void switchToNextPresetForGroup(int groupId) {
672         mHapClientNativeInterface.groupNextActivePreset(groupId);
673     }
674 
675     /**
676      * Sets the previous preset as a currently active preset for a HA device
677      *
678      * @param device is the device for which we want to set the active preset
679      */
switchToPreviousPreset(BluetoothDevice device)680     public void switchToPreviousPreset(BluetoothDevice device) {
681         mHapClientNativeInterface.previousActivePreset(device);
682     }
683 
684     /**
685      * Sets the previous preset as a currently active preset for a HA device group
686      *
687      * @param groupId is the device group identifier for which want to set the active preset
688      */
switchToPreviousPresetForGroup(int groupId)689     public void switchToPreviousPresetForGroup(int groupId) {
690         mHapClientNativeInterface.groupPreviousActivePreset(groupId);
691     }
692 
693     /**
694      * Requests the preset name
695      *
696      * @param device is the device for which we want to get the preset name
697      * @param presetIndex is an index of one of the available presets
698      * @return a preset Info corresponding to the requested preset index or null if not available
699      */
getPresetInfo(BluetoothDevice device, int presetIndex)700     public @Nullable BluetoothHapPresetInfo getPresetInfo(BluetoothDevice device, int presetIndex) {
701         BluetoothHapPresetInfo defaultValue = null;
702         if (presetIndex == BluetoothHapClient.PRESET_INDEX_UNAVAILABLE) return defaultValue;
703 
704         if (Utils.isPtsTestMode()) {
705             /* We want native to be called for PTS testing even we have all
706              * the data in the cache here
707              */
708             mHapClientNativeInterface.getPresetInfo(device, presetIndex);
709         }
710         List<BluetoothHapPresetInfo> current_presets = mPresetsMap.get(device);
711         if (current_presets != null) {
712             for (BluetoothHapPresetInfo preset : current_presets) {
713                 if (preset.getIndex() == presetIndex) {
714                     return preset;
715                 }
716             }
717         }
718 
719         return defaultValue;
720     }
721 
722     /**
723      * Requests all presets info
724      *
725      * @param device is the device for which we want to get all presets info
726      * @return a list of all presets Info
727      */
getAllPresetInfo(BluetoothDevice device)728     public List<BluetoothHapPresetInfo> getAllPresetInfo(BluetoothDevice device) {
729         if (mPresetsMap.containsKey(device)) {
730             return mPresetsMap.get(device);
731         }
732         return Collections.emptyList();
733     }
734 
735     /**
736      * Requests features
737      *
738      * @param device is the device for which we want to get features
739      * @return integer with feature bits set
740      */
getFeatures(BluetoothDevice device)741     public int getFeatures(BluetoothDevice device) {
742         if (mDeviceFeaturesMap.containsKey(device)) {
743             return mDeviceFeaturesMap.get(device);
744         }
745         return 0x00;
746     }
747 
stackEventPresetInfoReasonToProfileStatus(int statusCode)748     private int stackEventPresetInfoReasonToProfileStatus(int statusCode) {
749         switch (statusCode) {
750             case HapClientStackEvent.PRESET_INFO_REASON_ALL_PRESET_INFO:
751                 return BluetoothStatusCodes.REASON_LOCAL_STACK_REQUEST;
752             case HapClientStackEvent.PRESET_INFO_REASON_PRESET_INFO_UPDATE:
753                 return BluetoothStatusCodes.REASON_REMOTE_REQUEST;
754             case HapClientStackEvent.PRESET_INFO_REASON_PRESET_DELETED:
755                 return BluetoothStatusCodes.REASON_REMOTE_REQUEST;
756             case HapClientStackEvent.PRESET_INFO_REASON_PRESET_AVAILABILITY_CHANGED:
757                 return BluetoothStatusCodes.REASON_REMOTE_REQUEST;
758             case HapClientStackEvent.PRESET_INFO_REASON_PRESET_INFO_REQUEST_RESPONSE:
759                 return BluetoothStatusCodes.REASON_LOCAL_APP_REQUEST;
760             default:
761                 return BluetoothStatusCodes.ERROR_UNKNOWN;
762         }
763     }
764 
notifyPresetInfoChanged(BluetoothDevice device, int infoReason)765     private void notifyPresetInfoChanged(BluetoothDevice device, int infoReason) {
766         List current_presets = mPresetsMap.get(device);
767         if (current_presets == null) return;
768 
769         if (mCallbacks != null) {
770             int n = mCallbacks.beginBroadcast();
771             for (int i = 0; i < n; i++) {
772                 try {
773                     mCallbacks.getBroadcastItem(i).onPresetInfoChanged(device, current_presets,
774                             stackEventPresetInfoReasonToProfileStatus(infoReason));
775                 } catch (RemoteException e) {
776                     continue;
777                 }
778             }
779             mCallbacks.finishBroadcast();
780         }
781     }
782 
notifyPresetInfoForGroupChanged(int groupId, int infoReason)783     private void notifyPresetInfoForGroupChanged(int groupId, int infoReason) {
784         List<BluetoothDevice> all_group_devices = getGroupDevices(groupId);
785         for (BluetoothDevice dev : all_group_devices) {
786             notifyPresetInfoChanged(dev, infoReason);
787         }
788     }
789 
notifyFeaturesAvailable(BluetoothDevice device, int features)790     private void notifyFeaturesAvailable(BluetoothDevice device, int features) {
791         Log.d(TAG, "HAP device: " + device + ", features: " + String.format("0x%04X", features));
792     }
793 
notifyActivePresetChanged(BluetoothDevice device, int presetIndex, int reasonCode)794     private void notifyActivePresetChanged(BluetoothDevice device, int presetIndex,
795             int reasonCode) {
796         if (mCallbacks != null) {
797             int n = mCallbacks.beginBroadcast();
798             for (int i = 0; i < n; i++) {
799                 try {
800                     mCallbacks.getBroadcastItem(i).onPresetSelected(device, presetIndex,
801                             reasonCode);
802                 } catch (RemoteException e) {
803                     continue;
804                 }
805             }
806             mCallbacks.finishBroadcast();
807         }
808     }
809 
notifyActivePresetChangedForGroup(int groupId, int presetIndex, int reasonCode)810     private void notifyActivePresetChangedForGroup(int groupId, int presetIndex, int reasonCode) {
811         List<BluetoothDevice> all_group_devices = getGroupDevices(groupId);
812         for (BluetoothDevice dev : all_group_devices) {
813             notifyActivePresetChanged(dev, presetIndex, reasonCode);
814         }
815     }
816 
stackEventStatusToProfileStatus(int statusCode)817     private int stackEventStatusToProfileStatus(int statusCode) {
818         switch (statusCode) {
819             case HapClientStackEvent.STATUS_SET_NAME_NOT_ALLOWED:
820                 return BluetoothStatusCodes.ERROR_REMOTE_OPERATION_REJECTED;
821             case HapClientStackEvent.STATUS_OPERATION_NOT_SUPPORTED:
822                 return BluetoothStatusCodes.ERROR_REMOTE_OPERATION_NOT_SUPPORTED;
823             case HapClientStackEvent.STATUS_OPERATION_NOT_POSSIBLE:
824                 return BluetoothStatusCodes.ERROR_REMOTE_OPERATION_REJECTED;
825             case HapClientStackEvent.STATUS_INVALID_PRESET_NAME_LENGTH:
826                 return BluetoothStatusCodes.ERROR_HAP_PRESET_NAME_TOO_LONG;
827             case HapClientStackEvent.STATUS_INVALID_PRESET_INDEX:
828                 return BluetoothStatusCodes.ERROR_HAP_INVALID_PRESET_INDEX;
829             case HapClientStackEvent.STATUS_GROUP_OPERATION_NOT_SUPPORTED:
830                 return BluetoothStatusCodes.ERROR_REMOTE_OPERATION_NOT_SUPPORTED;
831             case HapClientStackEvent.STATUS_PROCEDURE_ALREADY_IN_PROGRESS:
832                 return BluetoothStatusCodes.ERROR_UNKNOWN;
833             default:
834                 return BluetoothStatusCodes.ERROR_UNKNOWN;
835         }
836     }
837 
notifySelectActivePresetFailed(BluetoothDevice device, int statusCode)838     private void notifySelectActivePresetFailed(BluetoothDevice device, int statusCode) {
839         if (mCallbacks != null) {
840             int n = mCallbacks.beginBroadcast();
841             for (int i = 0; i < n; i++) {
842                 try {
843                     mCallbacks.getBroadcastItem(i).onPresetSelectionFailed(device,
844                             stackEventStatusToProfileStatus(statusCode));
845                 } catch (RemoteException e) {
846                     continue;
847                 }
848             }
849             mCallbacks.finishBroadcast();
850         }
851     }
852 
notifySelectActivePresetForGroupFailed(int groupId, int statusCode)853     private void notifySelectActivePresetForGroupFailed(int groupId, int statusCode) {
854         if (mCallbacks != null) {
855             int n = mCallbacks.beginBroadcast();
856             for (int i = 0; i < n; i++) {
857                 try {
858                     mCallbacks.getBroadcastItem(i).onPresetSelectionForGroupFailed(groupId,
859                             stackEventStatusToProfileStatus(statusCode));
860                 } catch (RemoteException e) {
861                     continue;
862                 }
863             }
864             mCallbacks.finishBroadcast();
865         }
866     }
867 
notifySetPresetNameFailed(BluetoothDevice device, int statusCode)868     private void notifySetPresetNameFailed(BluetoothDevice device, int statusCode) {
869         if (mCallbacks != null) {
870             int n = mCallbacks.beginBroadcast();
871             for (int i = 0; i < n; i++) {
872                 try {
873                     mCallbacks.getBroadcastItem(i).onSetPresetNameFailed(device,
874                             stackEventStatusToProfileStatus(statusCode));
875                 } catch (RemoteException e) {
876                     continue;
877                 }
878             }
879             mCallbacks.finishBroadcast();
880         }
881     }
882 
notifySetPresetNameForGroupFailed(int groupId, int statusCode)883     private void notifySetPresetNameForGroupFailed(int groupId, int statusCode) {
884         if (mCallbacks != null) {
885             int n = mCallbacks.beginBroadcast();
886             for (int i = 0; i < n; i++) {
887                 try {
888                     mCallbacks.getBroadcastItem(i).onSetPresetNameForGroupFailed(groupId,
889                             stackEventStatusToProfileStatus(statusCode));
890                 } catch (RemoteException e) {
891                     continue;
892                 }
893             }
894             mCallbacks.finishBroadcast();
895         }
896     }
897 
isPresetIndexValid(BluetoothDevice device, int presetIndex)898     private boolean isPresetIndexValid(BluetoothDevice device, int presetIndex) {
899         if (presetIndex == BluetoothHapClient.PRESET_INDEX_UNAVAILABLE) return false;
900 
901         List<BluetoothHapPresetInfo> device_presets = mPresetsMap.get(device);
902         if (device_presets != null) {
903             for (BluetoothHapPresetInfo preset : device_presets) {
904                 if (preset.getIndex() == presetIndex) {
905                     return true;
906                 }
907             }
908         }
909         return false;
910     }
911 
isPresetIndexValid(int groupId, int presetIndex)912     private boolean isPresetIndexValid(int groupId, int presetIndex) {
913         List<BluetoothDevice> all_group_devices = getGroupDevices(groupId);
914         if (all_group_devices.isEmpty()) return false;
915 
916         for (BluetoothDevice device : all_group_devices) {
917             if (!isPresetIndexValid(device, presetIndex)) return false;
918         }
919         return true;
920     }
921 
922 
isGroupIdValid(int groupId)923     private boolean isGroupIdValid(int groupId) {
924         if (groupId == BluetoothCsipSetCoordinator.GROUP_ID_INVALID) return false;
925 
926         CsipSetCoordinatorService csipClient = mFactory.getCsipSetCoordinatorService();
927         if (csipClient != null) {
928             List<Integer> groups = csipClient.getAllGroupIds(BluetoothUuid.CAP);
929             return groups.contains(groupId);
930         }
931         return false;
932     }
933 
934     /**
935      * Sets the preset name
936      *
937      * @param device is the device for which we want to get the preset name
938      * @param presetIndex is an index of one of the available presets
939      * @param name is a new name for a preset
940      */
setPresetName(BluetoothDevice device, int presetIndex, String name)941     public void setPresetName(BluetoothDevice device, int presetIndex, String name) {
942         if (!isPresetIndexValid(device, presetIndex)) {
943             if (mCallbacks != null) {
944                 int n = mCallbacks.beginBroadcast();
945                 for (int i = 0; i < n; i++) {
946                     try {
947                         mCallbacks.getBroadcastItem(i).onSetPresetNameFailed(device,
948                                 BluetoothStatusCodes.ERROR_HAP_INVALID_PRESET_INDEX);
949                     } catch (RemoteException e) {
950                         continue;
951                     }
952                 }
953                 mCallbacks.finishBroadcast();
954             }
955             return;
956         }
957         // WARNING: We should check cache if preset exists and is writable, but then we would still
958         //          need a way to trigger this action with an invalid index or on a non-writable
959         //          preset for tests purpose.
960         mHapClientNativeInterface.setPresetName(device, presetIndex, name);
961     }
962 
963     /**
964      * Sets the preset name
965      *
966      * @param groupId is the device group identifier
967      * @param presetIndex is an index of one of the available presets
968      * @param name is a new name for a preset
969      */
setPresetNameForGroup(int groupId, int presetIndex, String name)970     public void setPresetNameForGroup(int groupId, int presetIndex, String name) {
971         int status = BluetoothStatusCodes.SUCCESS;
972 
973         if (!isGroupIdValid(groupId)) {
974             status = BluetoothStatusCodes.ERROR_CSIP_INVALID_GROUP_ID;
975         } else if (!isPresetIndexValid(groupId, presetIndex)) {
976             status = BluetoothStatusCodes.ERROR_HAP_INVALID_PRESET_INDEX;
977         }
978         if (status != BluetoothStatusCodes.SUCCESS) {
979             if (mCallbacks != null) {
980                 int n = mCallbacks.beginBroadcast();
981                 for (int i = 0; i < n; i++) {
982                     try {
983                         mCallbacks.getBroadcastItem(i).onSetPresetNameForGroupFailed(groupId,
984                                 status);
985                     } catch (RemoteException e) {
986                         continue;
987                     }
988                 }
989                 mCallbacks.finishBroadcast();
990             }
991             return;
992         }
993 
994         mHapClientNativeInterface.groupSetPresetName(groupId, presetIndex, name);
995     }
996 
997     @Override
dump(StringBuilder sb)998     public void dump(StringBuilder sb) {
999         super.dump(sb);
1000         for (HapClientStateMachine sm : mStateMachines.values()) {
1001             sm.dump(sb);
1002         }
1003     }
1004 
isPresetCoordinationSupported(BluetoothDevice device)1005     private boolean isPresetCoordinationSupported(BluetoothDevice device) {
1006         Integer features = mDeviceFeaturesMap.getOrDefault(device, 0x00);
1007         return BigInteger.valueOf(features).testBit(
1008                 HapClientStackEvent.FEATURE_BIT_NUM_SYNCHRONIZATED_PRESETS);
1009     }
1010 
updateDevicePresetsCache(BluetoothDevice device, int infoReason, List<BluetoothHapPresetInfo> presets)1011     void updateDevicePresetsCache(BluetoothDevice device, int infoReason,
1012             List<BluetoothHapPresetInfo> presets) {
1013         switch (infoReason) {
1014             case HapClientStackEvent.PRESET_INFO_REASON_ALL_PRESET_INFO:
1015                 mPresetsMap.put(device, presets);
1016                 break;
1017             case HapClientStackEvent.PRESET_INFO_REASON_PRESET_INFO_UPDATE:
1018             case HapClientStackEvent.PRESET_INFO_REASON_PRESET_AVAILABILITY_CHANGED:
1019             case HapClientStackEvent.PRESET_INFO_REASON_PRESET_INFO_REQUEST_RESPONSE: {
1020                 List current_presets = mPresetsMap.get(device);
1021                 if (current_presets != null) {
1022                     ListIterator<BluetoothHapPresetInfo> iter = current_presets.listIterator();
1023                     for (BluetoothHapPresetInfo new_preset : presets) {
1024                         while (iter.hasNext()) {
1025                             if (iter.next().getIndex() == new_preset.getIndex()) {
1026                                 iter.remove();
1027                             }
1028                         }
1029                     }
1030                     current_presets.addAll(presets);
1031                     mPresetsMap.put(device, current_presets);
1032                 } else {
1033                     mPresetsMap.put(device, presets);
1034                 }
1035             }
1036                 break;
1037 
1038             case HapClientStackEvent.PRESET_INFO_REASON_PRESET_DELETED: {
1039                 List current_presets = mPresetsMap.get(device);
1040                 if (current_presets != null) {
1041                     ListIterator<BluetoothHapPresetInfo> iter = current_presets.listIterator();
1042                     for (BluetoothHapPresetInfo new_preset : presets) {
1043                         while (iter.hasNext()) {
1044                             if (iter.next().getIndex() == new_preset.getIndex()) {
1045                                 iter.remove();
1046                             }
1047                         }
1048                     }
1049                     mPresetsMap.put(device, current_presets);
1050                 }
1051             }
1052                 break;
1053 
1054             default:
1055                 break;
1056         }
1057     }
1058 
getGroupDevices(int groupId)1059     private List<BluetoothDevice> getGroupDevices(int groupId) {
1060         List<BluetoothDevice> devices = new ArrayList<>();
1061 
1062         CsipSetCoordinatorService csipClient = mFactory.getCsipSetCoordinatorService();
1063         if (csipClient != null) {
1064             if (groupId != BluetoothLeAudio.GROUP_ID_INVALID) {
1065                 devices = csipClient.getGroupDevicesOrdered(groupId);
1066             }
1067         }
1068         return devices;
1069     }
1070 
1071     /**
1072      * Handle messages from native (JNI) to Java
1073      *
1074      * @param stackEvent the event that need to be handled
1075      */
messageFromNative(HapClientStackEvent stackEvent)1076     public void messageFromNative(HapClientStackEvent stackEvent) {
1077         // Decide which event should be sent to the state machine
1078         if (stackEvent.type == HapClientStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED) {
1079             resendToStateMachine(stackEvent);
1080             return;
1081         }
1082 
1083         Intent intent = null;
1084         BluetoothDevice device = stackEvent.device;
1085 
1086         switch (stackEvent.type) {
1087             case (HapClientStackEvent.EVENT_TYPE_DEVICE_AVAILABLE): {
1088                 int features = stackEvent.valueInt1;
1089 
1090                 if (device != null) {
1091                     mDeviceFeaturesMap.put(device, features);
1092 
1093                     intent = new Intent(BluetoothHapClient.ACTION_HAP_DEVICE_AVAILABLE);
1094                     intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
1095                     intent.putExtra(BluetoothHapClient.EXTRA_HAP_FEATURES, features);
1096                 }
1097             } break;
1098 
1099             case (HapClientStackEvent.EVENT_TYPE_DEVICE_FEATURES): {
1100                 int features = stackEvent.valueInt1;
1101 
1102                 if (device != null) {
1103                     mDeviceFeaturesMap.put(device, features);
1104                     notifyFeaturesAvailable(device, features);
1105                 }
1106             } return;
1107 
1108             case (HapClientStackEvent.EVENT_TYPE_ON_ACTIVE_PRESET_SELECTED): {
1109                 int currentPresetIndex = stackEvent.valueInt1;
1110                 int groupId = stackEvent.valueInt2;
1111 
1112                 if (device != null) {
1113                     mDeviceCurrentPresetMap.put(device, currentPresetIndex);
1114                     // FIXME: Add app request queueing to support other reasons
1115                     int reasonCode = BluetoothStatusCodes.REASON_LOCAL_STACK_REQUEST;
1116                     notifyActivePresetChanged(device, currentPresetIndex, reasonCode);
1117 
1118                 } else if (groupId != BluetoothCsipSetCoordinator.GROUP_ID_INVALID) {
1119                     List<BluetoothDevice> all_group_devices = getGroupDevices(groupId);
1120                     for (BluetoothDevice dev : all_group_devices) {
1121                         mDeviceCurrentPresetMap.put(dev, currentPresetIndex);
1122                     }
1123                     // FIXME: Add app request queueing to support other reasons
1124                     int reasonCode = BluetoothStatusCodes.REASON_LOCAL_STACK_REQUEST;
1125                     notifyActivePresetChangedForGroup(groupId, currentPresetIndex, reasonCode);
1126                 }
1127             } return;
1128 
1129             case (HapClientStackEvent.EVENT_TYPE_ON_ACTIVE_PRESET_SELECT_ERROR): {
1130                 int groupId = stackEvent.valueInt2;
1131                 int statusCode = stackEvent.valueInt1;
1132 
1133                 if (device != null) {
1134                     notifySelectActivePresetFailed(device, statusCode);
1135                 } else if (groupId != BluetoothCsipSetCoordinator.GROUP_ID_INVALID) {
1136                     notifySelectActivePresetForGroupFailed(groupId, statusCode);
1137                 }
1138             } break;
1139 
1140             case (HapClientStackEvent.EVENT_TYPE_ON_PRESET_INFO): {
1141                 int presetIndex = stackEvent.valueInt1;
1142                 int infoReason = stackEvent.valueInt2;
1143                 int groupId = stackEvent.valueInt3;
1144                 ArrayList presets = stackEvent.valueList;
1145 
1146                 if (device != null) {
1147                     updateDevicePresetsCache(device, infoReason, presets);
1148                     notifyPresetInfoChanged(device, infoReason);
1149 
1150                 } else if (groupId != BluetoothCsipSetCoordinator.GROUP_ID_INVALID) {
1151                     List<BluetoothDevice> all_group_devices = getGroupDevices(groupId);
1152                     for (BluetoothDevice dev : all_group_devices) {
1153                         updateDevicePresetsCache(dev, infoReason, presets);
1154                     }
1155                     notifyPresetInfoForGroupChanged(groupId, infoReason);
1156                 }
1157 
1158             } return;
1159 
1160             case (HapClientStackEvent.EVENT_TYPE_ON_PRESET_NAME_SET_ERROR): {
1161                 int statusCode = stackEvent.valueInt1;
1162                 int presetIndex = stackEvent.valueInt2;
1163                 int groupId = stackEvent.valueInt3;
1164 
1165                 if (device != null) {
1166                     notifySetPresetNameFailed(device, statusCode);
1167                 } else if (groupId != BluetoothCsipSetCoordinator.GROUP_ID_INVALID) {
1168                     notifySetPresetNameForGroupFailed(groupId, statusCode);
1169                 }
1170             } break;
1171 
1172             case (HapClientStackEvent.EVENT_TYPE_ON_PRESET_INFO_ERROR): {
1173                 // Used only to report back on hidden API calls used for testing.
1174                 Log.d(TAG, stackEvent.toString());
1175             } break;
1176 
1177             default:
1178                 return;
1179         }
1180 
1181         if (intent != null) {
1182             sendBroadcast(intent, BLUETOOTH_PRIVILEGED);
1183         }
1184     }
1185 
resendToStateMachine(HapClientStackEvent stackEvent)1186     private void resendToStateMachine(HapClientStackEvent stackEvent) {
1187         synchronized (mStateMachines) {
1188             BluetoothDevice device = stackEvent.device;
1189             HapClientStateMachine sm = mStateMachines.get(device);
1190 
1191             if (sm == null) {
1192                 if (stackEvent.type == HapClientStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED) {
1193                     switch (stackEvent.valueInt1) {
1194                         case HapClientStackEvent.CONNECTION_STATE_CONNECTED:
1195                         case HapClientStackEvent.CONNECTION_STATE_CONNECTING:
1196                             sm = getOrCreateStateMachine(device);
1197                             break;
1198                         default:
1199                             break;
1200                     }
1201                 }
1202             }
1203             if (sm == null) {
1204                 Log.e(TAG, "Cannot process stack event: no state machine: " + stackEvent);
1205                 return;
1206             }
1207             sm.sendMessage(HapClientStateMachine.STACK_EVENT, stackEvent);
1208         }
1209     }
1210 
1211     /**
1212      * Binder object: must be a static class or memory leak may occur
1213      */
1214     @VisibleForTesting
1215     static class BluetoothHapClientBinder extends IBluetoothHapClient.Stub
1216             implements IProfileServiceBinder {
1217         @VisibleForTesting
1218         boolean mIsTesting = false;
1219         private HapClientService mService;
1220 
BluetoothHapClientBinder(HapClientService svc)1221         BluetoothHapClientBinder(HapClientService svc) {
1222             mService = svc;
1223         }
1224 
getService(AttributionSource source)1225         private HapClientService getService(AttributionSource source) {
1226             if (mIsTesting) {
1227                 return mService;
1228             }
1229             if (!Utils.checkServiceAvailable(mService, TAG)
1230                     || !Utils.checkCallerIsSystemOrActiveOrManagedUser(mService, TAG)
1231                     || !Utils.checkConnectPermissionForDataDelivery(mService, source, TAG)) {
1232                 Log.w(TAG, "Hearing Access call not allowed for non-active user");
1233                 return null;
1234             }
1235 
1236             if (mService != null && mService.isAvailable()) {
1237                 return mService;
1238             }
1239             return null;
1240         }
1241 
1242         @Override
cleanup()1243         public void cleanup() {
1244             mService = null;
1245         }
1246 
1247         @Override
getConnectedDevices(AttributionSource source, SynchronousResultReceiver receiver)1248         public void getConnectedDevices(AttributionSource source,
1249                 SynchronousResultReceiver receiver) {
1250             try {
1251                 Objects.requireNonNull(source, "source cannot be null");
1252                 Objects.requireNonNull(receiver, "receiver cannot be null");
1253                 List<BluetoothDevice> defaultValue = new ArrayList<>();
1254                 HapClientService service = getService(source);
1255                 if (service == null) {
1256                     throw new IllegalStateException("service is null");
1257                 }
1258                 enforceBluetoothPrivilegedPermission(service);
1259                 defaultValue = service.getConnectedDevices();
1260                 receiver.send(defaultValue);
1261             } catch (RuntimeException e) {
1262                 receiver.propagateException(e);
1263             }
1264         }
1265 
1266         @Override
getDevicesMatchingConnectionStates(int[] states, AttributionSource source, SynchronousResultReceiver receiver)1267         public void getDevicesMatchingConnectionStates(int[] states,
1268                 AttributionSource source, SynchronousResultReceiver receiver) {
1269             try {
1270                 Objects.requireNonNull(source, "source cannot be null");
1271                 Objects.requireNonNull(receiver, "receiver cannot be null");
1272                 List<BluetoothDevice> defaultValue = new ArrayList<>();
1273                 HapClientService service = getService(source);
1274                 if (service == null) {
1275                     throw new IllegalStateException("service is null");
1276                 }
1277                 enforceBluetoothPrivilegedPermission(service);
1278                 defaultValue = service.getDevicesMatchingConnectionStates(states);
1279                 receiver.send(defaultValue);
1280             } catch (RuntimeException e) {
1281                 receiver.propagateException(e);
1282             }
1283         }
1284 
1285         @Override
getConnectionState(BluetoothDevice device, AttributionSource source, SynchronousResultReceiver receiver)1286         public void getConnectionState(BluetoothDevice device, AttributionSource source,
1287                 SynchronousResultReceiver receiver) {
1288             try {
1289                 Objects.requireNonNull(device, "device cannot be null");
1290                 Objects.requireNonNull(source, "source cannot be null");
1291                 Objects.requireNonNull(receiver, "receiver cannot be null");
1292                 int defaultValue = BluetoothProfile.STATE_DISCONNECTED;
1293                 HapClientService service = getService(source);
1294                 if (service == null) {
1295                     throw new IllegalStateException("service is null");
1296                 }
1297                 enforceBluetoothPrivilegedPermission(service);
1298                 defaultValue = service.getConnectionState(device);
1299                 receiver.send(defaultValue);
1300             } catch (RuntimeException e) {
1301                 receiver.propagateException(e);
1302             }
1303         }
1304 
1305         @Override
setConnectionPolicy(BluetoothDevice device, int connectionPolicy, AttributionSource source, SynchronousResultReceiver receiver)1306         public void setConnectionPolicy(BluetoothDevice device, int connectionPolicy,
1307                 AttributionSource source, SynchronousResultReceiver receiver) {
1308             try {
1309                 Objects.requireNonNull(device, "device cannot be null");
1310                 Objects.requireNonNull(source, "source cannot be null");
1311                 Objects.requireNonNull(receiver, "receiver cannot be null");
1312                 boolean defaultValue = false;
1313                 HapClientService service = getService(source);
1314                 if (service == null) {
1315                     throw new IllegalStateException("service is null");
1316                 }
1317                 enforceBluetoothPrivilegedPermission(service);
1318                 defaultValue = service.setConnectionPolicy(device, connectionPolicy);
1319                 receiver.send(defaultValue);
1320             } catch (RuntimeException e) {
1321                 receiver.propagateException(e);
1322             }
1323         }
1324 
1325         @Override
getConnectionPolicy(BluetoothDevice device, AttributionSource source, SynchronousResultReceiver receiver)1326         public void getConnectionPolicy(BluetoothDevice device, AttributionSource source,
1327                 SynchronousResultReceiver receiver) {
1328             try {
1329                 Objects.requireNonNull(device, "device cannot be null");
1330                 Objects.requireNonNull(source, "source cannot be null");
1331                 Objects.requireNonNull(receiver, "receiver cannot be null");
1332                 int defaultValue = BluetoothProfile.CONNECTION_POLICY_UNKNOWN;
1333                 HapClientService service = getService(source);
1334                 if (service == null) {
1335                     throw new IllegalStateException("service is null");
1336                 }
1337                 enforceBluetoothPrivilegedPermission(service);
1338                 defaultValue = service.getConnectionPolicy(device);
1339                 receiver.send(defaultValue);
1340             } catch (RuntimeException e) {
1341                 receiver.propagateException(e);
1342             }
1343         }
1344 
1345         @Override
getActivePresetIndex(BluetoothDevice device, AttributionSource source, SynchronousResultReceiver receiver)1346         public void getActivePresetIndex(BluetoothDevice device, AttributionSource source,
1347                 SynchronousResultReceiver receiver) {
1348             try {
1349                 Objects.requireNonNull(device, "device cannot be null");
1350                 Objects.requireNonNull(source, "source cannot be null");
1351                 Objects.requireNonNull(receiver, "receiver cannot be null");
1352                 int defaultValue = BluetoothHapClient.PRESET_INDEX_UNAVAILABLE;
1353                 HapClientService service = getService(source);
1354                 if (service == null) {
1355                     throw new IllegalStateException("service is null");
1356                 }
1357                 enforceBluetoothPrivilegedPermission(service);
1358                 defaultValue = service.getActivePresetIndex(device);
1359                 receiver.send(defaultValue);
1360             } catch (RuntimeException e) {
1361                 receiver.propagateException(e);
1362             }
1363         }
1364 
1365         @Override
getActivePresetInfo(BluetoothDevice device, AttributionSource source, SynchronousResultReceiver receiver)1366         public void getActivePresetInfo(BluetoothDevice device,
1367                 AttributionSource source, SynchronousResultReceiver receiver) {
1368             try {
1369                 Objects.requireNonNull(device, "device cannot be null");
1370                 Objects.requireNonNull(source, "source cannot be null");
1371                 Objects.requireNonNull(receiver, "receiver cannot be null");
1372                 BluetoothHapPresetInfo defaultValue = null;
1373                 HapClientService service = getService(source);
1374                 if (service == null) {
1375                     throw new IllegalStateException("service is null");
1376                 }
1377                 enforceBluetoothPrivilegedPermission(service);
1378                 defaultValue = service.getActivePresetInfo(device);
1379                 receiver.send(defaultValue);
1380             } catch (RuntimeException e) {
1381                 receiver.propagateException(e);
1382             }
1383         }
1384 
1385         @Override
getHapGroup(BluetoothDevice device, AttributionSource source, SynchronousResultReceiver receiver)1386         public void getHapGroup(BluetoothDevice device, AttributionSource source,
1387                 SynchronousResultReceiver receiver) {
1388             try {
1389                 Objects.requireNonNull(device, "device cannot be null");
1390                 Objects.requireNonNull(source, "source cannot be null");
1391                 Objects.requireNonNull(receiver, "receiver cannot be null");
1392                 int defaultValue = BluetoothCsipSetCoordinator.GROUP_ID_INVALID;
1393                 HapClientService service = getService(source);
1394                 if (service == null) {
1395                     throw new IllegalStateException("service is null");
1396                 }
1397                 enforceBluetoothPrivilegedPermission(service);
1398                 defaultValue = service.getHapGroup(device);
1399                 receiver.send(defaultValue);
1400             } catch (RuntimeException e) {
1401                 receiver.propagateException(e);
1402             }
1403         }
1404 
1405         @Override
selectPreset(BluetoothDevice device, int presetIndex, AttributionSource source)1406         public void selectPreset(BluetoothDevice device, int presetIndex,
1407                 AttributionSource source) {
1408             if (source == null) {
1409                 Log.w(TAG, "source cannot be null");
1410                 return;
1411             }
1412 
1413             HapClientService service = getService(source);
1414             if (service == null) {
1415                 Log.w(TAG, "service is null");
1416                 return;
1417             }
1418             enforceBluetoothPrivilegedPermission(service);
1419             service.selectPreset(device, presetIndex);
1420         }
1421 
1422         @Override
selectPresetForGroup(int groupId, int presetIndex, AttributionSource source)1423         public void selectPresetForGroup(int groupId, int presetIndex, AttributionSource source) {
1424             if (source == null) {
1425                 Log.w(TAG, "source cannot be null");
1426                 return;
1427             }
1428 
1429             HapClientService service = getService(source);
1430             if (service == null) {
1431                 Log.w(TAG, "service is null");
1432                 return;
1433             }
1434             enforceBluetoothPrivilegedPermission(service);
1435             service.selectPresetForGroup(groupId, presetIndex);
1436         }
1437 
1438         @Override
switchToNextPreset(BluetoothDevice device, AttributionSource source)1439         public void switchToNextPreset(BluetoothDevice device, AttributionSource source) {
1440             if (source == null) {
1441                 Log.w(TAG, "source cannot be null");
1442                 return;
1443             }
1444 
1445             HapClientService service = getService(source);
1446             if (service == null) {
1447                 Log.w(TAG, "service is null");
1448                 return;
1449             }
1450             enforceBluetoothPrivilegedPermission(service);
1451             service.switchToNextPreset(device);
1452         }
1453 
1454         @Override
switchToNextPresetForGroup(int groupId, AttributionSource source)1455         public void switchToNextPresetForGroup(int groupId, AttributionSource source) {
1456             if (source == null) {
1457                 Log.w(TAG, "source cannot be null");
1458                 return;
1459             }
1460 
1461             HapClientService service = getService(source);
1462             if (service == null) {
1463                 Log.w(TAG, "service is null");
1464                 return;
1465             }
1466             enforceBluetoothPrivilegedPermission(service);
1467             service.switchToNextPresetForGroup(groupId);
1468         }
1469 
1470         @Override
switchToPreviousPreset(BluetoothDevice device, AttributionSource source)1471         public void switchToPreviousPreset(BluetoothDevice device, AttributionSource source) {
1472             if (source == null) {
1473                 Log.w(TAG, "source cannot be null");
1474                 return;
1475             }
1476 
1477             HapClientService service = getService(source);
1478             if (service == null) {
1479                 Log.w(TAG, "service is null");
1480                 return;
1481             }
1482             enforceBluetoothPrivilegedPermission(service);
1483             service.switchToPreviousPreset(device);
1484         }
1485 
1486         @Override
switchToPreviousPresetForGroup(int groupId, AttributionSource source)1487         public void switchToPreviousPresetForGroup(int groupId, AttributionSource source) {
1488             if (source == null) {
1489                 Log.w(TAG, "source cannot be null");
1490                 return;
1491             }
1492 
1493             HapClientService service = getService(source);
1494             if (service == null) {
1495                 Log.w(TAG, "service is null");
1496                 return;
1497             }
1498             enforceBluetoothPrivilegedPermission(service);
1499             service.switchToPreviousPresetForGroup(groupId);
1500         }
1501 
1502         @Override
getPresetInfo(BluetoothDevice device, int presetIndex, AttributionSource source, SynchronousResultReceiver receiver)1503         public void getPresetInfo(BluetoothDevice device, int presetIndex,
1504                 AttributionSource source, SynchronousResultReceiver receiver) {
1505 
1506             try {
1507                 Objects.requireNonNull(device, "device cannot be null");
1508                 Objects.requireNonNull(source, "source cannot be null");
1509                 Objects.requireNonNull(receiver, "receiver cannot be null");
1510                 BluetoothHapPresetInfo defaultValue = null;
1511                 HapClientService service = getService(source);
1512                 if (service == null) {
1513                     throw new IllegalStateException("service is null");
1514                 }
1515                 enforceBluetoothPrivilegedPermission(service);
1516                 defaultValue = service.getPresetInfo(device, presetIndex);
1517                 receiver.send(defaultValue);
1518             } catch (RuntimeException e) {
1519                 receiver.propagateException(e);
1520             }
1521         }
1522 
1523         @Override
getAllPresetInfo(BluetoothDevice device, AttributionSource source, SynchronousResultReceiver receiver)1524         public void getAllPresetInfo(BluetoothDevice device, AttributionSource source,
1525                 SynchronousResultReceiver receiver) {
1526             try {
1527                 Objects.requireNonNull(device, "device cannot be null");
1528                 Objects.requireNonNull(source, "source cannot be null");
1529                 Objects.requireNonNull(receiver, "receiver cannot be null");
1530                 List<BluetoothHapPresetInfo> defaultValue = new ArrayList<>();
1531                 HapClientService service = getService(source);
1532                 if (service == null) {
1533                     throw new IllegalStateException("service is null");
1534                 }
1535                 enforceBluetoothPrivilegedPermission(service);
1536                 defaultValue = service.getAllPresetInfo(device);
1537                 receiver.send(defaultValue);
1538             } catch (RuntimeException e) {
1539                 receiver.propagateException(e);
1540             }
1541         }
1542 
1543         @Override
getFeatures(BluetoothDevice device, AttributionSource source, SynchronousResultReceiver receiver)1544         public void getFeatures(BluetoothDevice device, AttributionSource source,
1545                 SynchronousResultReceiver receiver) {
1546             try {
1547                 Objects.requireNonNull(device, "device cannot be null");
1548                 Objects.requireNonNull(source, "source cannot be null");
1549                 Objects.requireNonNull(receiver, "receiver cannot be null");
1550                 int defaultValue = 0x00;
1551                 HapClientService service = getService(source);
1552                 if (service == null) {
1553                     throw new IllegalStateException("service is null");
1554                 }
1555                 enforceBluetoothPrivilegedPermission(service);
1556                 defaultValue = service.getFeatures(device);
1557                 receiver.send(defaultValue);
1558             } catch (RuntimeException e) {
1559                 receiver.propagateException(e);
1560             }
1561         }
1562 
1563         @Override
setPresetName(BluetoothDevice device, int presetIndex, String name, AttributionSource source)1564         public void setPresetName(BluetoothDevice device, int presetIndex, String name,
1565                 AttributionSource source) {
1566             if (device == null) {
1567                 Log.w(TAG, "device cannot be null");
1568                 return;
1569             }
1570             if (name == null) {
1571                 Log.w(TAG, "name cannot be null");
1572                 return;
1573             }
1574             if (source == null) {
1575                 Log.w(TAG, "source cannot be null");
1576                 return;
1577             }
1578 
1579             HapClientService service = getService(source);
1580             if (service == null) {
1581                 Log.w(TAG, "service is null");
1582                 return;
1583             }
1584             enforceBluetoothPrivilegedPermission(service);
1585             service.setPresetName(device, presetIndex, name);
1586         }
1587 
1588         @Override
setPresetNameForGroup(int groupId, int presetIndex, String name, AttributionSource source)1589         public void setPresetNameForGroup(int groupId, int presetIndex, String name,
1590                 AttributionSource source) {
1591             if (name == null) {
1592                 Log.w(TAG, "name cannot be null");
1593                 return;
1594             }
1595             if (source == null) {
1596                 Log.w(TAG, "source cannot be null");
1597                 return;
1598             }
1599             HapClientService service = getService(source);
1600             if (service == null) {
1601                 Log.w(TAG, "service is null");
1602                 return;
1603             }
1604             enforceBluetoothPrivilegedPermission(service);
1605             service.setPresetNameForGroup(groupId, presetIndex, name);
1606         }
1607 
1608         @Override
registerCallback(IBluetoothHapClientCallback callback, AttributionSource source, SynchronousResultReceiver receiver)1609         public void registerCallback(IBluetoothHapClientCallback callback,
1610                 AttributionSource source, SynchronousResultReceiver receiver) {
1611             try {
1612                 Objects.requireNonNull(callback, "callback cannot be null");
1613                 Objects.requireNonNull(source, "source cannot be null");
1614                 Objects.requireNonNull(receiver, "receiver cannot be null");
1615                 HapClientService service = getService(source);
1616                 if (service == null) {
1617                     throw new IllegalStateException("Service is unavailable");
1618                 }
1619                 enforceBluetoothPrivilegedPermission(service);
1620                 service.mCallbacks.register(callback);
1621                 receiver.send(null);
1622             } catch (RuntimeException e) {
1623                 receiver.propagateException(e);
1624             }
1625         }
1626 
1627         @Override
unregisterCallback(IBluetoothHapClientCallback callback, AttributionSource source, SynchronousResultReceiver receiver)1628         public void unregisterCallback(IBluetoothHapClientCallback callback,
1629                 AttributionSource source, SynchronousResultReceiver receiver) {
1630             try {
1631                 Objects.requireNonNull(callback, "callback cannot be null");
1632                 Objects.requireNonNull(source, "source cannot be null");
1633                 Objects.requireNonNull(receiver, "receiver cannot be null");
1634 
1635                 HapClientService service = getService(source);
1636                 if (service == null) {
1637                     throw new IllegalStateException("Service is unavailable");
1638                 }
1639                 enforceBluetoothPrivilegedPermission(service);
1640                 service.mCallbacks.unregister(callback);
1641                 receiver.send(null);
1642             } catch (RuntimeException e) {
1643                 receiver.propagateException(e);
1644             }
1645         }
1646     }
1647 
1648     // Remove state machine if the bonding for a device is removed
1649     private class BondStateChangedReceiver extends BroadcastReceiver {
1650         @Override
onReceive(Context context, Intent intent)1651         public void onReceive(Context context, Intent intent) {
1652             if (!BluetoothDevice.ACTION_BOND_STATE_CHANGED.equals(intent.getAction())) {
1653                 return;
1654             }
1655             int state = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE,
1656                     BluetoothDevice.ERROR);
1657             BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
1658             Objects.requireNonNull(device, "ACTION_BOND_STATE_CHANGED with no EXTRA_DEVICE");
1659             bondStateChanged(device, state);
1660         }
1661     }
1662 
1663     private class ConnectionStateChangedReceiver extends BroadcastReceiver {
1664         @Override
onReceive(Context context, Intent intent)1665         public void onReceive(Context context, Intent intent) {
1666             if (!BluetoothHapClient.ACTION_HAP_CONNECTION_STATE_CHANGED.equals(
1667                     intent.getAction())) {
1668                 return;
1669             }
1670             BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
1671             int toState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1);
1672             int fromState = intent.getIntExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, -1);
1673             connectionStateChanged(device, fromState, toState);
1674         }
1675     }
1676 }
1677