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