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