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 android.Manifest;
21 import android.animation.ObjectAnimator;
22 import android.bluetooth.BluetoothAdapter;
23 import android.bluetooth.BluetoothDevice;
24 import android.content.Intent;
25 import android.content.pm.PackageManager;
26 import android.os.Bundle;
27 import android.view.Menu;
28 import android.view.MenuInflater;
29 import android.view.MenuItem;
30 import android.widget.Toast;
31 
32 import androidx.activity.result.ActivityResultLauncher;
33 import androidx.activity.result.contract.ActivityResultContracts;
34 import androidx.annotation.NonNull;
35 import androidx.appcompat.app.AppCompatActivity;
36 import androidx.appcompat.widget.Toolbar;
37 import androidx.core.content.ContextCompat;
38 import androidx.lifecycle.ViewModelProviders;
39 import androidx.recyclerview.widget.LinearLayoutManager;
40 import androidx.recyclerview.widget.RecyclerView;
41 
42 import com.google.android.material.floatingactionbutton.FloatingActionButton;
43 
44 import java.util.List;
45 import java.util.Objects;
46 
47 public class MainActivity extends AppCompatActivity {
48     private static final String[] REQUIRED_PERMISSIONS =
49             new String[] {
50                 Manifest.permission.BLUETOOTH_ADMIN, Manifest.permission.BLUETOOTH_CONNECT,
51                 Manifest.permission.BLUETOOTH_SCAN, Manifest.permission.BLUETOOTH_PRIVILEGED,
52                 Manifest.permission.BLUETOOTH_ADVERTISE,
53                         Manifest.permission.INTERACT_ACROSS_USERS_FULL,
54                 Manifest.permission.ACCESS_FINE_LOCATION,
55             };
56     LeAudioRecycleViewAdapter recyclerViewAdapter;
57     private LeAudioViewModel leAudioViewModel;
58 
59     /** Returns true if any of the required permissions is missing. */
isPermissionMissing()60     private boolean isPermissionMissing() {
61         for (String permission : REQUIRED_PERMISSIONS) {
62             if (ContextCompat.checkSelfPermission(this, permission)
63                     != PackageManager.PERMISSION_GRANTED) {
64                 return true;
65             }
66         }
67         return false;
68     }
69 
initialize()70     private void initialize() {
71         setContentView(R.layout.activity_main);
72         Toolbar toolbar = findViewById(R.id.toolbar);
73         setSupportActionBar(toolbar);
74 
75         // Setup each component
76         setupLeAudioViewModel();
77         setupRecyclerViewAdapter();
78         setupViewModelObservers();
79 
80         // The 'refresh devices' button
81         FloatingActionButton fab = findViewById(R.id.fab);
82         fab.setOnClickListener(
83                 view -> {
84                     leAudioViewModel.queryDevices();
85                     ObjectAnimator.ofFloat(fab, "rotation", 0f, 360f).setDuration(500).start();
86                 });
87     }
88 
89     /** Request permission if missing. */
setupPermissions()90     private void setupPermissions() {
91         if (isPermissionMissing()) {
92             ActivityResultLauncher<String[]> permissionLauncher =
93                     registerForActivityResult(
94                             new ActivityResultContracts.RequestMultiplePermissions(),
95                             result -> {
96                                 for (String permission : REQUIRED_PERMISSIONS) {
97                                     if (!Objects.requireNonNull(result.get(permission))) {
98                                         Toast.makeText(
99                                                         getApplicationContext(),
100                                                         "LeAudio test apk permission denied.",
101                                                         Toast.LENGTH_SHORT)
102                                                 .show();
103                                         finish();
104                                         return;
105                                     }
106                                 }
107                                 initialize();
108                             });
109 
110             permissionLauncher.launch(REQUIRED_PERMISSIONS);
111         } else {
112             initialize();
113         }
114     }
115 
116     @Override
onCreate(Bundle savedInstanceState)117     protected void onCreate(Bundle savedInstanceState) {
118         setupPermissions();
119         super.onCreate(savedInstanceState);
120     }
121 
122     @Override
onDestroy()123     protected void onDestroy() {
124         super.onDestroy();
125         cleanupLeAudioViewModel();
126         cleanupRecyclerViewAdapter();
127         cleanupViewModelObservers();
128 
129         FloatingActionButton fab = findViewById(R.id.fab);
130         fab.setOnClickListener(null);
131     }
132 
133     @Override
onCreateOptionsMenu(Menu menu)134     public boolean onCreateOptionsMenu(Menu menu) {
135         MenuInflater inflater = getMenuInflater();
136         inflater.inflate(R.menu.menu_main, menu);
137         return true;
138     }
139 
140     @Override
onOptionsItemSelected(@onNull MenuItem item)141     public boolean onOptionsItemSelected(@NonNull MenuItem item) {
142         Intent intent = null;
143 
144         switch (item.getItemId()) {
145             case R.id.action_scan:
146                 // Clicking this gives no device or receiver context - no extras for this intent.
147                 intent = new Intent(MainActivity.this, BroadcastScanActivity.class);
148                 startActivity(intent);
149                 return true;
150 
151             case R.id.action_broadcast:
152                 if (leAudioViewModel.getBluetoothEnabledLive().getValue() == null
153                         || !leAudioViewModel.getBluetoothEnabledLive().getValue()) {
154                     Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
155                     startActivityForResult(enableBtIntent, 1);
156                 } else if (leAudioViewModel.isLeAudioBroadcastSourceSupported()) {
157                     intent = new Intent(MainActivity.this, BroadcasterActivity.class);
158                     intent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
159                     startActivityForResult(intent, 0);
160                 } else {
161                     Toast.makeText(
162                                     MainActivity.this,
163                                     "Broadcast Source is not supported.",
164                                     Toast.LENGTH_SHORT)
165                             .show();
166                 }
167                 return true;
168             default:
169                 // If we got here, the user's action was not recognized.
170                 // Invoke the superclass to handle it.onCreate
171                 return super.onOptionsItemSelected(item);
172         }
173     }
174 
175     @Override
onActivityResult(int requestCode, int resultCode, Intent intent)176     protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
177         super.onActivityResult(requestCode, resultCode, intent);
178 
179         // check if the request code is same as what was passed in request
180         if (requestCode == 0xc0de) {
181             if (intent != null) {
182                 String message = intent.getStringExtra("MESSAGE");
183                 Toast.makeText(
184                                 MainActivity.this,
185                                 message + "(" + resultCode + ")",
186                                 Toast.LENGTH_SHORT)
187                         .show();
188             }
189 
190             // TODO: Depending on the resultCode we should either stop the sync or try the PAST
191             leAudioViewModel.stopBroadcastObserving();
192         }
193     }
194 
195     @Override
onBackPressed()196     public void onBackPressed() {
197         finishActivity(0);
198         super.onBackPressed();
199     }
200 
setupLeAudioViewModel()201     private void setupLeAudioViewModel() {
202         leAudioViewModel = ViewModelProviders.of(this).get(LeAudioViewModel.class);
203 
204         // Observe bluetooth adapter state
205         leAudioViewModel
206                 .getBluetoothEnabledLive()
207                 .observe(
208                         this,
209                         is_enabled -> {
210                             if (is_enabled) {
211                                 List<LeAudioDeviceStateWrapper> deviceList =
212                                         leAudioViewModel.getAllLeAudioDevicesLive().getValue();
213                                 if (deviceList == null || deviceList.size() == 0)
214                                     leAudioViewModel.queryDevices();
215                             } else {
216                                 Intent enableBtIntent =
217                                         new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
218                                 startActivityForResult(enableBtIntent, 1);
219                             }
220 
221                             Toast.makeText(
222                                             MainActivity.this,
223                                             "Bluetooth is " + (is_enabled ? "enabled" : "disabled"),
224                                             Toast.LENGTH_SHORT)
225                                     .show();
226                         });
227     }
228 
cleanupLeAudioViewModel()229     private void cleanupLeAudioViewModel() {
230         leAudioViewModel.getBluetoothEnabledLive().removeObservers(this);
231     }
232 
setupRecyclerViewAdapter()233     void setupRecyclerViewAdapter() {
234         recyclerViewAdapter = new LeAudioRecycleViewAdapter(this);
235 
236         // Set listeners
237         setupViewsListItemClickListener();
238         setupViewsProfileUiEventListeners();
239 
240         // Generic stuff
241         RecyclerView recyclerView = findViewById(R.id.recycler_view);
242         recyclerView.setLayoutManager(new LinearLayoutManager(this));
243         recyclerView.setAdapter(recyclerViewAdapter);
244         recyclerView.setHasFixedSize(true);
245     }
246 
cleanupRecyclerViewAdapter()247     void cleanupRecyclerViewAdapter() {
248         cleanupViewsListItemClickListener();
249         cleanupViewsProfileUiEventListeners();
250     }
251 
setupViewsListItemClickListener()252     private void setupViewsListItemClickListener() {
253         recyclerViewAdapter.setOnItemClickListener(
254                 device -> {
255                     // Not used anymore
256                 });
257     }
258 
cleanupViewsListItemClickListener()259     private void cleanupViewsListItemClickListener() {
260         recyclerViewAdapter.setOnItemClickListener(null);
261     }
262 
setupViewsProfileUiEventListeners()263     private void setupViewsProfileUiEventListeners() {
264         recyclerViewAdapter.setOnLeAudioInteractionListener(
265                 new LeAudioRecycleViewAdapter.OnLeAudioInteractionListener() {
266                     @Override
267                     public void onConnectClick(
268                             LeAudioDeviceStateWrapper leAudioDeviceStateWrapper) {
269                         Toast.makeText(
270                                         MainActivity.this,
271                                         "Connecting Le Audio to "
272                                                 + leAudioDeviceStateWrapper.device.toString(),
273                                         Toast.LENGTH_SHORT)
274                                 .show();
275                         leAudioViewModel.connectLeAudio(leAudioDeviceStateWrapper.device, true);
276                     }
277 
278                     @Override
279                     public void onDisconnectClick(
280                             LeAudioDeviceStateWrapper leAudioDeviceStateWrapper) {
281                         Toast.makeText(
282                                         MainActivity.this,
283                                         "Disconnecting Le Audio from "
284                                                 + leAudioDeviceStateWrapper.device.toString(),
285                                         Toast.LENGTH_SHORT)
286                                 .show();
287                         leAudioViewModel.connectLeAudio(leAudioDeviceStateWrapper.device, false);
288                     }
289 
290                     @Override
291                     public void onStreamActionClicked(
292                             LeAudioDeviceStateWrapper leAudioDeviceStateWrapper,
293                             Integer group_id,
294                             Integer content_type,
295                             Integer action) {
296                         leAudioViewModel.streamAction(group_id, action, content_type);
297                     }
298 
299                     @Override
300                     public void onGroupSetClicked(
301                             LeAudioDeviceStateWrapper leAudioDeviceStateWrapper, Integer group_id) {
302                         leAudioViewModel.groupSet(leAudioDeviceStateWrapper.device, group_id);
303                     }
304 
305                     @Override
306                     public void onGroupUnsetClicked(
307                             LeAudioDeviceStateWrapper leAudioDeviceStateWrapper, Integer group_id) {
308                         leAudioViewModel.groupUnset(leAudioDeviceStateWrapper.device, group_id);
309                     }
310 
311                     @Override
312                     public void onGroupDestroyClicked(
313                             LeAudioDeviceStateWrapper leAudioDeviceStateWrapper, Integer group_id) {
314                         // Not available anymore
315                         Toast.makeText(
316                                         MainActivity.this,
317                                         "Operation not supported on this API version",
318                                         Toast.LENGTH_SHORT)
319                                 .show();
320                     }
321 
322                     @Override
323                     public void onGroupSetLockClicked(
324                             LeAudioDeviceStateWrapper leAudioDeviceStateWrapper,
325                             Integer group_id,
326                             boolean lock) {
327                         leAudioViewModel.groupSetLock(group_id, lock);
328                     }
329 
330                     @Override
331                     public void onMicrophoneMuteChanged(
332                             LeAudioDeviceStateWrapper leAudioDeviceStateWrapper,
333                             boolean mute,
334                             boolean is_from_user) {
335                         // Not available anymore
336                         Toast.makeText(
337                                         MainActivity.this,
338                                         "Operation not supported on this API version",
339                                         Toast.LENGTH_SHORT)
340                                 .show();
341                     }
342                 });
343 
344         recyclerViewAdapter.setOnVolumeControlInteractionListener(
345                 new LeAudioRecycleViewAdapter.OnVolumeControlInteractionListener() {
346                     @Override
347                     public void onConnectClick(
348                             LeAudioDeviceStateWrapper leAudioDeviceStateWrapper) {
349                         // Not available anymore
350                         Toast.makeText(
351                                         MainActivity.this,
352                                         "Operation not supported on this API version",
353                                         Toast.LENGTH_SHORT)
354                                 .show();
355                     }
356 
357                     @Override
358                     public void onDisconnectClick(
359                             LeAudioDeviceStateWrapper leAudioDeviceStateWrapper) {
360                         // Not available anymore
361                         Toast.makeText(
362                                         MainActivity.this,
363                                         "Operation not supported on this API version",
364                                         Toast.LENGTH_SHORT)
365                                 .show();
366                     }
367 
368                     @Override
369                     public void onVolumeChanged(
370                             LeAudioDeviceStateWrapper leAudioDeviceStateWrapper,
371                             int volume,
372                             boolean is_from_user) {
373                         if (is_from_user) {
374                             leAudioViewModel.setVolume(leAudioDeviceStateWrapper.device, volume);
375                         }
376                     }
377 
378                     @Override
379                     public void onCheckedChanged(
380                             LeAudioDeviceStateWrapper leAudioDeviceStateWrapper,
381                             boolean is_checked) {
382                         // Not available anymore
383                         Toast.makeText(
384                                         MainActivity.this,
385                                         "Operation not supported on this API version",
386                                         Toast.LENGTH_SHORT)
387                                 .show();
388                     }
389 
390                     @Override
391                     public void onInputGetStateButtonClicked(
392                             LeAudioDeviceStateWrapper leAudioDeviceStateWrapper, int input_id) {
393                         // Not available anymore
394                         Toast.makeText(
395                                         MainActivity.this,
396                                         "Operation not supported on this API version",
397                                         Toast.LENGTH_SHORT)
398                                 .show();
399                     }
400 
401                     @Override
402                     public void onInputGainValueChanged(
403                             LeAudioDeviceStateWrapper leAudioDeviceStateWrapper,
404                             int input_id,
405                             int value) {
406                         // Not available anymore
407                         Toast.makeText(
408                                         MainActivity.this,
409                                         "Operation not supported on this API version",
410                                         Toast.LENGTH_SHORT)
411                                 .show();
412                     }
413 
414                     @Override
415                     public void onInputMuteSwitched(
416                             LeAudioDeviceStateWrapper leAudioDeviceStateWrapper,
417                             int input_id,
418                             boolean is_muted) {
419                         // Not available anymore
420                         Toast.makeText(
421                                         MainActivity.this,
422                                         "Operation not supported on this API version",
423                                         Toast.LENGTH_SHORT)
424                                 .show();
425                     }
426 
427                     @Override
428                     public void onInputSetGainModeButtonClicked(
429                             LeAudioDeviceStateWrapper leAudioDeviceStateWrapper,
430                             int input_id,
431                             boolean is_auto) {
432                         // Not available anymore
433                         Toast.makeText(
434                                         MainActivity.this,
435                                         "Operation not supported on this API version",
436                                         Toast.LENGTH_SHORT)
437                                 .show();
438                     }
439 
440                     @Override
441                     public void onInputGetGainPropsButtonClicked(
442                             LeAudioDeviceStateWrapper leAudioDeviceStateWrapper, int input_id) {
443                         // Not available anymore
444                         Toast.makeText(
445                                         MainActivity.this,
446                                         "Operation not supported on this API version",
447                                         Toast.LENGTH_SHORT)
448                                 .show();
449                     }
450 
451                     @Override
452                     public void onInputGetTypeButtonClicked(
453                             LeAudioDeviceStateWrapper leAudioDeviceStateWrapper, int input_id) {
454                         // Not available anymore
455                         Toast.makeText(
456                                         MainActivity.this,
457                                         "Operation not supported on this API version",
458                                         Toast.LENGTH_SHORT)
459                                 .show();
460                     }
461 
462                     @Override
463                     public void onInputGetStatusButton(
464                             LeAudioDeviceStateWrapper leAudioDeviceStateWrapper, int input_id) {
465                         // Not available anymore
466                         Toast.makeText(
467                                         MainActivity.this,
468                                         "Operation not supported on this API version",
469                                         Toast.LENGTH_SHORT)
470                                 .show();
471                     }
472 
473                     @Override
474                     public void onInputGetDescriptionButtonClicked(
475                             LeAudioDeviceStateWrapper leAudioDeviceStateWrapper, int input_id) {
476                         // Not available anymore
477                         Toast.makeText(
478                                         MainActivity.this,
479                                         "Operation not supported on this API version",
480                                         Toast.LENGTH_SHORT)
481                                 .show();
482                     }
483 
484                     @Override
485                     public void onInputSetDescriptionButtonClicked(
486                             LeAudioDeviceStateWrapper leAudioDeviceStateWrapper,
487                             int input_id,
488                             String description) {
489                         // Not available anymore
490                         Toast.makeText(
491                                         MainActivity.this,
492                                         "Operation not supported on this API version",
493                                         Toast.LENGTH_SHORT)
494                                 .show();
495                     }
496 
497                     @Override
498                     public void onOutputGetGainButtonClicked(
499                             LeAudioDeviceStateWrapper leAudioDeviceStateWrapper, int output_id) {
500                         // Not available anymore
501                         Toast.makeText(
502                                         MainActivity.this,
503                                         "Operation not supported on this API version",
504                                         Toast.LENGTH_SHORT)
505                                 .show();
506                     }
507 
508                     @Override
509                     public void onOutputGainOffsetGainValueChanged(
510                             LeAudioDeviceStateWrapper leAudioDeviceStateWrapper,
511                             int output_id,
512                             int value) {
513                         // Not available anymore
514                         Toast.makeText(
515                                         MainActivity.this,
516                                         "Operation not supported on this API version",
517                                         Toast.LENGTH_SHORT)
518                                 .show();
519                     }
520 
521                     @Override
522                     public void onOutputGetLocationButtonClicked(
523                             LeAudioDeviceStateWrapper leAudioDeviceStateWrapper, int output_id) {
524                         // Not available anymore
525                         Toast.makeText(
526                                         MainActivity.this,
527                                         "Operation not supported on this API version",
528                                         Toast.LENGTH_SHORT)
529                                 .show();
530                     }
531 
532                     @Override
533                     public void onOutputSetLocationButtonClicked(
534                             LeAudioDeviceStateWrapper leAudioDeviceStateWrapper,
535                             int output_id,
536                             int location) {
537                         // Not available anymore
538                         Toast.makeText(
539                                         MainActivity.this,
540                                         "Operation not supported on this API version",
541                                         Toast.LENGTH_SHORT)
542                                 .show();
543                     }
544 
545                     @Override
546                     public void onOutputGetDescriptionButtonClicked(
547                             LeAudioDeviceStateWrapper leAudioDeviceStateWrapper, int output_id) {
548                         // Not available anymore
549                         Toast.makeText(
550                                         MainActivity.this,
551                                         "Operation not supported on this API version",
552                                         Toast.LENGTH_SHORT)
553                                 .show();
554                     }
555 
556                     @Override
557                     public void onOutputSetDescriptionButton(
558                             LeAudioDeviceStateWrapper leAudioDeviceStateWrapper,
559                             int output_id,
560                             String description) {
561                         // Not available anymore
562                         Toast.makeText(
563                                         MainActivity.this,
564                                         "Operation not supported on this API version",
565                                         Toast.LENGTH_SHORT)
566                                 .show();
567                     }
568                 });
569 
570         recyclerViewAdapter.setOnHapInteractionListener(
571                 new LeAudioRecycleViewAdapter.OnHapInteractionListener() {
572                     @Override
573                     public void onConnectClick(
574                             LeAudioDeviceStateWrapper leAudioDeviceStateWrapper) {
575                         Toast.makeText(
576                                         MainActivity.this,
577                                         "Connecting HAP to "
578                                                 + leAudioDeviceStateWrapper.device.toString(),
579                                         Toast.LENGTH_SHORT)
580                                 .show();
581                         leAudioViewModel.connectHap(leAudioDeviceStateWrapper.device, true);
582                     }
583 
584                     @Override
585                     public void onDisconnectClick(
586                             LeAudioDeviceStateWrapper leAudioDeviceStateWrapper) {
587                         Toast.makeText(
588                                         MainActivity.this,
589                                         "Disconnecting HAP from "
590                                                 + leAudioDeviceStateWrapper.device.toString(),
591                                         Toast.LENGTH_SHORT)
592                                 .show();
593                         leAudioViewModel.connectHap(leAudioDeviceStateWrapper.device, false);
594                     }
595 
596                     @Override
597                     public void onReadPresetInfoClicked(BluetoothDevice device, int preset_index) {
598                         leAudioViewModel.hapReadPresetInfo(device, preset_index);
599                     }
600 
601                     @Override
602                     public void onSetActivePresetClicked(BluetoothDevice device, int preset_index) {
603                         leAudioViewModel.hapSetActivePreset(device, preset_index);
604                     }
605 
606                     @Override
607                     public void onSetActivePresetForGroupClicked(
608                             BluetoothDevice device, int preset_index) {
609                         leAudioViewModel.hapSetActivePresetForGroup(device, preset_index);
610                     }
611 
612                     @Override
613                     public void onChangePresetNameClicked(
614                             BluetoothDevice device, int preset_index, String name) {
615                         leAudioViewModel.hapChangePresetName(device, preset_index, name);
616                     }
617 
618                     @Override
619                     public void onPreviousDevicePresetClicked(BluetoothDevice device) {
620                         leAudioViewModel.hapPreviousDevicePreset(device);
621                     }
622 
623                     @Override
624                     public void onNextDevicePresetClicked(BluetoothDevice device) {
625                         leAudioViewModel.hapNextDevicePreset(device);
626                     }
627 
628                     @Override
629                     public void onPreviousGroupPresetClicked(BluetoothDevice device) {
630                         final int group_id = leAudioViewModel.hapGetHapGroup(device);
631                         final boolean sent = leAudioViewModel.hapPreviousGroupPreset(group_id);
632                         if (!sent)
633                             Toast.makeText(
634                                             MainActivity.this,
635                                             "Group " + group_id + " operation failed",
636                                             Toast.LENGTH_SHORT)
637                                     .show();
638                     }
639 
640                     @Override
641                     public void onNextGroupPresetClicked(BluetoothDevice device) {
642                         final int group_id = leAudioViewModel.hapGetHapGroup(device);
643                         final boolean sent = leAudioViewModel.hapNextGroupPreset(group_id);
644                         if (!sent)
645                             Toast.makeText(
646                                             MainActivity.this,
647                                             "Group " + group_id + " operation failed",
648                                             Toast.LENGTH_SHORT)
649                                     .show();
650                     }
651                 });
652 
653         recyclerViewAdapter.setOnBassInteractionListener(
654                 new LeAudioRecycleViewAdapter.OnBassInteractionListener() {
655                     @Override
656                     public void onConnectClick(
657                             LeAudioDeviceStateWrapper leAudioDeviceStateWrapper) {
658                         Toast.makeText(
659                                         MainActivity.this,
660                                         "Connecting BASS to "
661                                                 + leAudioDeviceStateWrapper.device.toString(),
662                                         Toast.LENGTH_SHORT)
663                                 .show();
664                         leAudioViewModel.connectBass(leAudioDeviceStateWrapper.device, true);
665                     }
666 
667                     @Override
668                     public void onDisconnectClick(
669                             LeAudioDeviceStateWrapper leAudioDeviceStateWrapper) {
670                         Toast.makeText(
671                                         MainActivity.this,
672                                         "Disconnecting BASS from "
673                                                 + leAudioDeviceStateWrapper.device.toString(),
674                                         Toast.LENGTH_SHORT)
675                                 .show();
676                         leAudioViewModel.connectBass(leAudioDeviceStateWrapper.device, false);
677                     }
678 
679                     @Override
680                     public void onReceiverSelected(
681                             LeAudioDeviceStateWrapper leAudioDeviceStateWrapper, int receiver_id) {
682                         // Do nothing here, the UI is updated elsewhere and we already have the
683                         // latest state value as well
684                     }
685 
686                     @Override
687                     public void onBroadcastCodeEntered(
688                             BluetoothDevice device, int receiver_id, byte[] broadcast_code) {
689                         leAudioViewModel.setBroadcastCode(device, receiver_id, broadcast_code);
690                     }
691 
692                     @Override
693                     public void onStopSyncReq(BluetoothDevice device, int receiver_id) {
694                         // TODO: When is onStopSyncReq called? and what does below code do?
695 
696                         //                        List<BluetoothBroadcastAudioScanBaseConfig>
697                         // configs = new ArrayList<>();
698                         //                        // JT@CC: How come you can call this with null
699                         // metadata when the
700                         //                        // constructor has the @Nonull annotation for the
701                         // param?
702                         //                        BluetoothBroadcastAudioScanBaseConfig stop_config
703                         // =
704                         //                                new
705                         // BluetoothBroadcastAudioScanBaseConfig(0, new byte[] {});
706                         //                        configs.add(stop_config);
707                         //
708                         //                        leAudioViewModel.modifyBroadcastSource(device,
709                         // receiver_id, false, configs);
710                     }
711 
712                     @Override
713                     public void onRemoveSourceReq(BluetoothDevice device, int receiver_id) {
714                         leAudioViewModel.removeBroadcastSource(device, receiver_id);
715                     }
716 
717                     @Override
718                     public void onStopObserving() {
719                         leAudioViewModel.stopBroadcastObserving();
720                     }
721                 });
722     }
723 
cleanupViewsProfileUiEventListeners()724     private void cleanupViewsProfileUiEventListeners() {
725         recyclerViewAdapter.setOnLeAudioInteractionListener(null);
726         recyclerViewAdapter.setOnVolumeControlInteractionListener(null);
727         recyclerViewAdapter.setOnHapInteractionListener(null);
728         recyclerViewAdapter.setOnBassInteractionListener(null);
729     }
730 
731     // This sets the initial values and set up the observers
setupViewModelObservers()732     private void setupViewModelObservers() {
733         List<LeAudioDeviceStateWrapper> devices =
734                 leAudioViewModel.getAllLeAudioDevicesLive().getValue();
735 
736         if (devices != null) recyclerViewAdapter.updateLeAudioDeviceList(devices);
737         leAudioViewModel
738                 .getAllLeAudioDevicesLive()
739                 .observe(
740                         this,
741                         bluetoothDevices -> {
742                             recyclerViewAdapter.updateLeAudioDeviceList(bluetoothDevices);
743                         });
744     }
745 
cleanupViewModelObservers()746     private void cleanupViewModelObservers() {
747         leAudioViewModel.getAllLeAudioDevicesLive().removeObservers(this);
748     }
749 }
750