• 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 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.setOnGattBrConnectListener(
571                 new LeAudioRecycleViewAdapter.OnGattBrListener() {
572                     @Override
573                     public void onToggleSwitch(
574                             LeAudioDeviceStateWrapper device_wrapper, boolean switchState) {
575                         Toast.makeText(
576                                         MainActivity.this,
577                                         "Connecting GATT BR to " + device_wrapper.device.toString(),
578                                         Toast.LENGTH_SHORT)
579                                 .show();
580                         leAudioViewModel.connectGattBr(
581                                 getApplicationContext(), device_wrapper, switchState);
582                     }
583                 });
584         recyclerViewAdapter.setOnHapInteractionListener(
585                 new LeAudioRecycleViewAdapter.OnHapInteractionListener() {
586                     @Override
587                     public void onConnectClick(
588                             LeAudioDeviceStateWrapper leAudioDeviceStateWrapper) {
589                         Toast.makeText(
590                                         MainActivity.this,
591                                         "Connecting HAP to "
592                                                 + leAudioDeviceStateWrapper.device.toString(),
593                                         Toast.LENGTH_SHORT)
594                                 .show();
595                         leAudioViewModel.connectHap(leAudioDeviceStateWrapper.device, true);
596                     }
597 
598                     @Override
599                     public void onDisconnectClick(
600                             LeAudioDeviceStateWrapper leAudioDeviceStateWrapper) {
601                         Toast.makeText(
602                                         MainActivity.this,
603                                         "Disconnecting HAP from "
604                                                 + leAudioDeviceStateWrapper.device.toString(),
605                                         Toast.LENGTH_SHORT)
606                                 .show();
607                         leAudioViewModel.connectHap(leAudioDeviceStateWrapper.device, false);
608                     }
609 
610                     @Override
611                     public void onReadPresetInfoClicked(BluetoothDevice device, int preset_index) {
612                         leAudioViewModel.hapReadPresetInfo(device, preset_index);
613                     }
614 
615                     @Override
616                     public void onSetActivePresetClicked(BluetoothDevice device, int preset_index) {
617                         leAudioViewModel.hapSetActivePreset(device, preset_index);
618                     }
619 
620                     @Override
621                     public void onSetActivePresetForGroupClicked(
622                             BluetoothDevice device, int preset_index) {
623                         leAudioViewModel.hapSetActivePresetForGroup(device, preset_index);
624                     }
625 
626                     @Override
627                     public void onChangePresetNameClicked(
628                             BluetoothDevice device, int preset_index, String name) {
629                         leAudioViewModel.hapChangePresetName(device, preset_index, name);
630                     }
631 
632                     @Override
633                     public void onPreviousDevicePresetClicked(BluetoothDevice device) {
634                         leAudioViewModel.hapPreviousDevicePreset(device);
635                     }
636 
637                     @Override
638                     public void onNextDevicePresetClicked(BluetoothDevice device) {
639                         leAudioViewModel.hapNextDevicePreset(device);
640                     }
641 
642                     @Override
643                     public void onPreviousGroupPresetClicked(BluetoothDevice device) {
644                         final int group_id = leAudioViewModel.hapGetHapGroup(device);
645                         final boolean sent = leAudioViewModel.hapPreviousGroupPreset(group_id);
646                         if (!sent)
647                             Toast.makeText(
648                                             MainActivity.this,
649                                             "Group " + group_id + " operation failed",
650                                             Toast.LENGTH_SHORT)
651                                     .show();
652                     }
653 
654                     @Override
655                     public void onNextGroupPresetClicked(BluetoothDevice device) {
656                         final int group_id = leAudioViewModel.hapGetHapGroup(device);
657                         final boolean sent = leAudioViewModel.hapNextGroupPreset(group_id);
658                         if (!sent)
659                             Toast.makeText(
660                                             MainActivity.this,
661                                             "Group " + group_id + " operation failed",
662                                             Toast.LENGTH_SHORT)
663                                     .show();
664                     }
665                 });
666 
667         recyclerViewAdapter.setOnBassInteractionListener(
668                 new LeAudioRecycleViewAdapter.OnBassInteractionListener() {
669                     @Override
670                     public void onConnectClick(
671                             LeAudioDeviceStateWrapper leAudioDeviceStateWrapper) {
672                         Toast.makeText(
673                                         MainActivity.this,
674                                         "Connecting BASS to "
675                                                 + leAudioDeviceStateWrapper.device.toString(),
676                                         Toast.LENGTH_SHORT)
677                                 .show();
678                         leAudioViewModel.connectBass(leAudioDeviceStateWrapper.device, true);
679                     }
680 
681                     @Override
682                     public void onDisconnectClick(
683                             LeAudioDeviceStateWrapper leAudioDeviceStateWrapper) {
684                         Toast.makeText(
685                                         MainActivity.this,
686                                         "Disconnecting BASS from "
687                                                 + leAudioDeviceStateWrapper.device.toString(),
688                                         Toast.LENGTH_SHORT)
689                                 .show();
690                         leAudioViewModel.connectBass(leAudioDeviceStateWrapper.device, false);
691                     }
692 
693                     @Override
694                     public void onReceiverSelected(
695                             LeAudioDeviceStateWrapper leAudioDeviceStateWrapper, int receiver_id) {
696                         // Do nothing here, the UI is updated elsewhere and we already have the
697                         // latest state value as well
698                     }
699 
700                     @Override
701                     public void onBroadcastCodeEntered(
702                             BluetoothDevice device, int receiver_id, byte[] broadcast_code) {
703                         leAudioViewModel.setBroadcastCode(device, receiver_id, broadcast_code);
704                     }
705 
706                     @Override
707                     public void onStopSyncReq(BluetoothDevice device, int receiver_id) {
708                         // TODO: When is onStopSyncReq called? and what does below code do?
709 
710                         //                        List<BluetoothBroadcastAudioScanBaseConfig>
711                         // configs = new ArrayList<>();
712                         //                        // JT@CC: How come you can call this with null
713                         // metadata when the
714                         //                        // constructor has the @Nonull annotation for the
715                         // param?
716                         //                        BluetoothBroadcastAudioScanBaseConfig stop_config
717                         // =
718                         //                                new
719                         // BluetoothBroadcastAudioScanBaseConfig(0, new byte[] {});
720                         //                        configs.add(stop_config);
721                         //
722                         //                        leAudioViewModel.modifyBroadcastSource(device,
723                         // receiver_id, false, configs);
724                     }
725 
726                     @Override
727                     public void onRemoveSourceReq(BluetoothDevice device, int receiver_id) {
728                         leAudioViewModel.removeBroadcastSource(device, receiver_id);
729                     }
730 
731                     @Override
732                     public void onStopObserving() {
733                         leAudioViewModel.stopBroadcastObserving();
734                     }
735                 });
736     }
737 
cleanupViewsProfileUiEventListeners()738     private void cleanupViewsProfileUiEventListeners() {
739         recyclerViewAdapter.setOnLeAudioInteractionListener(null);
740         recyclerViewAdapter.setOnVolumeControlInteractionListener(null);
741         recyclerViewAdapter.setOnHapInteractionListener(null);
742         recyclerViewAdapter.setOnBassInteractionListener(null);
743     }
744 
745     // This sets the initial values and set up the observers
setupViewModelObservers()746     private void setupViewModelObservers() {
747         List<LeAudioDeviceStateWrapper> devices =
748                 leAudioViewModel.getAllLeAudioDevicesLive().getValue();
749 
750         if (devices != null) recyclerViewAdapter.updateLeAudioDeviceList(devices);
751         leAudioViewModel
752                 .getAllLeAudioDevicesLive()
753                 .observe(
754                         this,
755                         bluetoothDevices -> {
756                             recyclerViewAdapter.updateLeAudioDeviceList(bluetoothDevices);
757                         });
758     }
759 
cleanupViewModelObservers()760     private void cleanupViewModelObservers() {
761         leAudioViewModel.getAllLeAudioDevicesLive().removeObservers(this);
762     }
763 }
764