• 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.leaudio;
19 
20 import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED;
21 import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
22 import static android.bluetooth.BluetoothProfile.STATE_CONNECTED;
23 import static android.bluetooth.BluetoothProfile.STATE_DISCONNECTED;
24 
25 import android.app.Application;
26 import android.bluetooth.*;
27 import android.content.BroadcastReceiver;
28 import android.content.Context;
29 import android.content.Intent;
30 import android.content.IntentFilter;
31 import android.os.ParcelUuid;
32 import android.util.Log;
33 
34 import androidx.annotation.Nullable;
35 import androidx.core.util.Pair;
36 import androidx.lifecycle.LiveData;
37 import androidx.lifecycle.MutableLiveData;
38 
39 import java.lang.reflect.InvocationTargetException;
40 import java.lang.reflect.Method;
41 import java.util.ArrayList;
42 import java.util.Arrays;
43 import java.util.Collections;
44 import java.util.HashMap;
45 import java.util.HashSet;
46 import java.util.List;
47 import java.util.ListIterator;
48 import java.util.Map;
49 import java.util.Objects;
50 import java.util.Optional;
51 import java.util.Set;
52 import java.util.UUID;
53 import java.util.concurrent.ExecutorService;
54 import java.util.concurrent.Executors;
55 import java.util.stream.Collectors;
56 
57 public class BluetoothProxy {
58     private static BluetoothProxy INSTANCE;
59     private final Application application;
60     private final BluetoothAdapter bluetoothAdapter;
61     private BluetoothLeAudio bluetoothLeAudio = null;
62     private BluetoothLeBroadcast mBluetoothLeBroadcast = null;
63     private BluetoothLeBroadcastAssistant mBluetoothLeBroadcastAssistant = null;
64     private Set<BluetoothDevice> mBroadcastScanDelegatorDevices = new HashSet<>();
65     private BluetoothCsipSetCoordinator bluetoothCsis = null;
66     private BluetoothVolumeControl bluetoothVolumeControl = null;
67     private BluetoothHapClient bluetoothHapClient = null;
68     private Map<LeAudioDeviceStateWrapper, BluetoothGatt> bluetoothGattMap = new HashMap<>();
69     private BluetoothProfile.ServiceListener profileListener = null;
70     private BluetoothHapClient.Callback hapCallback = null;
71     private OnBassEventListener mBassEventListener;
72     private OnLocalBroadcastEventListener mLocalBroadcastEventListener;
73     private final IntentFilter adapterIntentFilter;
74     private final IntentFilter bassIntentFilter;
75     private IntentFilter intentFilter;
76     private final ExecutorService mExecutor;
77 
78     private final Map<Integer, UUID> mGroupLocks = new HashMap<>();
79 
80     private int GROUP_NODE_ADDED = 1;
81     private int GROUP_NODE_REMOVED = 2;
82 
83     private boolean mLeAudioCallbackRegistered = false;
84     private BluetoothLeAudio.Callback mLeAudioCallbacks =
85             new BluetoothLeAudio.Callback() {
86                 @Override
87                 public void onCodecConfigChanged(int groupId, BluetoothLeAudioCodecStatus status) {}
88 
89                 @Override
90                 public void onGroupStatusChanged(int groupId, int groupStatus) {
91                     List<LeAudioDeviceStateWrapper> valid_devices = null;
92                     valid_devices =
93                             allLeAudioDevicesMutable.getValue().stream()
94                                     .filter(
95                                             state ->
96                                                     state.leAudioData != null
97                                                             && state.leAudioData.nodeStatusMutable
98                                                                             .getValue()
99                                                                     != null
100                                                             && state.leAudioData
101                                                                     .nodeStatusMutable
102                                                                     .getValue()
103                                                                     .first
104                                                                     .equals(groupId))
105                                     .collect(Collectors.toList());
106                     for (LeAudioDeviceStateWrapper dev : valid_devices) {
107                         dev.leAudioData.groupStatusMutable.postValue(
108                                 new Pair<>(groupId, new Pair<>(groupStatus, 0)));
109                     }
110                 }
111 
112                 @Override
113                 public void onGroupNodeAdded(BluetoothDevice device, int groupId) {
114                     Log.d("LeCB:", device + " group added " + groupId);
115                     if (device == null || groupId == BluetoothLeAudio.GROUP_ID_INVALID) {
116                         Log.d("LeCB:", "invalid parameter");
117                         return;
118                     }
119                     Optional<LeAudioDeviceStateWrapper> valid_device_opt =
120                             allLeAudioDevicesMutable.getValue().stream()
121                                     .filter(
122                                             state ->
123                                                     state.device
124                                                             .getAddress()
125                                                             .equals(device.getAddress()))
126                                     .findAny();
127 
128                     if (!valid_device_opt.isPresent()) {
129                         Log.d("LeCB:", "Device not present");
130                         return;
131                     }
132 
133                     LeAudioDeviceStateWrapper valid_device = valid_device_opt.get();
134                     LeAudioDeviceStateWrapper.LeAudioData svc_data = valid_device.leAudioData;
135 
136                     svc_data.nodeStatusMutable.postValue(new Pair<>(groupId, GROUP_NODE_ADDED));
137                     svc_data.groupStatusMutable.postValue(new Pair<>(groupId, new Pair<>(-1, -1)));
138                 }
139 
140                 @Override
141                 public void onGroupNodeRemoved(BluetoothDevice device, int groupId) {
142                     if (device == null || groupId == BluetoothLeAudio.GROUP_ID_INVALID) {
143                         Log.d("LeCB:", "invalid parameter");
144                         return;
145                     }
146 
147                     Log.d("LeCB:", device + " group added " + groupId);
148                     if (device == null || groupId == BluetoothLeAudio.GROUP_ID_INVALID) {
149                         Log.d("LeCB:", "invalid parameter");
150                         return;
151                     }
152 
153                     Optional<LeAudioDeviceStateWrapper> valid_device_opt =
154                             allLeAudioDevicesMutable.getValue().stream()
155                                     .filter(
156                                             state ->
157                                                     state.device
158                                                             .getAddress()
159                                                             .equals(device.getAddress()))
160                                     .findAny();
161 
162                     if (!valid_device_opt.isPresent()) {
163                         Log.d("LeCB:", "Device not present");
164                         return;
165                     }
166 
167                     LeAudioDeviceStateWrapper valid_device = valid_device_opt.get();
168                     LeAudioDeviceStateWrapper.LeAudioData svc_data = valid_device.leAudioData;
169 
170                     svc_data.nodeStatusMutable.postValue(new Pair<>(groupId, GROUP_NODE_REMOVED));
171                     svc_data.groupStatusMutable.postValue(new Pair<>(groupId, new Pair<>(-1, -1)));
172                 }
173             };
174 
175     private final MutableLiveData<Boolean> enabledBluetoothMutable;
176     private final BroadcastReceiver adapterIntentReceiver =
177             new BroadcastReceiver() {
178                 @Override
179                 public void onReceive(Context context, Intent intent) {
180                     String action = intent.getAction();
181                     if (BluetoothAdapter.ACTION_STATE_CHANGED.equals(action)) {
182                         int toState =
183                                 intent.getIntExtra(
184                                         BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR);
185                         if (toState == BluetoothAdapter.STATE_ON) {
186                             enabledBluetoothMutable.postValue(true);
187                         } else if (toState == BluetoothAdapter.STATE_OFF) {
188                             enabledBluetoothMutable.postValue(false);
189                         }
190                     }
191                 }
192             };
193     private final MutableLiveData<List<LeAudioDeviceStateWrapper>> allLeAudioDevicesMutable;
194     private final BroadcastReceiver leAudioIntentReceiver =
195             new BroadcastReceiver() {
196                 @Override
197                 public void onReceive(Context context, Intent intent) {
198                     String action = intent.getAction();
199                     final BluetoothDevice device =
200                             intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
201 
202                     if (allLeAudioDevicesMutable.getValue() != null) {
203                         if (device != null) {
204                             Optional<LeAudioDeviceStateWrapper> valid_device_opt =
205                                     allLeAudioDevicesMutable.getValue().stream()
206                                             .filter(
207                                                     state ->
208                                                             state.device
209                                                                     .getAddress()
210                                                                     .equals(device.getAddress()))
211                                             .findAny();
212 
213                             if (valid_device_opt.isPresent()) {
214                                 LeAudioDeviceStateWrapper valid_device = valid_device_opt.get();
215                                 LeAudioDeviceStateWrapper.LeAudioData svc_data =
216                                         valid_device.leAudioData;
217                                 int group_id;
218 
219                                 // Handle Le Audio actions
220                                 switch (action) {
221                                     case BluetoothLeAudio.ACTION_LE_AUDIO_CONNECTION_STATE_CHANGED:
222                                         {
223                                             final int toState =
224                                                     intent.getIntExtra(
225                                                             BluetoothLeAudio.EXTRA_STATE, -1);
226                                             if (toState == BluetoothLeAudio.STATE_CONNECTED
227                                                     || toState
228                                                             == BluetoothLeAudio.STATE_DISCONNECTED)
229                                                 svc_data.isConnectedMutable.postValue(
230                                                         toState
231                                                                 == BluetoothLeAudio
232                                                                         .STATE_CONNECTED);
233 
234                                             group_id = bluetoothLeAudio.getGroupId(device);
235                                             svc_data.nodeStatusMutable.postValue(
236                                                     new Pair<>(group_id, GROUP_NODE_ADDED));
237                                             svc_data.groupStatusMutable.postValue(
238                                                     new Pair<>(group_id, new Pair<>(-1, -1)));
239                                             break;
240                                         }
241                                 }
242                             }
243                         }
244                     }
245                 }
246             };
247 
248     private final BroadcastReceiver hapClientIntentReceiver =
249             new BroadcastReceiver() {
250                 @Override
251                 public void onReceive(Context context, Intent intent) {
252                     String action = intent.getAction();
253                     final BluetoothDevice device =
254                             intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
255 
256                     if (allLeAudioDevicesMutable.getValue() != null) {
257                         if (device != null) {
258                             Optional<LeAudioDeviceStateWrapper> valid_device_opt =
259                                     allLeAudioDevicesMutable.getValue().stream()
260                                             .filter(
261                                                     state ->
262                                                             state.device
263                                                                     .getAddress()
264                                                                     .equals(device.getAddress()))
265                                             .findAny();
266 
267                             if (valid_device_opt.isPresent()) {
268                                 LeAudioDeviceStateWrapper valid_device = valid_device_opt.get();
269                                 LeAudioDeviceStateWrapper.HapData svc_data = valid_device.hapData;
270 
271                                 switch (action) {
272                                     case BluetoothHapClient.ACTION_HAP_CONNECTION_STATE_CHANGED:
273                                         {
274                                             final int toState =
275                                                     intent.getIntExtra(
276                                                             BluetoothHapClient.EXTRA_STATE, -1);
277                                             svc_data.hapStateMutable.postValue(toState);
278                                             break;
279                                         }
280                                         // Hidden API
281                                     case "android.bluetooth.action.HAP_DEVICE_AVAILABLE":
282                                         {
283                                             final int features =
284                                                     intent.getIntExtra(
285                                                             "android.bluetooth.extra.HAP_FEATURES",
286                                                             -1);
287                                             svc_data.hapFeaturesMutable.postValue(features);
288                                             break;
289                                         }
290                                     default:
291                                         // Do nothing
292                                         break;
293                                 }
294                             }
295                         }
296                     }
297                 }
298             };
299 
300     private final BroadcastReceiver volumeControlIntentReceiver =
301             new BroadcastReceiver() {
302                 @Override
303                 public void onReceive(Context context, Intent intent) {
304                     String action = intent.getAction();
305                     final BluetoothDevice device =
306                             intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
307 
308                     if (allLeAudioDevicesMutable.getValue() != null) {
309                         if (device != null) {
310                             Optional<LeAudioDeviceStateWrapper> valid_device_opt =
311                                     allLeAudioDevicesMutable.getValue().stream()
312                                             .filter(
313                                                     state ->
314                                                             state.device
315                                                                     .getAddress()
316                                                                     .equals(device.getAddress()))
317                                             .findAny();
318 
319                             if (valid_device_opt.isPresent()) {
320                                 LeAudioDeviceStateWrapper valid_device = valid_device_opt.get();
321                                 LeAudioDeviceStateWrapper.VolumeControlData svc_data =
322                                         valid_device.volumeControlData;
323 
324                                 switch (action) {
325                                     case BluetoothVolumeControl.ACTION_CONNECTION_STATE_CHANGED:
326                                         final int toState =
327                                                 intent.getIntExtra(
328                                                         BluetoothVolumeControl.EXTRA_STATE, -1);
329                                         if (toState == BluetoothVolumeControl.STATE_CONNECTED
330                                                 || toState
331                                                         == BluetoothVolumeControl
332                                                                 .STATE_DISCONNECTED)
333                                             svc_data.isConnectedMutable.postValue(
334                                                     toState
335                                                             == BluetoothVolumeControl
336                                                                     .STATE_CONNECTED);
337                                         break;
338                                 }
339                             }
340                         }
341                     }
342                 }
343             };
344     private final MutableLiveData<BluetoothLeBroadcastMetadata> mBroadcastUpdateMutableLive;
345     private final MutableLiveData<Pair<Integer /* reason */, Integer /* broadcastId */>>
346             mBroadcastPlaybackStartedMutableLive;
347     private final MutableLiveData<Pair<Integer /* reason */, Integer /* broadcastId */>>
348             mBroadcastPlaybackStoppedMutableLive;
349     private final MutableLiveData<Integer /* broadcastId */> mBroadcastAddedMutableLive;
350     private final MutableLiveData<Pair<Integer /* reason */, Integer /* broadcastId */>>
351             mBroadcastRemovedMutableLive;
352     private final MutableLiveData<String> mBroadcastStatusMutableLive;
353     private final BluetoothLeBroadcast.Callback mBroadcasterCallback =
354             new BluetoothLeBroadcast.Callback() {
355                 @Override
356                 public void onBroadcastStarted(int reason, int broadcastId) {
357                     if ((reason != BluetoothStatusCodes.REASON_LOCAL_APP_REQUEST)
358                             && (reason != BluetoothStatusCodes.REASON_LOCAL_STACK_REQUEST)) {
359                         mBroadcastStatusMutableLive.postValue(
360                                 "Unable to create broadcast: "
361                                         + broadcastId
362                                         + ", reason: "
363                                         + reason);
364                     }
365 
366                     mBroadcastAddedMutableLive.postValue(broadcastId);
367                     if (mLocalBroadcastEventListener != null) {
368                         mLocalBroadcastEventListener.onBroadcastStarted(broadcastId);
369                     }
370                 }
371 
372                 @Override
373                 public void onBroadcastStartFailed(int reason) {
374                     mBroadcastStatusMutableLive.postValue(
375                             "Unable to START broadcast due to reason: " + reason);
376                 }
377 
378                 @Override
379                 public void onBroadcastStopped(int reason, int broadcastId) {
380                     mBroadcastRemovedMutableLive.postValue(new Pair<>(reason, broadcastId));
381                     if (mLocalBroadcastEventListener != null) {
382                         mLocalBroadcastEventListener.onBroadcastStopped(broadcastId);
383                     }
384                 }
385 
386                 @Override
387                 public void onBroadcastStopFailed(int reason) {
388                     mBroadcastStatusMutableLive.postValue(
389                             "Unable to STOP broadcast due to reason: " + reason);
390                 }
391 
392                 @Override
393                 public void onPlaybackStarted(int reason, int broadcastId) {
394                     mBroadcastPlaybackStartedMutableLive.postValue(new Pair<>(reason, broadcastId));
395                 }
396 
397                 @Override
398                 public void onPlaybackStopped(int reason, int broadcastId) {
399                     mBroadcastPlaybackStoppedMutableLive.postValue(new Pair<>(reason, broadcastId));
400                 }
401 
402                 @Override
403                 public void onBroadcastUpdated(int reason, int broadcastId) {
404                     mBroadcastStatusMutableLive.postValue(
405                             "Broadcast "
406                                     + broadcastId
407                                     + "has been updated due to reason: "
408                                     + reason);
409                     if (mLocalBroadcastEventListener != null) {
410                         mLocalBroadcastEventListener.onBroadcastUpdated(broadcastId);
411                     }
412                 }
413 
414                 @Override
415                 public void onBroadcastUpdateFailed(int reason, int broadcastId) {
416                     mBroadcastStatusMutableLive.postValue(
417                             "Unable to UPDATE broadcast "
418                                     + broadcastId
419                                     + " due to reason: "
420                                     + reason);
421                 }
422 
423                 @Override
424                 public void onBroadcastMetadataChanged(
425                         int broadcastId, BluetoothLeBroadcastMetadata metadata) {
426                     mBroadcastUpdateMutableLive.postValue(metadata);
427                     if (mLocalBroadcastEventListener != null) {
428                         mLocalBroadcastEventListener.onBroadcastMetadataChanged(
429                                 broadcastId, metadata);
430                     }
431                 }
432             };
433 
434     // TODO: Add behaviors in empty methods if necessary.
435     private final BluetoothLeBroadcastAssistant.Callback mBroadcastAssistantCallback =
436             new BluetoothLeBroadcastAssistant.Callback() {
437                 @Override
438                 public void onSearchStarted(int reason) {}
439 
440                 @Override
441                 public void onSearchStartFailed(int reason) {}
442 
443                 @Override
444                 public void onSearchStopped(int reason) {}
445 
446                 @Override
447                 public void onSearchStopFailed(int reason) {}
448 
449                 @Override
450                 public void onSourceFound(BluetoothLeBroadcastMetadata source) {
451                     Log.d("BluetoothProxy", "onSourceFound");
452                     if (mBassEventListener != null) {
453                         mBassEventListener.onSourceFound(source);
454                     }
455                 }
456 
457                 @Override
458                 public void onSourceAdded(BluetoothDevice sink, int sourceId, int reason) {}
459 
460                 @Override
461                 public void onSourceAddFailed(
462                         BluetoothDevice sink, BluetoothLeBroadcastMetadata source, int reason) {}
463 
464                 @Override
465                 public void onSourceModified(BluetoothDevice sink, int sourceId, int reason) {}
466 
467                 @Override
468                 public void onSourceModifyFailed(BluetoothDevice sink, int sourceId, int reason) {}
469 
470                 @Override
471                 public void onSourceRemoved(BluetoothDevice sink, int sourceId, int reason) {}
472 
473                 @Override
474                 public void onSourceRemoveFailed(BluetoothDevice sink, int sourceId, int reason) {}
475 
476                 @Override
477                 public void onReceiveStateChanged(
478                         BluetoothDevice sink,
479                         int sourceId,
480                         BluetoothLeBroadcastReceiveState state) {
481                     Log.d("BluetoothProxy", "onReceiveStateChanged");
482                     if (allLeAudioDevicesMutable.getValue() != null) {
483                         Optional<LeAudioDeviceStateWrapper> valid_device_opt =
484                                 allLeAudioDevicesMutable.getValue().stream()
485                                         .filter(
486                                                 stateWrapper ->
487                                                         stateWrapper
488                                                                 .device
489                                                                 .getAddress()
490                                                                 .equals(sink.getAddress()))
491                                         .findAny();
492 
493                         if (!valid_device_opt.isPresent()) return;
494 
495                         LeAudioDeviceStateWrapper valid_device = valid_device_opt.get();
496                         LeAudioDeviceStateWrapper.BassData svc_data = valid_device.bassData;
497 
498                         /**
499                          * From "Introducing-Bluetooth-LE-Audio-book" 8.6.3.1:
500                          *
501                          * <p>The Source_ID is an Acceptor generated number which is used to
502                          * identify a specific set of broadcast device and BIG information. It is
503                          * local to an Acceptor and used as a reference for a Broadcast Assistant.
504                          * In the case of a Coordinated Set of Acceptors, such as a left and right
505                          * earbud, the Source_IDs are not related and may be different, even if both
506                          * are receiving the same BIS, as each Acceptor independently creates their
507                          * own Source ID values
508                          */
509 
510                         /** Broadcast receiver's endpoint identifier. */
511                         synchronized (this) {
512                             HashMap<Integer, BluetoothLeBroadcastReceiveState> states =
513                                     svc_data.receiverStatesMutable.getValue();
514                             if (states == null) states = new HashMap<>();
515                             states.put(state.getSourceId(), state);
516 
517                             // Use SetValue instead of PostValue() since we want to make it
518                             // synchronous due to getValue() we do here as well
519                             // Otherwise we could miss the update and store only the last
520                             // receiver ID
521                             //                    svc_data.receiverStatesMutable.setValue(states);
522                             svc_data.receiverStatesMutable.postValue(states);
523                         }
524                     }
525                 }
526             };
527 
528     private final BroadcastReceiver bassIntentReceiver =
529             new BroadcastReceiver() {
530                 @Override
531                 public void onReceive(Context context, Intent intent) {
532                     String action = intent.getAction();
533                     if (action.equals(
534                             BluetoothLeBroadcastAssistant.ACTION_CONNECTION_STATE_CHANGED)) {
535                         final BluetoothDevice device =
536                                 intent.getParcelableExtra(
537                                         BluetoothDevice.EXTRA_DEVICE, BluetoothDevice.class);
538 
539                         if (allLeAudioDevicesMutable.getValue() != null) {
540                             if (device != null) {
541                                 Optional<LeAudioDeviceStateWrapper> valid_device_opt =
542                                         allLeAudioDevicesMutable.getValue().stream()
543                                                 .filter(
544                                                         state ->
545                                                                 state.device
546                                                                         .getAddress()
547                                                                         .equals(
548                                                                                 device
549                                                                                         .getAddress()))
550                                                 .findAny();
551 
552                                 if (valid_device_opt.isPresent()) {
553                                     LeAudioDeviceStateWrapper valid_device = valid_device_opt.get();
554                                     LeAudioDeviceStateWrapper.BassData svc_data =
555                                             valid_device.bassData;
556 
557                                     final int toState =
558                                             intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1);
559                                     if (toState == STATE_CONNECTED || toState == STATE_DISCONNECTED)
560                                         svc_data.isConnectedMutable.postValue(
561                                                 toState == STATE_CONNECTED);
562                                 }
563                             }
564                         }
565                     }
566                     // TODO: Remove this if unnecessary.
567                     //          case
568                     // BluetoothBroadcastAudioScan.ACTION_BASS_BROADCAST_ANNONCEMENT_AVAILABLE:
569                     //              // FIXME: Never happen since there is no valid device with this
570                     // intent
571                     //              break;
572                 }
573             };
574 
BluetoothProxy(Application application)575     private BluetoothProxy(Application application) {
576         this.application = application;
577         bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
578 
579         enabledBluetoothMutable = new MutableLiveData<>();
580         allLeAudioDevicesMutable = new MutableLiveData<>();
581 
582         mBroadcastUpdateMutableLive = new MutableLiveData<>();
583         mBroadcastStatusMutableLive = new MutableLiveData<>();
584 
585         mBroadcastPlaybackStartedMutableLive = new MutableLiveData<>();
586         mBroadcastPlaybackStoppedMutableLive = new MutableLiveData<>();
587         mBroadcastAddedMutableLive = new MutableLiveData();
588         mBroadcastRemovedMutableLive = new MutableLiveData<>();
589 
590         MutableLiveData<String> mBroadcastStatusMutableLive;
591 
592         mExecutor = Executors.newSingleThreadExecutor();
593 
594         adapterIntentFilter = new IntentFilter();
595         adapterIntentFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
596         adapterIntentFilter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
597         application.registerReceiver(
598                 adapterIntentReceiver, adapterIntentFilter, Context.RECEIVER_EXPORTED);
599 
600         bassIntentFilter = new IntentFilter();
601         bassIntentFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
602         bassIntentFilter.addAction(BluetoothLeBroadcastAssistant.ACTION_CONNECTION_STATE_CHANGED);
603         application.registerReceiver(
604                 bassIntentReceiver, bassIntentFilter, Context.RECEIVER_EXPORTED);
605     }
606 
607     // Lazy constructing Singleton acquire method
getBluetoothProxy(Application application)608     public static BluetoothProxy getBluetoothProxy(Application application) {
609         if (INSTANCE == null) {
610             INSTANCE = new BluetoothProxy(application);
611         }
612         return (INSTANCE);
613     }
614 
initProfiles()615     public void initProfiles() {
616         if (profileListener != null) return;
617 
618         hapCallback =
619                 new BluetoothHapClient.Callback() {
620                     @Override
621                     public void onPresetSelected(
622                             BluetoothDevice device, int presetIndex, int statusCode) {
623                         Optional<LeAudioDeviceStateWrapper> valid_device_opt =
624                                 allLeAudioDevicesMutable.getValue().stream()
625                                         .filter(
626                                                 state ->
627                                                         state.device
628                                                                 .getAddress()
629                                                                 .equals(device.getAddress()))
630                                         .findAny();
631 
632                         if (!valid_device_opt.isPresent()) return;
633 
634                         LeAudioDeviceStateWrapper valid_device = valid_device_opt.get();
635                         LeAudioDeviceStateWrapper.HapData svc_data = valid_device.hapData;
636 
637                         svc_data.hapActivePresetIndexMutable.postValue(presetIndex);
638 
639                         svc_data.hapStatusMutable.postValue(
640                                 "Preset changed to " + presetIndex + ", reason: " + statusCode);
641                     }
642 
643                     @Override
644                     public void onPresetSelectionFailed(BluetoothDevice device, int statusCode) {
645                         Optional<LeAudioDeviceStateWrapper> valid_device_opt =
646                                 allLeAudioDevicesMutable.getValue().stream()
647                                         .filter(
648                                                 state ->
649                                                         state.device
650                                                                 .getAddress()
651                                                                 .equals(device.getAddress()))
652                                         .findAny();
653 
654                         if (!valid_device_opt.isPresent()) return;
655 
656                         LeAudioDeviceStateWrapper valid_device = valid_device_opt.get();
657                         LeAudioDeviceStateWrapper.HapData svc_data = valid_device.hapData;
658 
659                         svc_data.hapStatusMutable.postValue(
660                                 "Select preset failed with status " + statusCode);
661                     }
662 
663                     @Override
664                     public void onPresetSelectionForGroupFailed(int hapGroupId, int statusCode) {
665                         List<LeAudioDeviceStateWrapper> valid_devices = null;
666                         if (hapGroupId != BluetoothCsipSetCoordinator.GROUP_ID_INVALID)
667                             valid_devices =
668                                     allLeAudioDevicesMutable.getValue().stream()
669                                             .filter(
670                                                     state ->
671                                                             state.leAudioData != null
672                                                                     && state.leAudioData
673                                                                                     .nodeStatusMutable
674                                                                                     .getValue()
675                                                                             != null
676                                                                     && state.leAudioData
677                                                                             .nodeStatusMutable
678                                                                             .getValue()
679                                                                             .first
680                                                                             .equals(hapGroupId))
681                                             .collect(Collectors.toList());
682 
683                         if (valid_devices != null) {
684                             for (LeAudioDeviceStateWrapper device : valid_devices) {
685                                 device.hapData.hapStatusMutable.postValue(
686                                         "Select preset for group "
687                                                 + hapGroupId
688                                                 + " failed with status "
689                                                 + statusCode);
690                             }
691                         }
692                     }
693 
694                     @Override
695                     public void onPresetInfoChanged(
696                             BluetoothDevice device,
697                             List<BluetoothHapPresetInfo> presetInfoList,
698                             int statusCode) {
699                         Optional<LeAudioDeviceStateWrapper> valid_device_opt =
700                                 allLeAudioDevicesMutable.getValue().stream()
701                                         .filter(
702                                                 state ->
703                                                         state.device
704                                                                 .getAddress()
705                                                                 .equals(device.getAddress()))
706                                         .findAny();
707 
708                         if (!valid_device_opt.isPresent()) return;
709 
710                         LeAudioDeviceStateWrapper valid_device = valid_device_opt.get();
711                         LeAudioDeviceStateWrapper.HapData svc_data = valid_device.hapData;
712 
713                         svc_data.hapStatusMutable.postValue(
714                                 "Preset list changed due to status " + statusCode);
715                         svc_data.hapPresetsMutable.postValue(presetInfoList);
716                     }
717 
718                     @Override
719                     public void onSetPresetNameFailed(BluetoothDevice device, int status) {
720                         Optional<LeAudioDeviceStateWrapper> valid_device_opt =
721                                 allLeAudioDevicesMutable.getValue().stream()
722                                         .filter(
723                                                 state ->
724                                                         state.device
725                                                                 .getAddress()
726                                                                 .equals(device.getAddress()))
727                                         .findAny();
728 
729                         if (!valid_device_opt.isPresent()) return;
730 
731                         LeAudioDeviceStateWrapper valid_device = valid_device_opt.get();
732                         LeAudioDeviceStateWrapper.HapData svc_data = valid_device.hapData;
733 
734                         svc_data.hapStatusMutable.postValue("Name set error: " + status);
735                     }
736 
737                     @Override
738                     public void onSetPresetNameForGroupFailed(int hapGroupId, int status) {
739                         List<LeAudioDeviceStateWrapper> valid_devices = null;
740                         if (hapGroupId != BluetoothCsipSetCoordinator.GROUP_ID_INVALID)
741                             valid_devices =
742                                     allLeAudioDevicesMutable.getValue().stream()
743                                             .filter(
744                                                     state ->
745                                                             state.leAudioData != null
746                                                                     && state.leAudioData
747                                                                                     .nodeStatusMutable
748                                                                                     .getValue()
749                                                                             != null
750                                                                     && state.leAudioData
751                                                                             .nodeStatusMutable
752                                                                             .getValue()
753                                                                             .first
754                                                                             .equals(hapGroupId))
755                                             .collect(Collectors.toList());
756 
757                         if (valid_devices != null) {
758                             for (LeAudioDeviceStateWrapper device : valid_devices) {
759                                 device.hapData.hapStatusMutable.postValue(
760                                         "Group Name set error: " + status);
761                             }
762                         }
763                     }
764                 };
765 
766         profileListener =
767                 new BluetoothProfile.ServiceListener() {
768                     @Override
769                     public void onServiceConnected(int i, BluetoothProfile bluetoothProfile) {
770                         Log.d(
771                                 "BluetoothProxy",
772                                 "onServiceConnected(): i = "
773                                         + i
774                                         + " bluetoothProfile = "
775                                         + bluetoothProfile);
776                         switch (i) {
777                             case BluetoothProfile.CSIP_SET_COORDINATOR:
778                                 bluetoothCsis = (BluetoothCsipSetCoordinator) bluetoothProfile;
779                                 break;
780                             case BluetoothProfile.LE_AUDIO:
781                                 bluetoothLeAudio = (BluetoothLeAudio) bluetoothProfile;
782                                 if (!mLeAudioCallbackRegistered) {
783                                     try {
784                                         bluetoothLeAudio.registerCallback(
785                                                 mExecutor, mLeAudioCallbacks);
786                                         mLeAudioCallbackRegistered = true;
787                                     } catch (Exception e) {
788                                         Log.e(
789                                                 "Unicast:",
790                                                 " Probably not supported: Exception on registering"
791                                                         + " callbacks: "
792                                                         + e);
793                                     }
794                                 }
795                                 break;
796                             case BluetoothProfile.VOLUME_CONTROL:
797                                 bluetoothVolumeControl = (BluetoothVolumeControl) bluetoothProfile;
798                                 break;
799                             case BluetoothProfile.HAP_CLIENT:
800                                 bluetoothHapClient = (BluetoothHapClient) bluetoothProfile;
801                                 try {
802                                     bluetoothHapClient.registerCallback(mExecutor, hapCallback);
803                                 } catch (IllegalArgumentException e) {
804                                     Log.e("HAP", "Application callback already registered.");
805                                 }
806                                 break;
807                             case BluetoothProfile.LE_AUDIO_BROADCAST:
808                                 mBluetoothLeBroadcast = (BluetoothLeBroadcast) bluetoothProfile;
809                                 try {
810                                     mBluetoothLeBroadcast.registerCallback(
811                                             mExecutor, mBroadcasterCallback);
812                                 } catch (IllegalArgumentException e) {
813                                     Log.e("Broadcast", "Application callback already registered.");
814                                 }
815                                 break;
816                             case BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT:
817                                 Log.d(
818                                         "BluetoothProxy",
819                                         "LE_AUDIO_BROADCAST_ASSISTANT Service connected");
820                                 mBluetoothLeBroadcastAssistant =
821                                         (BluetoothLeBroadcastAssistant) bluetoothProfile;
822                                 try {
823                                     mBluetoothLeBroadcastAssistant.registerCallback(
824                                             mExecutor, mBroadcastAssistantCallback);
825                                 } catch (IllegalArgumentException e) {
826                                     Log.e("BASS", "Application callback already registered.");
827                                 }
828                                 break;
829                         }
830                         queryLeAudioDevices();
831                     }
832 
833                     @Override
834                     public void onServiceDisconnected(int i) {}
835                 };
836 
837         initCsisProxy();
838         initLeAudioProxy();
839         initVolumeControlProxy();
840         initHapProxy();
841         initLeAudioBroadcastProxy();
842         initBassProxy();
843     }
844 
cleanupProfiles()845     public void cleanupProfiles() {
846         if (profileListener == null) return;
847 
848         cleanupCsisProxy();
849         cleanupLeAudioProxy();
850         cleanupVolumeControlProxy();
851         cleanupHapProxy();
852         cleanupLeAudioBroadcastProxy();
853         cleanupBassProxy();
854 
855         profileListener = null;
856     }
857 
initCsisProxy()858     private void initCsisProxy() {
859         if (!isCoordinatedSetProfileSupported()) return;
860         if (bluetoothCsis == null) {
861             bluetoothAdapter.getProfileProxy(
862                     this.application, profileListener, BluetoothProfile.CSIP_SET_COORDINATOR);
863         }
864     }
865 
cleanupCsisProxy()866     private void cleanupCsisProxy() {
867         if (!isCoordinatedSetProfileSupported()) return;
868         if (bluetoothCsis != null) {
869             bluetoothAdapter.closeProfileProxy(BluetoothProfile.LE_AUDIO, bluetoothCsis);
870         }
871     }
872 
initLeAudioProxy()873     private void initLeAudioProxy() {
874         if (!isLeAudioUnicastSupported()) return;
875         if (bluetoothLeAudio == null) {
876             bluetoothAdapter.getProfileProxy(
877                     this.application, profileListener, BluetoothProfile.LE_AUDIO);
878         }
879 
880         intentFilter = new IntentFilter();
881         intentFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
882         intentFilter.addAction(BluetoothLeAudio.ACTION_LE_AUDIO_CONNECTION_STATE_CHANGED);
883         application.registerReceiver(
884                 leAudioIntentReceiver, intentFilter, Context.RECEIVER_EXPORTED);
885     }
886 
cleanupLeAudioProxy()887     private void cleanupLeAudioProxy() {
888         if (!isLeAudioUnicastSupported()) return;
889         if (bluetoothLeAudio != null) {
890             bluetoothAdapter.closeProfileProxy(BluetoothProfile.LE_AUDIO, bluetoothLeAudio);
891             application.unregisterReceiver(leAudioIntentReceiver);
892         }
893     }
894 
initVolumeControlProxy()895     private void initVolumeControlProxy() {
896         if (!isVolumeControlClientSupported()) return;
897         bluetoothAdapter.getProfileProxy(
898                 this.application, profileListener, BluetoothProfile.VOLUME_CONTROL);
899 
900         intentFilter = new IntentFilter();
901         intentFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
902         intentFilter.addAction(BluetoothVolumeControl.ACTION_CONNECTION_STATE_CHANGED);
903         application.registerReceiver(
904                 volumeControlIntentReceiver, intentFilter, Context.RECEIVER_EXPORTED);
905     }
906 
cleanupVolumeControlProxy()907     private void cleanupVolumeControlProxy() {
908         if (!isVolumeControlClientSupported()) return;
909         if (bluetoothVolumeControl != null) {
910             bluetoothAdapter.closeProfileProxy(
911                     BluetoothProfile.VOLUME_CONTROL, bluetoothVolumeControl);
912             application.unregisterReceiver(volumeControlIntentReceiver);
913         }
914     }
915 
initHapProxy()916     private void initHapProxy() {
917         if (!isLeAudioHearingAccessClientSupported()) return;
918         bluetoothAdapter.getProfileProxy(
919                 this.application, profileListener, BluetoothProfile.HAP_CLIENT);
920 
921         intentFilter = new IntentFilter();
922         intentFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
923         intentFilter.addAction(BluetoothHapClient.ACTION_HAP_CONNECTION_STATE_CHANGED);
924         intentFilter.addAction("android.bluetooth.action.HAP_DEVICE_AVAILABLE");
925         application.registerReceiver(
926                 hapClientIntentReceiver, intentFilter, Context.RECEIVER_EXPORTED);
927     }
928 
cleanupHapProxy()929     private void cleanupHapProxy() {
930         if (!isLeAudioHearingAccessClientSupported()) return;
931         if (bluetoothHapClient != null) {
932             bluetoothHapClient.unregisterCallback(hapCallback);
933             bluetoothAdapter.closeProfileProxy(BluetoothProfile.HAP_CLIENT, bluetoothHapClient);
934             application.unregisterReceiver(hapClientIntentReceiver);
935         }
936     }
937 
initBassProxy()938     private void initBassProxy() {
939         if (!isLeAudioBroadcastScanAssistanSupported()) return;
940         bluetoothAdapter.getProfileProxy(
941                 this.application, profileListener, BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT);
942     }
943 
cleanupBassProxy()944     private void cleanupBassProxy() {
945         if (!isLeAudioBroadcastScanAssistanSupported()) return;
946         if (mBluetoothLeBroadcastAssistant != null) {
947             mBluetoothLeBroadcastAssistant.unregisterCallback(mBroadcastAssistantCallback);
948             bluetoothAdapter.closeProfileProxy(
949                     BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT, mBluetoothLeBroadcastAssistant);
950         }
951     }
952 
checkForEnabledBluetooth()953     private Boolean checkForEnabledBluetooth() {
954         Boolean current_state = bluetoothAdapter.isEnabled();
955 
956         // Force the update since event may not come if bt was already enabled
957         if (!Objects.equals(enabledBluetoothMutable.getValue(), current_state))
958             enabledBluetoothMutable.setValue(current_state);
959 
960         return current_state;
961     }
962 
queryLeAudioDevices()963     public void queryLeAudioDevices() {
964         if (checkForEnabledBluetooth()) {
965             // Consider those with the ASC service as valid devices
966             List<LeAudioDeviceStateWrapper> validDevices = new ArrayList<>();
967             for (BluetoothDevice dev : bluetoothAdapter.getBondedDevices()) {
968                 LeAudioDeviceStateWrapper state_wrapper = new LeAudioDeviceStateWrapper(dev);
969                 Boolean valid_device = false;
970 
971                 if (Arrays.asList(dev.getUuids() != null ? dev.getUuids() : new ParcelUuid[0])
972                         .contains(
973                                 ParcelUuid.fromString(
974                                         application.getString(R.string.svc_uuid_le_audio)))) {
975                     if (state_wrapper.leAudioData == null)
976                         state_wrapper.leAudioData = new LeAudioDeviceStateWrapper.LeAudioData();
977                     valid_device = true;
978 
979                     if (bluetoothLeAudio != null) {
980                         state_wrapper.leAudioData.isConnectedMutable.postValue(
981                                 bluetoothLeAudio.getConnectionState(dev)
982                                         == BluetoothLeAudio.STATE_CONNECTED);
983                         int group_id = bluetoothLeAudio.getGroupId(dev);
984                         state_wrapper.leAudioData.nodeStatusMutable.setValue(
985                                 new Pair<>(group_id, GROUP_NODE_ADDED));
986                         state_wrapper.leAudioData.groupStatusMutable.setValue(
987                                 new Pair<>(group_id, new Pair<>(-1, -1)));
988                     }
989                 }
990 
991                 if (Arrays.asList(dev.getUuids() != null ? dev.getUuids() : new ParcelUuid[0])
992                         .contains(
993                                 ParcelUuid.fromString(
994                                         application.getString(R.string.svc_uuid_volume_control)))) {
995                     if (state_wrapper.volumeControlData == null)
996                         state_wrapper.volumeControlData =
997                                 new LeAudioDeviceStateWrapper.VolumeControlData();
998                     valid_device = true;
999 
1000                     if (bluetoothVolumeControl != null) {
1001                         state_wrapper.volumeControlData.isConnectedMutable.postValue(
1002                                 bluetoothVolumeControl.getConnectionState(dev)
1003                                         == BluetoothVolumeControl.STATE_CONNECTED);
1004                         // FIXME: We don't have the api to get the volume and mute states? :(
1005                     }
1006                 }
1007 
1008                 if (Arrays.asList(dev.getUuids() != null ? dev.getUuids() : new ParcelUuid[0])
1009                         .contains(
1010                                 ParcelUuid.fromString(
1011                                         application.getString(R.string.svc_uuid_has)))) {
1012                     if (state_wrapper.hapData == null)
1013                         state_wrapper.hapData = new LeAudioDeviceStateWrapper.HapData();
1014                     valid_device = true;
1015 
1016                     if (bluetoothHapClient != null) {
1017                         state_wrapper.hapData.hapStateMutable.postValue(
1018                                 bluetoothHapClient.getConnectionState(dev));
1019                         boolean is_connected =
1020                                 bluetoothHapClient.getConnectionState(dev)
1021                                         == BluetoothHapClient.STATE_CONNECTED;
1022                         if (is_connected) {
1023                             // Use hidden API
1024                             try {
1025                                 Method getFeaturesMethod =
1026                                         BluetoothHapClient.class.getDeclaredMethod(
1027                                                 "getFeatures", BluetoothDevice.class);
1028                                 getFeaturesMethod.setAccessible(true);
1029                                 state_wrapper.hapData.hapFeaturesMutable.postValue(
1030                                         (Integer)
1031                                                 getFeaturesMethod.invoke(bluetoothHapClient, dev));
1032                             } catch (NoSuchMethodException
1033                                     | IllegalAccessException
1034                                     | InvocationTargetException e) {
1035                                 state_wrapper.hapData.hapStatusMutable.postValue(
1036                                         "Hidden API for getFeatures not accessible.");
1037                             }
1038 
1039                             state_wrapper.hapData.hapPresetsMutable.postValue(
1040                                     bluetoothHapClient.getAllPresetInfo(dev));
1041                             try {
1042                                 Method getActivePresetIndexMethod =
1043                                         BluetoothHapClient.class.getDeclaredMethod(
1044                                                 "getActivePresetIndex", BluetoothDevice.class);
1045                                 getActivePresetIndexMethod.setAccessible(true);
1046                                 state_wrapper.hapData.hapActivePresetIndexMutable.postValue(
1047                                         (Integer)
1048                                                 getActivePresetIndexMethod.invoke(
1049                                                         bluetoothHapClient, dev));
1050                             } catch (NoSuchMethodException
1051                                     | IllegalAccessException
1052                                     | InvocationTargetException e) {
1053                                 state_wrapper.hapData.hapStatusMutable.postValue(
1054                                         "Hidden API for getFeatures not accessible.");
1055                             }
1056                         }
1057                     }
1058                 }
1059 
1060                 if (Arrays.asList(dev.getUuids() != null ? dev.getUuids() : new ParcelUuid[0])
1061                         .contains(
1062                                 ParcelUuid.fromString(
1063                                         application.getString(
1064                                                 R.string.svc_uuid_broadcast_audio)))) {
1065                     if (state_wrapper.bassData == null)
1066                         state_wrapper.bassData = new LeAudioDeviceStateWrapper.BassData();
1067                     valid_device = true;
1068 
1069                     if (mBluetoothLeBroadcastAssistant != null) {
1070                         boolean is_connected =
1071                                 mBluetoothLeBroadcastAssistant.getConnectionState(dev)
1072                                         == STATE_CONNECTED;
1073                         state_wrapper.bassData.isConnectedMutable.setValue(is_connected);
1074                     }
1075                 }
1076 
1077                 if (valid_device) validDevices.add(state_wrapper);
1078             }
1079 
1080             // Async update
1081             allLeAudioDevicesMutable.postValue(validDevices);
1082         }
1083     }
1084 
connectLeAudio(BluetoothDevice device, boolean connect)1085     public void connectLeAudio(BluetoothDevice device, boolean connect) {
1086         if (bluetoothLeAudio != null) {
1087             if (connect) {
1088                 try {
1089                     Method connectMethod =
1090                             BluetoothLeAudio.class.getDeclaredMethod(
1091                                     "connect", BluetoothDevice.class);
1092                     connectMethod.setAccessible(true);
1093                     connectMethod.invoke(bluetoothLeAudio, device);
1094                 } catch (NoSuchMethodException
1095                         | IllegalAccessException
1096                         | InvocationTargetException e) {
1097                     // Do nothing
1098                 }
1099             } else {
1100                 try {
1101                     Method disconnectMethod =
1102                             BluetoothLeAudio.class.getDeclaredMethod(
1103                                     "disconnect", BluetoothDevice.class);
1104                     disconnectMethod.setAccessible(true);
1105                     disconnectMethod.invoke(bluetoothLeAudio, device);
1106                 } catch (NoSuchMethodException
1107                         | IllegalAccessException
1108                         | InvocationTargetException e) {
1109                     // Do nothing
1110                 }
1111             }
1112         }
1113     }
1114 
streamAction(Integer group_id, int action, Integer content_type)1115     public void streamAction(Integer group_id, int action, Integer content_type) {
1116         if (bluetoothLeAudio != null) {
1117             switch (action) {
1118                 case 0:
1119                     // No longer available, not needed
1120                     // bluetoothLeAudio.groupStream(group_id, content_type);
1121                     break;
1122                 case 1:
1123                     // No longer available, not needed
1124                     // bluetoothLeAudio.groupSuspend(group_id);
1125                     break;
1126                 case 2:
1127                     // No longer available, not needed
1128                     // bluetoothLeAudio.groupStop(group_id);
1129                     break;
1130                 default:
1131                     break;
1132             }
1133         }
1134     }
1135 
groupSet(BluetoothDevice device, Integer group_id)1136     public void groupSet(BluetoothDevice device, Integer group_id) {
1137         if (bluetoothLeAudio == null) return;
1138 
1139         try {
1140             Method groupAddNodeMethod =
1141                     BluetoothLeAudio.class.getDeclaredMethod(
1142                             "groupAddNode", int.class, BluetoothDevice.class);
1143             groupAddNodeMethod.setAccessible(true);
1144             groupAddNodeMethod.invoke(bluetoothLeAudio, group_id, device);
1145         } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
1146             // Do nothing
1147         }
1148     }
1149 
groupUnset(BluetoothDevice device, Integer group_id)1150     public void groupUnset(BluetoothDevice device, Integer group_id) {
1151         if (bluetoothLeAudio == null) return;
1152 
1153         try {
1154             Method groupRemoveNodeMethod =
1155                     BluetoothLeAudio.class.getDeclaredMethod(
1156                             "groupRemoveNode", int.class, BluetoothDevice.class);
1157             groupRemoveNodeMethod.setAccessible(true);
1158             groupRemoveNodeMethod.invoke(bluetoothLeAudio, group_id, device);
1159         } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
1160             // Do nothing
1161         }
1162     }
1163 
groupSetLock(Integer group_id, boolean lock)1164     public void groupSetLock(Integer group_id, boolean lock) {
1165         if (bluetoothCsis == null) return;
1166 
1167         Log.d("Lock", "lock: " + lock);
1168         if (lock) {
1169             if (mGroupLocks.containsKey(group_id)) {
1170                 Log.e(
1171                         "Lock",
1172                         "group" + group_id + " is already in locking process or locked: " + lock);
1173                 return;
1174             }
1175 
1176             UUID uuid =
1177                     bluetoothCsis.lockGroup(
1178                             group_id,
1179                             mExecutor,
1180                             (int group, int op_status, boolean is_locked) -> {
1181                                 Log.d("LockCb", "lock: " + is_locked + " status: " + op_status);
1182                                 if (((op_status == BluetoothStatusCodes.SUCCESS)
1183                                                 || (op_status
1184                                                         == BluetoothStatusCodes
1185                                                                 .ERROR_CSIP_LOCKED_GROUP_MEMBER_LOST))
1186                                         && (group != BluetoothLeAudio.GROUP_ID_INVALID)) {
1187                                     allLeAudioDevicesMutable
1188                                             .getValue()
1189                                             .forEach(
1190                                                     (dev_wrapper) -> {
1191                                                         if (dev_wrapper.leAudioData
1192                                                                                 .nodeStatusMutable
1193                                                                                 .getValue()
1194                                                                         != null
1195                                                                 && dev_wrapper
1196                                                                         .leAudioData
1197                                                                         .nodeStatusMutable
1198                                                                         .getValue()
1199                                                                         .first
1200                                                                         .equals(group_id)) {
1201                                                             dev_wrapper.leAudioData
1202                                                                     .groupLockStateMutable
1203                                                                     .postValue(
1204                                                                             new Pair<
1205                                                                                     Integer,
1206                                                                                     Boolean>(
1207                                                                                     group,
1208                                                                                     is_locked));
1209                                                         }
1210                                                     });
1211                                 } else {
1212                                     // TODO: Set error status so it could be notified/toasted to the
1213                                     // user
1214                                 }
1215 
1216                                 if (!is_locked) mGroupLocks.remove(group_id);
1217                             });
1218             // Store the lock key
1219             mGroupLocks.put(group_id, uuid);
1220         } else {
1221             if (!mGroupLocks.containsKey(group_id)) return;
1222 
1223             // Use the stored lock key
1224             bluetoothCsis.unlockGroup(mGroupLocks.get(group_id));
1225             mGroupLocks.remove(group_id);
1226         }
1227     }
1228 
connectBass(BluetoothDevice device, boolean connect)1229     public void connectBass(BluetoothDevice device, boolean connect) {
1230         if (mBluetoothLeBroadcastAssistant != null) {
1231             if (connect) {
1232                 mBluetoothLeBroadcastAssistant.setConnectionPolicy(
1233                         device, CONNECTION_POLICY_ALLOWED);
1234             } else {
1235                 mBluetoothLeBroadcastAssistant.setConnectionPolicy(
1236                         device, CONNECTION_POLICY_FORBIDDEN);
1237             }
1238         }
1239     }
1240 
scanForBroadcasts(@ullable BluetoothDevice scanDelegator, boolean scan)1241     public boolean scanForBroadcasts(@Nullable BluetoothDevice scanDelegator, boolean scan) {
1242         if (mBluetoothLeBroadcastAssistant != null) {
1243             // Note: startSearchingForSources() does not support scanning on behalf of
1244             // a specific device - it only searches for all BASS connected devices.
1245             // Therefore, we manage the list of the devices and start/stop the scanning.
1246             if (scan) {
1247                 if (scanDelegator != null) {
1248                     mBroadcastScanDelegatorDevices.add(scanDelegator);
1249                 }
1250                 try {
1251                     mBluetoothLeBroadcastAssistant.startSearchingForSources(new ArrayList<>());
1252                 } catch (IllegalArgumentException e) {
1253                     Log.e("BluetoothProxy", " Unexpected " + e);
1254                 }
1255                 if (mBassEventListener != null) {
1256                     mBassEventListener.onScanningStateChanged(true);
1257                 }
1258             } else {
1259                 if (scanDelegator != null) {
1260                     mBroadcastScanDelegatorDevices.remove(scanDelegator);
1261                 }
1262                 if (mBroadcastScanDelegatorDevices.isEmpty()) {
1263                     try {
1264                         mBluetoothLeBroadcastAssistant.stopSearchingForSources();
1265                         if (mBassEventListener != null) {
1266                             mBassEventListener.onScanningStateChanged(false);
1267                         }
1268                     } catch (IllegalArgumentException e) {
1269                         Log.e("BluetoothProxy", " Unexpected " + e);
1270                     }
1271                 }
1272             }
1273             return true;
1274         }
1275         return false;
1276     }
1277 
stopBroadcastObserving()1278     public boolean stopBroadcastObserving() {
1279         if (mBluetoothLeBroadcastAssistant != null) {
1280             mBroadcastScanDelegatorDevices.clear();
1281             try {
1282                 mBluetoothLeBroadcastAssistant.stopSearchingForSources();
1283             } catch (IllegalArgumentException e) {
1284                 Log.e("BluetoothProxy", " Unexpected " + e);
1285             }
1286 
1287             if (mBassEventListener != null) {
1288                 mBassEventListener.onScanningStateChanged(false);
1289             }
1290             return true;
1291         }
1292         return false;
1293     }
1294 
1295     // TODO: Uncomment this method if necessary
1296     //    public boolean getBroadcastReceiverState(BluetoothDevice device, int receiver_id) {
1297     //        if (mBluetoothLeBroadcastAssistant != null) {
1298     //            return mBluetoothLeBroadcastAssistant.getBroadcastReceiverState(device,
1299     // receiver_id);
1300     //        }
1301     //        return false;
1302     //    }
1303 
addBroadcastSource( BluetoothDevice sink, BluetoothLeBroadcastMetadata sourceMetadata)1304     public boolean addBroadcastSource(
1305             BluetoothDevice sink, BluetoothLeBroadcastMetadata sourceMetadata) {
1306         if (mBluetoothLeBroadcastAssistant != null) {
1307             mBluetoothLeBroadcastAssistant.addSource(sink, sourceMetadata, true /* isGroupOp */);
1308             return true;
1309         }
1310         return false;
1311     }
1312 
modifyBroadcastSource( BluetoothDevice sink, int sourceId, BluetoothLeBroadcastMetadata metadata)1313     public boolean modifyBroadcastSource(
1314             BluetoothDevice sink, int sourceId, BluetoothLeBroadcastMetadata metadata) {
1315         if (mBluetoothLeBroadcastAssistant != null) {
1316             mBluetoothLeBroadcastAssistant.modifySource(sink, sourceId, metadata);
1317             return true;
1318         }
1319         return false;
1320     }
1321 
removeBroadcastSource(BluetoothDevice sink, int sourceId)1322     public boolean removeBroadcastSource(BluetoothDevice sink, int sourceId) {
1323         if (mBluetoothLeBroadcastAssistant != null) {
1324             mBluetoothLeBroadcastAssistant.removeSource(sink, sourceId);
1325             return true;
1326         }
1327         return false;
1328     }
1329 
setVolume(BluetoothDevice device, int volume)1330     public void setVolume(BluetoothDevice device, int volume) {
1331         if (bluetoothLeAudio != null && !bluetoothLeAudio.getConnectedDevices().isEmpty()) {
1332             bluetoothLeAudio.setVolume(volume);
1333         } else if (bluetoothVolumeControl != null) {
1334             bluetoothVolumeControl.setVolumeOffset(device, volume);
1335         }
1336     }
1337 
getBluetoothEnabled()1338     public LiveData<Boolean> getBluetoothEnabled() {
1339         return enabledBluetoothMutable;
1340     }
1341 
getAllLeAudioDevices()1342     public LiveData<List<LeAudioDeviceStateWrapper>> getAllLeAudioDevices() {
1343         return allLeAudioDevicesMutable;
1344     }
1345 
connectHap(BluetoothDevice device, boolean connect)1346     public void connectHap(BluetoothDevice device, boolean connect) {
1347         if (bluetoothHapClient != null) {
1348             if (connect) {
1349                 bluetoothHapClient.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
1350             } else {
1351                 bluetoothHapClient.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
1352             }
1353         }
1354     }
1355 
connectGattBr( Context context, LeAudioDeviceStateWrapper device_wrapper, boolean connect)1356     public void connectGattBr(
1357             Context context, LeAudioDeviceStateWrapper device_wrapper, boolean connect) {
1358 
1359         BluetoothGatt bluetoothGatt = bluetoothGattMap.get(device_wrapper);
1360         if (bluetoothGatt == null) {
1361             bluetoothGatt =
1362                     device_wrapper.device.connectGatt(
1363                             context,
1364                             false,
1365                             new BluetoothGattCallback() {
1366                                 public void onConnectionStateChange(
1367                                         BluetoothGatt gatt, int status, int newState) {
1368                                     LeAudioDeviceStateWrapper device_wrapper = null;
1369                                     for (Map.Entry<LeAudioDeviceStateWrapper, BluetoothGatt> entry :
1370                                             bluetoothGattMap.entrySet()) {
1371                                         if (gatt == entry.getValue()) {
1372                                             device_wrapper = entry.getKey();
1373                                             break;
1374                                         }
1375                                     }
1376                                     if (device_wrapper == null) {
1377                                         return;
1378                                     }
1379 
1380                                     switch (newState) {
1381                                         case STATE_DISCONNECTED:
1382                                             device_wrapper.isGattBrConnectedMutable.postValue(
1383                                                     false);
1384                                             break;
1385                                         case STATE_CONNECTED:
1386                                             device_wrapper.isGattBrConnectedMutable.postValue(true);
1387                                             break;
1388                                         default:
1389                                             break;
1390                                     }
1391                                 }
1392                             },
1393                             BluetoothDevice.TRANSPORT_BREDR);
1394             bluetoothGattMap.put(device_wrapper, bluetoothGatt);
1395         }
1396 
1397         if (bluetoothGatt == null) {
1398             return;
1399         }
1400 
1401         if (connect) {
1402             bluetoothGatt.connect();
1403         } else {
1404             bluetoothGatt.disconnect();
1405         }
1406     }
1407 
hapReadPresetInfo(BluetoothDevice device, int preset_index)1408     public boolean hapReadPresetInfo(BluetoothDevice device, int preset_index) {
1409         if (bluetoothHapClient == null) return false;
1410 
1411         BluetoothHapPresetInfo new_preset = null;
1412 
1413         // Use hidden API
1414         try {
1415             Method getPresetInfoMethod =
1416                     BluetoothHapClient.class.getDeclaredMethod(
1417                             "getPresetInfo", BluetoothDevice.class, int.class);
1418             getPresetInfoMethod.setAccessible(true);
1419 
1420             new_preset =
1421                     (BluetoothHapPresetInfo)
1422                             getPresetInfoMethod.invoke(bluetoothHapClient, device, preset_index);
1423             if (new_preset == null) return false;
1424         } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
1425             // Do nothing'
1426             return false;
1427         }
1428 
1429         Optional<LeAudioDeviceStateWrapper> valid_device_opt =
1430                 allLeAudioDevicesMutable.getValue().stream()
1431                         .filter(state -> state.device.getAddress().equals(device.getAddress()))
1432                         .findAny();
1433 
1434         if (!valid_device_opt.isPresent()) return false;
1435 
1436         LeAudioDeviceStateWrapper valid_device = valid_device_opt.get();
1437         LeAudioDeviceStateWrapper.HapData svc_data = valid_device.hapData;
1438 
1439         List current_presets = svc_data.hapPresetsMutable.getValue();
1440         if (current_presets == null) current_presets = new ArrayList<BluetoothHapPresetInfo>();
1441 
1442         // Remove old one and add back the new one
1443         ListIterator<BluetoothHapPresetInfo> iter = current_presets.listIterator();
1444         while (iter.hasNext()) {
1445             if (iter.next().getIndex() == new_preset.getIndex()) {
1446                 iter.remove();
1447             }
1448         }
1449         current_presets.add(new_preset);
1450 
1451         svc_data.hapPresetsMutable.postValue(current_presets);
1452         return true;
1453     }
1454 
hapSetActivePreset(BluetoothDevice device, int preset_index)1455     public boolean hapSetActivePreset(BluetoothDevice device, int preset_index) {
1456         if (bluetoothHapClient == null) return false;
1457 
1458         bluetoothHapClient.selectPreset(device, preset_index);
1459         return true;
1460     }
1461 
hapSetActivePresetForGroup(BluetoothDevice device, int preset_index)1462     public boolean hapSetActivePresetForGroup(BluetoothDevice device, int preset_index) {
1463         if (bluetoothHapClient == null) return false;
1464 
1465         int groupId = bluetoothLeAudio.getGroupId(device);
1466         bluetoothHapClient.selectPresetForGroup(groupId, preset_index);
1467         return true;
1468     }
1469 
hapChangePresetName(BluetoothDevice device, int preset_index, String name)1470     public boolean hapChangePresetName(BluetoothDevice device, int preset_index, String name) {
1471         if (bluetoothHapClient == null) return false;
1472 
1473         bluetoothHapClient.setPresetName(device, preset_index, name);
1474         return true;
1475     }
1476 
hapPreviousDevicePreset(BluetoothDevice device)1477     public boolean hapPreviousDevicePreset(BluetoothDevice device) {
1478         if (bluetoothHapClient == null) return false;
1479 
1480         // Use hidden API
1481         try {
1482             Method switchToPreviousPresetMethod =
1483                     BluetoothHapClient.class.getDeclaredMethod(
1484                             "switchToPreviousPreset", BluetoothDevice.class);
1485             switchToPreviousPresetMethod.setAccessible(true);
1486 
1487             switchToPreviousPresetMethod.invoke(bluetoothHapClient, device);
1488         } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
1489             return false;
1490         }
1491         return true;
1492     }
1493 
hapNextDevicePreset(BluetoothDevice device)1494     public boolean hapNextDevicePreset(BluetoothDevice device) {
1495         if (bluetoothHapClient == null) return false;
1496 
1497         // Use hidden API
1498         try {
1499             Method switchToNextPresetMethod =
1500                     BluetoothHapClient.class.getDeclaredMethod(
1501                             "switchToNextPreset", BluetoothDevice.class);
1502             switchToNextPresetMethod.setAccessible(true);
1503 
1504             switchToNextPresetMethod.invoke(bluetoothHapClient, device);
1505         } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
1506             return false;
1507         }
1508         return true;
1509     }
1510 
hapPreviousGroupPreset(int group_id)1511     public boolean hapPreviousGroupPreset(int group_id) {
1512         if (bluetoothHapClient == null) return false;
1513 
1514         // Use hidden API
1515         try {
1516             Method switchToPreviousPresetForGroupMethod =
1517                     BluetoothHapClient.class.getDeclaredMethod(
1518                             "switchToPreviousPresetForGroup", int.class);
1519             switchToPreviousPresetForGroupMethod.setAccessible(true);
1520 
1521             switchToPreviousPresetForGroupMethod.invoke(bluetoothHapClient, group_id);
1522         } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
1523             return false;
1524         }
1525         return true;
1526     }
1527 
hapNextGroupPreset(int group_id)1528     public boolean hapNextGroupPreset(int group_id) {
1529         if (bluetoothHapClient == null) return false;
1530 
1531         // Use hidden API
1532         try {
1533             Method switchToNextPresetForGroupMethod =
1534                     BluetoothHapClient.class.getDeclaredMethod(
1535                             "switchToNextPresetForGroup", int.class);
1536             switchToNextPresetForGroupMethod.setAccessible(true);
1537 
1538             switchToNextPresetForGroupMethod.invoke(bluetoothHapClient, group_id);
1539         } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
1540             return false;
1541         }
1542         return true;
1543     }
1544 
hapGetHapGroup(BluetoothDevice device)1545     public int hapGetHapGroup(BluetoothDevice device) {
1546         if (bluetoothHapClient == null) return BluetoothCsipSetCoordinator.GROUP_ID_INVALID;
1547 
1548         // Use hidden API
1549         try {
1550             Method getHapGroupMethod =
1551                     BluetoothHapClient.class.getDeclaredMethod(
1552                             "getHapGroup", BluetoothDevice.class);
1553             getHapGroupMethod.setAccessible(true);
1554 
1555             return (Integer) getHapGroupMethod.invoke(bluetoothHapClient, device);
1556         } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
1557             // Do nothing
1558         }
1559         return BluetoothCsipSetCoordinator.GROUP_ID_INVALID;
1560     }
1561 
initLeAudioBroadcastProxy()1562     private void initLeAudioBroadcastProxy() {
1563         if (!isLeAudioBroadcastSourceSupported()) return;
1564         if (mBluetoothLeBroadcast == null) {
1565             bluetoothAdapter.getProfileProxy(
1566                     this.application, profileListener, BluetoothProfile.LE_AUDIO_BROADCAST);
1567         }
1568     }
1569 
cleanupLeAudioBroadcastProxy()1570     private void cleanupLeAudioBroadcastProxy() {
1571         if (!isLeAudioBroadcastSourceSupported()) return;
1572         if (mBluetoothLeBroadcast != null) {
1573             bluetoothAdapter.closeProfileProxy(
1574                     BluetoothProfile.LE_AUDIO_BROADCAST, mBluetoothLeBroadcast);
1575         }
1576     }
1577 
getBroadcastUpdateMetadataLive()1578     public LiveData<BluetoothLeBroadcastMetadata> getBroadcastUpdateMetadataLive() {
1579         return mBroadcastUpdateMutableLive;
1580     }
1581 
1582     public LiveData<Pair<Integer /* reason */, Integer /* broadcastId */>>
getBroadcastPlaybackStartedMutableLive()1583             getBroadcastPlaybackStartedMutableLive() {
1584         return mBroadcastPlaybackStartedMutableLive;
1585     }
1586 
1587     public LiveData<Pair<Integer /* reason */, Integer /* broadcastId */>>
getBroadcastPlaybackStoppedMutableLive()1588             getBroadcastPlaybackStoppedMutableLive() {
1589         return mBroadcastPlaybackStoppedMutableLive;
1590     }
1591 
getBroadcastAddedMutableLive()1592     public LiveData<Integer /* broadcastId */> getBroadcastAddedMutableLive() {
1593         return mBroadcastAddedMutableLive;
1594     }
1595 
1596     public LiveData<Pair<Integer /* reason */, Integer /* broadcastId */>>
getBroadcastRemovedMutableLive()1597             getBroadcastRemovedMutableLive() {
1598         return mBroadcastRemovedMutableLive;
1599     }
1600 
getBroadcastStatusMutableLive()1601     public LiveData<String> getBroadcastStatusMutableLive() {
1602         return mBroadcastStatusMutableLive;
1603     }
1604 
startBroadcast(BluetoothLeBroadcastSettings settings)1605     public boolean startBroadcast(BluetoothLeBroadcastSettings settings) {
1606         if (mBluetoothLeBroadcast == null) return false;
1607         mBluetoothLeBroadcast.startBroadcast(settings);
1608         return true;
1609     }
1610 
stopBroadcast(int broadcastId)1611     public boolean stopBroadcast(int broadcastId) {
1612         if (mBluetoothLeBroadcast == null) return false;
1613         mBluetoothLeBroadcast.stopBroadcast(broadcastId);
1614         return true;
1615     }
1616 
getAllLocalBroadcasts()1617     public List<BluetoothLeBroadcastMetadata> getAllLocalBroadcasts() {
1618         if (mBluetoothLeBroadcast == null) return Collections.emptyList();
1619         return mBluetoothLeBroadcast.getAllBroadcastMetadata();
1620     }
1621 
updateBroadcast(int broadcastId, BluetoothLeBroadcastSettings settings)1622     public boolean updateBroadcast(int broadcastId, BluetoothLeBroadcastSettings settings) {
1623         if (mBluetoothLeBroadcast == null) return false;
1624 
1625         mBluetoothLeBroadcast.updateBroadcast(broadcastId, settings);
1626         return true;
1627     }
1628 
getMaximumNumberOfBroadcast()1629     public int getMaximumNumberOfBroadcast() {
1630         if (mBluetoothLeBroadcast == null) {
1631             Log.d("BluetoothProxy", "mBluetoothLeBroadcast is null");
1632             return 0;
1633         }
1634         return mBluetoothLeBroadcast.getMaximumNumberOfBroadcasts();
1635     }
1636 
isPlaying(int broadcastId)1637     public boolean isPlaying(int broadcastId) {
1638         if (mBluetoothLeBroadcast == null) return false;
1639         return mBluetoothLeBroadcast.isPlaying(broadcastId);
1640     }
1641 
isLeAudioUnicastSupported()1642     boolean isLeAudioUnicastSupported() {
1643         return (bluetoothAdapter.isLeAudioSupported() == BluetoothStatusCodes.FEATURE_SUPPORTED);
1644     }
1645 
isCoordinatedSetProfileSupported()1646     boolean isCoordinatedSetProfileSupported() {
1647         return isLeAudioUnicastSupported();
1648     }
1649 
isVolumeControlClientSupported()1650     boolean isVolumeControlClientSupported() {
1651         return isLeAudioUnicastSupported();
1652     }
1653 
isLeAudioHearingAccessClientSupported()1654     boolean isLeAudioHearingAccessClientSupported() {
1655         return isLeAudioUnicastSupported();
1656     }
1657 
isLeAudioBroadcastSourceSupported()1658     public boolean isLeAudioBroadcastSourceSupported() {
1659         return (bluetoothAdapter.isLeAudioBroadcastSourceSupported()
1660                 == BluetoothStatusCodes.FEATURE_SUPPORTED);
1661     }
1662 
isLeAudioBroadcastScanAssistanSupported()1663     public boolean isLeAudioBroadcastScanAssistanSupported() {
1664         return (bluetoothAdapter.isLeAudioBroadcastAssistantSupported()
1665                 == BluetoothStatusCodes.FEATURE_SUPPORTED);
1666     }
1667 
setOnBassEventListener(OnBassEventListener listener)1668     public void setOnBassEventListener(OnBassEventListener listener) {
1669         mBassEventListener = listener;
1670     }
1671 
1672     // Used by BroadcastScanViewModel
1673     public interface OnBassEventListener {
onSourceFound(BluetoothLeBroadcastMetadata source)1674         void onSourceFound(BluetoothLeBroadcastMetadata source);
1675 
onScanningStateChanged(boolean isScanning)1676         void onScanningStateChanged(boolean isScanning);
1677     }
1678 
setOnLocalBroadcastEventListener(OnLocalBroadcastEventListener listener)1679     public void setOnLocalBroadcastEventListener(OnLocalBroadcastEventListener listener) {
1680         mLocalBroadcastEventListener = listener;
1681     }
1682 
1683     // Used by BroadcastScanViewModel
1684     public interface OnLocalBroadcastEventListener {
1685         // TODO: Add arguments in methods
onBroadcastStarted(int broadcastId)1686         void onBroadcastStarted(int broadcastId);
1687 
onBroadcastStopped(int broadcastId)1688         void onBroadcastStopped(int broadcastId);
1689 
onBroadcastUpdated(int broadcastId)1690         void onBroadcastUpdated(int broadcastId);
1691 
onBroadcastMetadataChanged(int broadcastId, BluetoothLeBroadcastMetadata metadata)1692         void onBroadcastMetadataChanged(int broadcastId, BluetoothLeBroadcastMetadata metadata);
1693     }
1694 }
1695