• 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.BluetoothLeBroadcastReceiveState.BIG_ENCRYPTION_STATE_BAD_CODE;
21 import static android.bluetooth.BluetoothLeBroadcastReceiveState.BIG_ENCRYPTION_STATE_CODE_REQUIRED;
22 import static android.bluetooth.BluetoothLeBroadcastReceiveState.BIG_ENCRYPTION_STATE_DECRYPTING;
23 import static android.bluetooth.BluetoothLeBroadcastReceiveState.BIG_ENCRYPTION_STATE_NOT_ENCRYPTED;
24 import static android.bluetooth.BluetoothLeBroadcastReceiveState.PA_SYNC_STATE_FAILED_TO_SYNCHRONIZE;
25 import static android.bluetooth.BluetoothLeBroadcastReceiveState.PA_SYNC_STATE_IDLE;
26 import static android.bluetooth.BluetoothLeBroadcastReceiveState.PA_SYNC_STATE_NO_PAST;
27 import static android.bluetooth.BluetoothLeBroadcastReceiveState.PA_SYNC_STATE_SYNCHRONIZED;
28 import static android.bluetooth.BluetoothLeBroadcastReceiveState.PA_SYNC_STATE_SYNCINFO_REQUEST;
29 
30 import android.animation.ObjectAnimator;
31 import android.bluetooth.BluetoothDevice;
32 import android.bluetooth.BluetoothHapClient;
33 import android.bluetooth.BluetoothLeAudio;
34 import android.bluetooth.BluetoothLeBroadcastReceiveState;
35 import android.content.Intent;
36 import android.content.res.Configuration;
37 import android.content.res.Resources;
38 import android.os.ParcelUuid;
39 import android.text.InputFilter;
40 import android.text.InputType;
41 import android.util.Log;
42 import android.view.LayoutInflater;
43 import android.view.View;
44 import android.view.ViewGroup;
45 import android.widget.AdapterView;
46 import android.widget.ArrayAdapter;
47 import android.widget.Button;
48 import android.widget.EditText;
49 import android.widget.ImageButton;
50 import android.widget.NumberPicker;
51 import android.widget.SeekBar;
52 import android.widget.Spinner;
53 import android.widget.Switch;
54 import android.widget.TextView;
55 import android.widget.Toast;
56 
57 import androidx.annotation.NonNull;
58 import androidx.annotation.Nullable;
59 import androidx.appcompat.app.AlertDialog;
60 import androidx.appcompat.app.AppCompatActivity;
61 import androidx.lifecycle.MutableLiveData;
62 import androidx.recyclerview.widget.RecyclerView;
63 
64 import java.lang.reflect.Field;
65 import java.util.ArrayList;
66 import java.util.Arrays;
67 import java.util.List;
68 import java.util.Map;
69 import java.util.stream.Collectors;
70 import java.util.stream.IntStream;
71 
72 public class LeAudioRecycleViewAdapter
73         extends RecyclerView.Adapter<LeAudioRecycleViewAdapter.ViewHolder> {
74     private final AppCompatActivity parent;
75     private OnItemClickListener clickListener;
76     private OnLeAudioInteractionListener leAudioInteractionListener;
77     private OnVolumeControlInteractionListener volumeControlInteractionListener;
78     private OnBassInteractionListener bassInteractionListener;
79     private OnHapInteractionListener hapInteractionListener;
80     private OnGattBrListener gattBrListener;
81     private final ArrayList<LeAudioDeviceStateWrapper> devices;
82 
83     private int GROUP_NODE_ADDED = 1;
84     private int GROUP_NODE_REMOVED = 2;
85 
LeAudioRecycleViewAdapter(AppCompatActivity context)86     public LeAudioRecycleViewAdapter(AppCompatActivity context) {
87         this.parent = context;
88         devices = new ArrayList<>();
89     }
90 
91     @NonNull
92     @Override
onCreateViewHolder(@onNull ViewGroup parent, int viewType)93     public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
94         View v =
95                 LayoutInflater.from(parent.getContext())
96                         .inflate(R.layout.le_audio_device_fragment, parent, false);
97         return new ViewHolder(v);
98     }
99 
100     // As we scroll this methods rebinds devices below to our ViewHolders which are reused when
101     // they go off the screen. This is also called when notifyItemChanged(position) is called
102     // without the payloads.
103     @Override
onBindViewHolder(@onNull ViewHolder holder, int position)104     public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
105         LeAudioDeviceStateWrapper leAudioDeviceStateWrapper = devices.get(position);
106 
107         if (leAudioDeviceStateWrapper != null) {
108             holder.deviceName.setText(
109                     parent.getString(R.string.notes_icon)
110                             + " "
111                             + leAudioDeviceStateWrapper.device.getName()
112                             + " ["
113                             + leAudioDeviceStateWrapper.device
114                             + "]");
115 
116             if (leAudioDeviceStateWrapper.device.getUuids() != null) {
117                 holder.itemView
118                         .findViewById(R.id.le_audio_switch)
119                         .setEnabled(
120                                 Arrays.asList(leAudioDeviceStateWrapper.device.getUuids())
121                                         .contains(
122                                                 ParcelUuid.fromString(
123                                                         parent.getString(
124                                                                 R.string.svc_uuid_le_audio))));
125 
126                 holder.itemView
127                         .findViewById(R.id.vc_switch)
128                         .setEnabled(
129                                 Arrays.asList(leAudioDeviceStateWrapper.device.getUuids())
130                                         .contains(
131                                                 ParcelUuid.fromString(
132                                                         parent.getString(
133                                                                 R.string
134                                                                         .svc_uuid_volume_control))));
135 
136                 holder.itemView
137                         .findViewById(R.id.hap_switch)
138                         .setEnabled(
139                                 Arrays.asList(leAudioDeviceStateWrapper.device.getUuids())
140                                         .contains(
141                                                 ParcelUuid.fromString(
142                                                         parent.getString(R.string.svc_uuid_has))));
143                 holder.itemView
144                         .findViewById(R.id.gatt_br_switch)
145                         .setEnabled(
146                                 leAudioDeviceStateWrapper.device.getType()
147                                         == BluetoothDevice.DEVICE_TYPE_DUAL);
148 
149                 holder.itemView
150                         .findViewById(R.id.bass_switch)
151                         .setEnabled(
152                                 Arrays.asList(leAudioDeviceStateWrapper.device.getUuids())
153                                         .contains(
154                                                 ParcelUuid.fromString(
155                                                         parent.getString(
156                                                                 R.string
157                                                                         .svc_uuid_broadcast_audio))));
158             }
159         }
160 
161         // Set state observables
162         setGattBrConnectionObserver(holder, leAudioDeviceStateWrapper);
163         setLeAudioStateObservers(holder, leAudioDeviceStateWrapper);
164         setVolumeControlStateObservers(holder, leAudioDeviceStateWrapper);
165         setVolumeControlUiStateObservers(holder, leAudioDeviceStateWrapper);
166         setBassStateObservers(holder, leAudioDeviceStateWrapper);
167         setHasStateObservers(holder, leAudioDeviceStateWrapper);
168         setBassUiStateObservers(holder, leAudioDeviceStateWrapper);
169     }
170 
setGattBrConnectionObserver( @onNull ViewHolder holder, LeAudioDeviceStateWrapper leAudioDeviceStateWrapper)171     private void setGattBrConnectionObserver(
172             @NonNull ViewHolder holder, LeAudioDeviceStateWrapper leAudioDeviceStateWrapper) {
173         if (leAudioDeviceStateWrapper.isGattBrConnectedMutable.hasObservers()) {
174             leAudioDeviceStateWrapper.isGattBrConnectedMutable.removeObservers(this.parent);
175         }
176         leAudioDeviceStateWrapper.isGattBrConnectedMutable.observe(
177                 this.parent,
178                 is_connected -> {
179                     // FIXME: How to prevent the callback from firing when we set this by code
180                     if (is_connected != holder.gattBrConnectionSwitch.isChecked()) {
181                         holder.gattBrConnectionSwitch.setActivated(false);
182                         holder.gattBrConnectionSwitch.setChecked(is_connected);
183                         holder.gattBrConnectionSwitch.setActivated(true);
184                     }
185                 });
186     }
187 
setLeAudioStateObservers( @onNull ViewHolder holder, LeAudioDeviceStateWrapper leAudioDeviceStateWrapper)188     private void setLeAudioStateObservers(
189             @NonNull ViewHolder holder, LeAudioDeviceStateWrapper leAudioDeviceStateWrapper) {
190         LeAudioDeviceStateWrapper.LeAudioData le_audio_svc_data =
191                 leAudioDeviceStateWrapper.leAudioData;
192         if (le_audio_svc_data != null) {
193             if (le_audio_svc_data.isConnectedMutable.hasObservers())
194                 le_audio_svc_data.isConnectedMutable.removeObservers(this.parent);
195             le_audio_svc_data.isConnectedMutable.observe(
196                     this.parent,
197                     is_connected -> {
198                         // FIXME: How to prevent the callback from firing when we set this by code
199                         if (is_connected != holder.leAudioConnectionSwitch.isChecked()) {
200                             holder.leAudioConnectionSwitch.setActivated(false);
201                             holder.leAudioConnectionSwitch.setChecked(is_connected);
202                             holder.leAudioConnectionSwitch.setActivated(true);
203                         }
204 
205                         if (holder.itemView.findViewById(R.id.le_audio_layout).getVisibility()
206                                 != (is_connected ? View.VISIBLE : View.GONE))
207                             holder.itemView
208                                     .findViewById(R.id.le_audio_layout)
209                                     .setVisibility(is_connected ? View.VISIBLE : View.GONE);
210                     });
211 
212             holder.itemView
213                     .findViewById(R.id.le_audio_layout)
214                     .setVisibility(
215                             le_audio_svc_data.isConnectedMutable.getValue() != null
216                                             && le_audio_svc_data.isConnectedMutable.getValue()
217                                     ? View.VISIBLE
218                                     : View.GONE);
219 
220             if (le_audio_svc_data.nodeStatusMutable.hasObservers())
221                 le_audio_svc_data.nodeStatusMutable.removeObservers(this.parent);
222             le_audio_svc_data.nodeStatusMutable.observe(
223                     this.parent,
224                     group_id_node_status_pair -> {
225                         final Integer status = group_id_node_status_pair.second;
226                         final Integer group_id = group_id_node_status_pair.first;
227 
228                         if (status == GROUP_NODE_REMOVED)
229                             holder.leAudioGroupIdText.setText(
230                                     ((Integer) BluetoothLeAudio.GROUP_ID_INVALID).toString());
231                         else holder.leAudioGroupIdText.setText(group_id.toString());
232                     });
233 
234             if (le_audio_svc_data.groupStatusMutable.hasObservers())
235                 le_audio_svc_data.groupStatusMutable.removeObservers(this.parent);
236             le_audio_svc_data.groupStatusMutable.observe(
237                     this.parent,
238                     group_id_node_status_pair -> {
239                         final Integer group_id = group_id_node_status_pair.first;
240                         final Integer status = group_id_node_status_pair.second.first;
241                         final Integer flags = group_id_node_status_pair.second.second;
242 
243                         // If our group.. actually we shouldn't get this event if it's nor ours,
244                         // right?
245                         if (holder.leAudioGroupIdText.getText().equals(group_id.toString())) {
246                             holder.leAudioGroupStatusText.setText(
247                                     status >= 0
248                                             ? this.parent.getResources()
249                                                     .getStringArray(R.array.group_statuses)[status]
250                                             : this.parent
251                                                     .getResources()
252                                                     .getString(R.string.unknown));
253                             holder.leAudioGroupFlagsText.setText(
254                                     flags > 0
255                                             ? flags.toString()
256                                             : this.parent.getResources().getString(R.string.none));
257                         }
258                     });
259 
260             if (le_audio_svc_data.groupLockStateMutable.hasObservers())
261                 le_audio_svc_data.groupLockStateMutable.removeObservers(this.parent);
262             le_audio_svc_data.groupLockStateMutable.observe(
263                     this.parent,
264                     group_id_node_status_pair -> {
265                         final Integer group_id = group_id_node_status_pair.first;
266                         final Boolean locked = group_id_node_status_pair.second;
267 
268                         // If our group.. actually we shouldn't get this event if it's nor ours,
269                         // right?
270                         if (holder.leAudioGroupIdText.getText().equals(group_id.toString())) {
271                             holder.leAudioSetLockStateText.setText(
272                                     this.parent
273                                             .getResources()
274                                             .getString(
275                                                     locked
276                                                             ? R.string.group_locked
277                                                             : R.string.group_unlocked));
278                         }
279                     });
280 
281             if (le_audio_svc_data.microphoneStateMutable.hasObservers())
282                 le_audio_svc_data.microphoneStateMutable.removeObservers(this.parent);
283             le_audio_svc_data.microphoneStateMutable.observe(
284                     this.parent,
285                     microphone_state -> {
286                         holder.leAudioGroupMicrophoneState.setText(
287                                 this.parent.getResources()
288                                         .getStringArray(R.array.mic_states)[microphone_state]);
289                         holder.leAudioGroupMicrophoneSwitch.setActivated(false);
290                     });
291         }
292     }
293 
setHasStateObservers( @onNull ViewHolder holder, LeAudioDeviceStateWrapper leAudioDeviceStateWrapper)294     private void setHasStateObservers(
295             @NonNull ViewHolder holder, LeAudioDeviceStateWrapper leAudioDeviceStateWrapper) {
296         LeAudioDeviceStateWrapper.HapData hap_svc_data = leAudioDeviceStateWrapper.hapData;
297         if (hap_svc_data != null) {
298             if (hap_svc_data.hapStateMutable.hasObservers())
299                 hap_svc_data.hapStateMutable.removeObservers(this.parent);
300             hap_svc_data.hapStateMutable.observe(
301                     this.parent,
302                     hap_state -> {
303                         holder.leAudioHapState.setText(
304                                 this.parent.getResources()
305                                         .getStringArray(R.array.profile_states)[hap_state]);
306 
307                         boolean is_connected = (hap_state == BluetoothHapClient.STATE_CONNECTED);
308                         if (is_connected != holder.hapConnectionSwitch.isChecked()) {
309                             holder.hapConnectionSwitch.setActivated(false);
310                             holder.hapConnectionSwitch.setChecked(is_connected);
311                             holder.hapConnectionSwitch.setActivated(true);
312                         }
313 
314                         if (holder.itemView.findViewById(R.id.hap_layout).getVisibility()
315                                 != (is_connected ? View.VISIBLE : View.GONE))
316                             holder.itemView
317                                     .findViewById(R.id.hap_layout)
318                                     .setVisibility(is_connected ? View.VISIBLE : View.GONE);
319                     });
320 
321             if (hap_svc_data.hapFeaturesMutable.hasObservers())
322                 hap_svc_data.hapFeaturesMutable.removeObservers(this.parent);
323 
324             hap_svc_data.hapFeaturesMutable.observe(
325                     this.parent,
326                     features -> {
327                         try {
328                             // Get hidden feature bits
329                             Field field =
330                                     BluetoothHapClient.class.getDeclaredField(
331                                             "FEATURE_TYPE_MONAURAL");
332                             field.setAccessible(true);
333                             Integer FEATURE_TYPE_MONAURAL = (Integer) field.get(null);
334 
335                             field =
336                                     BluetoothHapClient.class.getDeclaredField(
337                                             "FEATURE_TYPE_BANDED");
338                             field.setAccessible(true);
339                             Integer FEATURE_TYPE_BANDED = (Integer) field.get(null);
340 
341                             field =
342                                     BluetoothHapClient.class.getDeclaredField(
343                                             "FEATURE_SYNCHRONIZATED_PRESETS");
344                             field.setAccessible(true);
345                             Integer FEATURE_SYNCHRONIZATED_PRESETS = (Integer) field.get(null);
346 
347                             field =
348                                     BluetoothHapClient.class.getDeclaredField(
349                                             "FEATURE_INDEPENDENT_PRESETS");
350                             field.setAccessible(true);
351                             Integer FEATURE_INDEPENDENT_PRESETS = (Integer) field.get(null);
352 
353                             field =
354                                     BluetoothHapClient.class.getDeclaredField(
355                                             "FEATURE_DYNAMIC_PRESETS");
356                             field.setAccessible(true);
357                             Integer FEATURE_DYNAMIC_PRESETS = (Integer) field.get(null);
358 
359                             field =
360                                     BluetoothHapClient.class.getDeclaredField(
361                                             "FEATURE_WRITABLE_PRESETS");
362                             field.setAccessible(true);
363                             Integer FEATURE_WRITABLE_PRESETS = (Integer) field.get(null);
364 
365                             int hearing_aid_type_idx =
366                                     (features & FEATURE_TYPE_MONAURAL) != 0
367                                             ? 0
368                                             : ((features & FEATURE_TYPE_BANDED) != 0 ? 1 : 2);
369                             String hearing_aid_type =
370                                     this.parent.getResources()
371                                             .getStringArray(R.array.hearing_aid_types)[
372                                             hearing_aid_type_idx];
373                             String preset_synchronization_support =
374                                     this.parent.getResources()
375                                             .getStringArray(R.array.preset_synchronization_support)[
376                                             (features & FEATURE_SYNCHRONIZATED_PRESETS) != 0
377                                                     ? 1
378                                                     : 0];
379                             String independent_presets =
380                                     this.parent.getResources()
381                                             .getStringArray(R.array.independent_presets)[
382                                             (features & FEATURE_INDEPENDENT_PRESETS) != 0 ? 1 : 0];
383                             String dynamic_presets =
384                                     this.parent.getResources()
385                                             .getStringArray(R.array.dynamic_presets)[
386                                             (features & FEATURE_DYNAMIC_PRESETS) != 0 ? 1 : 0];
387                             String writable_presets_support =
388                                     this.parent.getResources()
389                                             .getStringArray(R.array.writable_presets_support)[
390                                             (features & FEATURE_WRITABLE_PRESETS) != 0 ? 1 : 0];
391                             holder.leAudioHapFeatures.setText(
392                                     hearing_aid_type
393                                             + " / "
394                                             + preset_synchronization_support
395                                             + " / "
396                                             + independent_presets
397                                             + " / "
398                                             + dynamic_presets
399                                             + " / "
400                                             + writable_presets_support);
401 
402                         } catch (IllegalAccessException | NoSuchFieldException e) {
403                             // Do nothing
404                             holder.leAudioHapFeatures.setText(
405                                     "Hidden API for feature fields unavailable.");
406                         }
407                     });
408 
409             if (hap_svc_data.hapPresetsMutable.hasActiveObservers())
410                 hap_svc_data.hapPresetsMutable.removeObservers(this.parent);
411             hap_svc_data.hapPresetsMutable.observe(
412                     this.parent,
413                     hapPresetsList -> {
414                         List<String> all_ids =
415                                 hapPresetsList.stream()
416                                         .map(
417                                                 info ->
418                                                         ""
419                                                                 + info.getIndex()
420                                                                 + " "
421                                                                 + info.getName()
422                                                                 + (info.isWritable()
423                                                                         ? " [wr"
424                                                                         : " [")
425                                                                 + (info.isAvailable() ? "a]" : "]"))
426                                         .collect(Collectors.toList());
427 
428                         ArrayAdapter<Integer> adapter =
429                                 new ArrayAdapter(
430                                         this.parent, android.R.layout.simple_spinner_item, all_ids);
431                         adapter.setDropDownViewResource(
432                                 android.R.layout.simple_spinner_dropdown_item);
433                         holder.leAudioHapPresetsSpinner.setAdapter(adapter);
434 
435                         if (hap_svc_data.viewsData != null) {
436                             Integer select_pos =
437                                     ((ViewHolderHapPersistentData) hap_svc_data.viewsData)
438                                             .selectedPresetPositionMutable.getValue();
439                             if (select_pos != null)
440                                 holder.leAudioHapPresetsSpinner.setSelection(select_pos);
441                         }
442                     });
443 
444             if (hap_svc_data.hapActivePresetIndexMutable.hasObservers())
445                 hap_svc_data.hapActivePresetIndexMutable.removeObservers(this.parent);
446             hap_svc_data.hapActivePresetIndexMutable.observe(
447                     this.parent,
448                     active_preset_index -> {
449                         holder.leAudioHapActivePresetIndex.setText(
450                                 String.valueOf(active_preset_index));
451                     });
452 
453             if (hap_svc_data.hapActivePresetIndexMutable.hasObservers())
454                 hap_svc_data.hapActivePresetIndexMutable.removeObservers(this.parent);
455             hap_svc_data.hapActivePresetIndexMutable.observe(
456                     this.parent,
457                     active_preset_index -> {
458                         holder.leAudioHapActivePresetIndex.setText(
459                                 String.valueOf(active_preset_index));
460                     });
461         } else {
462             holder.itemView.findViewById(R.id.hap_layout).setVisibility(View.GONE);
463         }
464     }
465 
setVolumeControlStateObservers( @onNull ViewHolder holder, LeAudioDeviceStateWrapper leAudioDeviceStateWrapper)466     private void setVolumeControlStateObservers(
467             @NonNull ViewHolder holder, LeAudioDeviceStateWrapper leAudioDeviceStateWrapper) {
468         LeAudioDeviceStateWrapper.VolumeControlData vc_svc_data =
469                 leAudioDeviceStateWrapper.volumeControlData;
470         if (vc_svc_data != null) {
471             if (vc_svc_data.isConnectedMutable.hasObservers())
472                 vc_svc_data.isConnectedMutable.removeObservers(this.parent);
473             vc_svc_data.isConnectedMutable.observe(
474                     this.parent,
475                     is_connected -> {
476                         // FIXME: How to prevent the callback from firing when we set this by code
477                         if (is_connected != holder.vcConnectionSwitch.isChecked()) {
478                             holder.vcConnectionSwitch.setActivated(false);
479                             holder.vcConnectionSwitch.setChecked(is_connected);
480                             holder.vcConnectionSwitch.setActivated(true);
481                         }
482 
483                         if (holder.itemView.findViewById(R.id.vc_layout).getVisibility()
484                                 != (is_connected ? View.VISIBLE : View.GONE))
485                             holder.itemView
486                                     .findViewById(R.id.vc_layout)
487                                     .setVisibility(is_connected ? View.VISIBLE : View.GONE);
488                     });
489 
490             holder.itemView
491                     .findViewById(R.id.vc_layout)
492                     .setVisibility(
493                             vc_svc_data.isConnectedMutable.getValue() != null
494                                             && vc_svc_data.isConnectedMutable.getValue()
495                                     ? View.VISIBLE
496                                     : View.GONE);
497 
498             if (vc_svc_data.volumeStateMutable.hasObservers())
499                 vc_svc_data.volumeStateMutable.removeObservers(this.parent);
500             vc_svc_data.volumeStateMutable.observe(
501                     this.parent,
502                     state -> {
503                         holder.volumeSeekBar.setProgress(state);
504                     });
505 
506             if (vc_svc_data.mutedStateMutable.hasObservers())
507                 vc_svc_data.mutedStateMutable.removeObservers(this.parent);
508             vc_svc_data.mutedStateMutable.observe(
509                     this.parent,
510                     state -> {
511                         holder.muteSwitch.setActivated(false);
512                         holder.muteSwitch.setChecked(state);
513                         holder.muteSwitch.setActivated(true);
514                     });
515 
516             if (vc_svc_data.numInputsMutable.hasObservers())
517                 vc_svc_data.numInputsMutable.removeObservers(this.parent);
518             vc_svc_data.numInputsMutable.observe(
519                     this.parent,
520                     num_inputs -> {
521                         List<Integer> range = new ArrayList<>();
522                         if (num_inputs != 0)
523                             range =
524                                     IntStream.rangeClosed(1, num_inputs)
525                                             .boxed()
526                                             .collect(Collectors.toList());
527                         ArrayAdapter<Integer> adapter =
528                                 new ArrayAdapter(
529                                         this.parent, android.R.layout.simple_spinner_item, range);
530                         adapter.setDropDownViewResource(
531                                 android.R.layout.simple_spinner_dropdown_item);
532                         holder.inputIdxSpinner.setAdapter(adapter);
533                     });
534 
535             if (vc_svc_data.viewsData != null) {
536                 Integer select_pos =
537                         ((ViewHolderVcPersistentData) vc_svc_data.viewsData).selectedInputPosition;
538                 if (select_pos != null) holder.inputIdxSpinner.setSelection(select_pos);
539             }
540 
541             if (vc_svc_data.inputDescriptionsMutable.hasObservers())
542                 vc_svc_data.inputDescriptionsMutable.removeObservers(this.parent);
543             vc_svc_data.inputDescriptionsMutable.observe(
544                     this.parent,
545                     integerStringMap -> {
546                         if (holder.inputIdxSpinner.getSelectedItem() != null) {
547                             Integer input_id =
548                                     Integer.valueOf(
549                                             holder.inputIdxSpinner.getSelectedItem().toString());
550                             holder.inputDescriptionText.setText(
551                                     integerStringMap.getOrDefault(input_id, ""));
552                         }
553                     });
554 
555             if (vc_svc_data.inputStateGainMutable.hasObservers())
556                 vc_svc_data.inputStateGainMutable.removeObservers(this.parent);
557             vc_svc_data.inputStateGainMutable.observe(
558                     this.parent,
559                     integerIntegerMap -> {
560                         if (holder.inputIdxSpinner.getSelectedItem() != null) {
561                             Integer input_id =
562                                     Integer.valueOf(
563                                             holder.inputIdxSpinner.getSelectedItem().toString());
564                             holder.inputGainSeekBar.setProgress(
565                                     integerIntegerMap.getOrDefault(input_id, 0));
566                         }
567                     });
568 
569             if (vc_svc_data.inputStateGainModeMutable.hasObservers())
570                 vc_svc_data.inputStateGainModeMutable.removeObservers(this.parent);
571             vc_svc_data.inputStateGainModeMutable.observe(
572                     this.parent,
573                     integerIntegerMap -> {
574                         if (holder.inputIdxSpinner.getSelectedItem() != null) {
575                             Integer input_id =
576                                     Integer.valueOf(
577                                             holder.inputIdxSpinner.getSelectedItem().toString());
578                             holder.inputGainModeText.setText(
579                                     this.parent.getResources()
580                                             .getStringArray(R.array.gain_modes)[
581                                             integerIntegerMap.getOrDefault(input_id, 1)]);
582                         }
583                     });
584 
585             if (vc_svc_data.inputStateGainUnitMutable.hasObservers())
586                 vc_svc_data.inputStateGainUnitMutable.removeObservers(this.parent);
587             vc_svc_data.inputStateGainUnitMutable.observe(
588                     this.parent,
589                     integerIntegerMap -> {
590                         if (holder.inputIdxSpinner.getSelectedItem() != null) {
591                             // TODO: Use string map with units instead of plain numbers
592                             Integer input_id =
593                                     Integer.valueOf(
594                                             holder.inputIdxSpinner.getSelectedItem().toString());
595                             holder.inputGainPropsUnitText.setText(
596                                     integerIntegerMap.getOrDefault(input_id, 0).toString());
597                         }
598                     });
599 
600             if (vc_svc_data.inputStateGainMinMutable.hasObservers())
601                 vc_svc_data.inputStateGainMinMutable.removeObservers(this.parent);
602             vc_svc_data.inputStateGainMinMutable.observe(
603                     this.parent,
604                     integerIntegerMap -> {
605                         if (holder.inputIdxSpinner.getSelectedItem() != null) {
606                             Integer input_id =
607                                     Integer.valueOf(
608                                             holder.inputIdxSpinner.getSelectedItem().toString());
609                             holder.inputGainPropsMinText.setText(
610                                     integerIntegerMap.getOrDefault(input_id, 0).toString());
611                             holder.inputGainSeekBar.setMin(
612                                     integerIntegerMap.getOrDefault(input_id, -255));
613                         }
614                     });
615 
616             if (vc_svc_data.inputStateGainMaxMutable.hasObservers())
617                 vc_svc_data.inputStateGainMaxMutable.removeObservers(this.parent);
618             vc_svc_data.inputStateGainMaxMutable.observe(
619                     this.parent,
620                     integerIntegerMap -> {
621                         if (holder.inputIdxSpinner.getSelectedItem() != null) {
622                             Integer input_id =
623                                     Integer.valueOf(
624                                             holder.inputIdxSpinner.getSelectedItem().toString());
625                             holder.inputGainPropsMaxText.setText(
626                                     integerIntegerMap.getOrDefault(input_id, 0).toString());
627                             holder.inputGainSeekBar.setMax(
628                                     integerIntegerMap.getOrDefault(input_id, 255));
629                         }
630                     });
631 
632             if (vc_svc_data.inputStateMuteMutable.hasObservers())
633                 vc_svc_data.inputStateMuteMutable.removeObservers(this.parent);
634             vc_svc_data.inputStateMuteMutable.observe(
635                     this.parent,
636                     integerIntegerMap -> {
637                         if (holder.inputIdxSpinner.getSelectedItem() != null) {
638                             Integer input_id =
639                                     Integer.valueOf(
640                                             holder.inputIdxSpinner.getSelectedItem().toString());
641                             holder.inputMuteSwitch.setActivated(false);
642                             holder.inputMuteSwitch.setChecked(
643                                     integerIntegerMap.getOrDefault(input_id, false));
644                             holder.inputMuteSwitch.setActivated(true);
645                         }
646                     });
647 
648             if (vc_svc_data.inputStatusMutable.hasObservers())
649                 vc_svc_data.inputStatusMutable.removeObservers(this.parent);
650             vc_svc_data.inputStatusMutable.observe(
651                     this.parent,
652                     integerIntegerMap -> {
653                         if (holder.inputIdxSpinner.getSelectedItem() != null) {
654                             Integer input_id =
655                                     Integer.valueOf(
656                                             holder.inputIdxSpinner.getSelectedItem().toString());
657                             // TODO: Use string map with units instead of plain numbers
658                             holder.inputStatusText.setText(
659                                     integerIntegerMap.getOrDefault(input_id, -1).toString());
660                         }
661                     });
662 
663             if (vc_svc_data.inputTypeMutable.hasObservers())
664                 vc_svc_data.inputTypeMutable.removeObservers(this.parent);
665             vc_svc_data.inputTypeMutable.observe(
666                     this.parent,
667                     integerIntegerMap -> {
668                         if (holder.inputIdxSpinner.getSelectedItem() != null) {
669                             Integer input_id =
670                                     Integer.valueOf(
671                                             holder.inputIdxSpinner.getSelectedItem().toString());
672                             // TODO: Use string map with units instead of plain numbers
673                             holder.inputTypeText.setText(
674                                     integerIntegerMap.getOrDefault(input_id, -1).toString());
675                         }
676                     });
677 
678             vc_svc_data.numOffsetsMutable.observe(
679                     this.parent,
680                     num_offsets -> {
681                         List<Integer> range = new ArrayList<>();
682                         if (num_offsets != 0)
683                             range =
684                                     IntStream.rangeClosed(1, num_offsets)
685                                             .boxed()
686                                             .collect(Collectors.toList());
687                         ArrayAdapter<Integer> adapter =
688                                 new ArrayAdapter(
689                                         this.parent, android.R.layout.simple_spinner_item, range);
690                         adapter.setDropDownViewResource(
691                                 android.R.layout.simple_spinner_dropdown_item);
692                         holder.outputIdxSpinner.setAdapter(adapter);
693                     });
694 
695             if (vc_svc_data.viewsData != null) {
696                 Integer select_pos =
697                         ((ViewHolderVcPersistentData) vc_svc_data.viewsData).selectedOutputPosition;
698                 if (select_pos != null) holder.outputIdxSpinner.setSelection(select_pos);
699             }
700 
701             if (vc_svc_data.outputVolumeOffsetMutable.hasObservers())
702                 vc_svc_data.outputVolumeOffsetMutable.removeObservers(this.parent);
703             vc_svc_data.outputVolumeOffsetMutable.observe(
704                     this.parent,
705                     integerIntegerMap -> {
706                         if (holder.outputIdxSpinner.getSelectedItem() != null) {
707                             Integer output_id =
708                                     Integer.valueOf(
709                                             holder.outputIdxSpinner.getSelectedItem().toString());
710                             holder.outputGainOffsetSeekBar.setProgress(
711                                     integerIntegerMap.getOrDefault(output_id, 0));
712                         }
713                     });
714 
715             if (vc_svc_data.outputLocationMutable.hasObservers())
716                 vc_svc_data.outputLocationMutable.removeObservers(this.parent);
717             vc_svc_data.outputLocationMutable.observe(
718                     this.parent,
719                     integerIntegerMap -> {
720                         if (holder.outputIdxSpinner.getSelectedItem() != null) {
721                             Integer output_id =
722                                     Integer.valueOf(
723                                             holder.outputIdxSpinner.getSelectedItem().toString());
724                             holder.outputLocationText.setText(
725                                     this.parent.getResources()
726                                             .getStringArray(R.array.audio_locations)[
727                                             integerIntegerMap.getOrDefault(output_id, 0)]);
728                         }
729                     });
730 
731             if (vc_svc_data.outputDescriptionMutable.hasObservers())
732                 vc_svc_data.outputDescriptionMutable.removeObservers(this.parent);
733             vc_svc_data.outputDescriptionMutable.observe(
734                     this.parent,
735                     integerStringMap -> {
736                         if (holder.outputIdxSpinner.getSelectedItem() != null) {
737                             Integer output_id =
738                                     Integer.valueOf(
739                                             holder.outputIdxSpinner.getSelectedItem().toString());
740                             holder.outputDescriptionText.setText(
741                                     integerStringMap.getOrDefault(output_id, "no description"));
742                         }
743                     });
744         } else {
745             holder.itemView.findViewById(R.id.vc_layout).setVisibility(View.GONE);
746         }
747     }
748 
setVolumeControlUiStateObservers( @onNull ViewHolder holder, LeAudioDeviceStateWrapper leAudioDeviceStateWrapper)749     private void setVolumeControlUiStateObservers(
750             @NonNull ViewHolder holder, LeAudioDeviceStateWrapper leAudioDeviceStateWrapper) {
751         if (leAudioDeviceStateWrapper.volumeControlData == null) return;
752 
753         ViewHolderVcPersistentData vData =
754                 (ViewHolderVcPersistentData) leAudioDeviceStateWrapper.volumeControlData.viewsData;
755         if (vData == null) return;
756 
757         if (vData.isInputsCollapsedMutable.hasObservers())
758             vData.isInputsCollapsedMutable.removeObservers(this.parent);
759         vData.isInputsCollapsedMutable.observe(
760                 this.parent,
761                 aBoolean -> {
762                     Float rbegin = aBoolean ? 0.0f : 180.0f;
763                     Float rend = aBoolean ? 180.0f : 0.0f;
764 
765                     ObjectAnimator.ofFloat(holder.inputFoldableIcon, "rotation", rbegin, rend)
766                             .setDuration(300)
767                             .start();
768                     holder.inputFoldable.setVisibility(aBoolean ? View.GONE : View.VISIBLE);
769                 });
770         vData.isInputsCollapsedMutable.setValue(holder.inputFoldable.getVisibility() == View.GONE);
771 
772         if (vData.isOutputsCollapsedMutable.hasObservers())
773             vData.isOutputsCollapsedMutable.removeObservers(this.parent);
774         vData.isOutputsCollapsedMutable.observe(
775                 this.parent,
776                 aBoolean -> {
777                     Float rbegin = aBoolean ? 0.0f : 180.0f;
778                     Float rend = aBoolean ? 180.0f : 0.0f;
779 
780                     ObjectAnimator.ofFloat(holder.outputFoldableIcon, "rotation", rbegin, rend)
781                             .setDuration(300)
782                             .start();
783                     holder.outputFoldable.setVisibility(aBoolean ? View.GONE : View.VISIBLE);
784                 });
785         vData.isOutputsCollapsedMutable.setValue(
786                 holder.outputFoldable.getVisibility() == View.GONE);
787     }
788 
setBassStateObservers( @onNull ViewHolder holder, LeAudioDeviceStateWrapper leAudioDeviceStateWrapper)789     private void setBassStateObservers(
790             @NonNull ViewHolder holder, LeAudioDeviceStateWrapper leAudioDeviceStateWrapper) {
791         LeAudioDeviceStateWrapper.BassData bass_svc_data = leAudioDeviceStateWrapper.bassData;
792         if (bass_svc_data != null) {
793             if (bass_svc_data.isConnectedMutable.hasObservers())
794                 bass_svc_data.isConnectedMutable.removeObservers(this.parent);
795             bass_svc_data.isConnectedMutable.observe(
796                     this.parent,
797                     is_connected -> {
798                         // FIXME: How to prevent the callback from firing when we set this by code
799                         if (is_connected != holder.bassConnectionSwitch.isChecked()) {
800                             holder.bassConnectionSwitch.setActivated(false);
801                             holder.bassConnectionSwitch.setChecked(is_connected);
802                             holder.bassConnectionSwitch.setActivated(true);
803                         }
804 
805                         if (holder.itemView.findViewById(R.id.bass_layout).getVisibility()
806                                 != (is_connected ? View.VISIBLE : View.GONE))
807                             holder.itemView
808                                     .findViewById(R.id.bass_layout)
809                                     .setVisibility(is_connected ? View.VISIBLE : View.GONE);
810                     });
811 
812             holder.itemView
813                     .findViewById(R.id.bass_layout)
814                     .setVisibility(
815                             bass_svc_data.isConnectedMutable.getValue() != null
816                                             && bass_svc_data.isConnectedMutable.getValue()
817                                     ? View.VISIBLE
818                                     : View.GONE);
819 
820             if (bass_svc_data.receiverStatesMutable.hasActiveObservers())
821                 bass_svc_data.receiverStatesMutable.removeObservers(this.parent);
822             bass_svc_data.receiverStatesMutable.observe(
823                     this.parent,
824                     integerReceiverStateHashMap -> {
825                         List<Integer> all_ids =
826                                 integerReceiverStateHashMap.entrySet().stream()
827                                         .map(Map.Entry::getKey)
828                                         .collect(Collectors.toList());
829 
830                         ArrayAdapter<Integer> adapter =
831                                 new ArrayAdapter(
832                                         this.parent, android.R.layout.simple_spinner_item, all_ids);
833                         adapter.setDropDownViewResource(
834                                 android.R.layout.simple_spinner_dropdown_item);
835                         holder.bassReceiverIdSpinner.setAdapter(adapter);
836 
837                         if (bass_svc_data.viewsData != null) {
838                             Integer select_pos =
839                                     ((ViewHolderBassPersistentData) bass_svc_data.viewsData)
840                                             .selectedReceiverPositionMutable.getValue();
841                             if (select_pos != null)
842                                 holder.bassReceiverIdSpinner.setSelection(select_pos);
843                         }
844                     });
845         } else {
846             holder.itemView.findViewById(R.id.bass_layout).setVisibility(View.GONE);
847         }
848     }
849 
setBassUiStateObservers( @onNull ViewHolder holder, LeAudioDeviceStateWrapper leAudioDeviceStateWrapper)850     private void setBassUiStateObservers(
851             @NonNull ViewHolder holder, LeAudioDeviceStateWrapper leAudioDeviceStateWrapper) {
852         if (leAudioDeviceStateWrapper.bassData == null) return;
853 
854         ViewHolderBassPersistentData vData =
855                 (ViewHolderBassPersistentData) leAudioDeviceStateWrapper.bassData.viewsData;
856         if (vData == null) return;
857 
858         if (vData.selectedReceiverPositionMutable.hasObservers())
859             vData.selectedReceiverPositionMutable.removeObservers(this.parent);
860 
861         vData.selectedReceiverPositionMutable.observe(
862                 this.parent,
863                 aInteger -> {
864                     int receiver_id =
865                             Integer.parseInt(
866                                     holder.bassReceiverIdSpinner
867                                             .getItemAtPosition(aInteger)
868                                             .toString());
869                     bassInteractionListener.onReceiverSelected(
870                             leAudioDeviceStateWrapper, receiver_id);
871 
872                     Map<Integer, BluetoothLeBroadcastReceiveState> states =
873                             leAudioDeviceStateWrapper.bassData.receiverStatesMutable.getValue();
874 
875                     Log.d(
876                             "LeAudioRecycleViewAdapter",
877                             "BluetoothLeBroadcastReceiveState "
878                                     + holder.bassReceiverIdSpinner.getSelectedItem());
879                     if (states != null) {
880                         if (states.containsKey(receiver_id)) {
881                             BluetoothLeBroadcastReceiveState state =
882                                     states.get(holder.bassReceiverIdSpinner.getSelectedItem());
883                             int paSyncState = state.getPaSyncState();
884                             int bigEncryptionState = state.getBigEncryptionState();
885                             long bisSyncState = 0;
886 
887                             if (state.getNumSubgroups() == 1) {
888                                 bisSyncState = state.getBisSyncState().get(0);
889                             } else if (state.getNumSubgroups() > 1) {
890                                 // TODO: Add multiple subgroup support
891                                 Log.w(
892                                         "LeAudioRecycleViewAdapter",
893                                         "There is more than one subgroup in " + "BIG");
894                                 bisSyncState = state.getBisSyncState().get(0);
895                             }
896 
897                             Resources res = this.parent.getResources();
898                             String paStateStr = null;
899                             String encStateStr = null;
900                             String bisSyncStateStr = null;
901 
902                             if (paSyncState == 0xffff) { // invalid sync state
903                                 paSyncState = PA_SYNC_STATE_IDLE;
904                             }
905                             if (bigEncryptionState == 0xffff) { // invalid encryption state
906                                 bigEncryptionState = BIG_ENCRYPTION_STATE_NOT_ENCRYPTED;
907                             }
908                             Log.d(
909                                     "LeAudioRecycleViewAdapter",
910                                     "paSyncState "
911                                             + paSyncState
912                                             + " bigEncryptionState"
913                                             + bigEncryptionState);
914 
915                             /* PA Sync state */
916                             if (paSyncState == PA_SYNC_STATE_IDLE) {
917                                 holder.bassScanButton.setImageResource(
918                                         R.drawable.ic_cast_black_24dp);
919                                 paStateStr =
920                                         res.getString(R.string.broadcast_pa_sync_state_pa_not_sync);
921                             } else if (paSyncState == PA_SYNC_STATE_SYNCINFO_REQUEST) {
922                                 paStateStr =
923                                         res.getString(
924                                                 R.string.broadcast_pa_sync_state_syncinfo_req);
925                             } else if (paSyncState == PA_SYNC_STATE_SYNCHRONIZED) {
926                                 paStateStr =
927                                         res.getString(R.string.broadcast_pa_sync_state_pa_sync);
928                             } else if (paSyncState == PA_SYNC_STATE_FAILED_TO_SYNCHRONIZE) {
929                                 holder.bassScanButton.setImageResource(
930                                         R.drawable.ic_warning_black_24dp);
931                                 paStateStr =
932                                         res.getString(
933                                                 R.string.broadcast_pa_sync_state_sync_pa_failed);
934                             } else if (paSyncState == PA_SYNC_STATE_NO_PAST) {
935                                 holder.bassScanButton.setImageResource(
936                                         R.drawable.ic_warning_black_24dp);
937                                 paStateStr =
938                                         res.getString(R.string.broadcast_pa_sync_state_no_past);
939                             } else {
940                                 holder.bassScanButton.setImageResource(
941                                         R.drawable.ic_warning_black_24dp);
942                                 paStateStr = res.getString(R.string.broadcast_state_rfu);
943                             }
944 
945                             /* ENC state */
946                             if (bigEncryptionState == BIG_ENCRYPTION_STATE_NOT_ENCRYPTED) {
947                                 encStateStr =
948                                         res.getString(R.string.broadcast_big_encryption_not_enc);
949                             } else if (bigEncryptionState == BIG_ENCRYPTION_STATE_CODE_REQUIRED) {
950                                 holder.bassScanButton.setImageResource(
951                                         R.drawable.ic_vpn_key_black_24dp);
952                                 encStateStr =
953                                         res.getString(
954                                                 R.string.broadcast_big_encryption_code_required);
955                             } else if (bigEncryptionState == BIG_ENCRYPTION_STATE_DECRYPTING) {
956                                 encStateStr =
957                                         res.getString(R.string.broadcast_big_encryption_decrypting);
958                             } else if (bigEncryptionState == BIG_ENCRYPTION_STATE_BAD_CODE) {
959                                 holder.bassScanButton.setImageResource(
960                                         R.drawable.ic_warning_black_24dp);
961                                 encStateStr =
962                                         res.getString(R.string.broadcast_big_encryption_bad_code);
963                             } else {
964                                 encStateStr = res.getString(R.string.broadcast_state_rfu);
965                             }
966 
967                             /* BIS state */
968                             if (bisSyncState == 0x00000000) {
969                                 bisSyncStateStr =
970                                         res.getString(
971                                                 R.string.broadcast_bis_sync_state_bis_not_sync);
972                             } else if (bisSyncState == 0xffffffff) {
973                                 bisSyncStateStr =
974                                         res.getString(
975                                                 R.string.broadcast_bis_sync_state_bis_not_sync);
976                             } else {
977                                 holder.bassScanButton.setImageResource(
978                                         R.drawable.ic_bluetooth_searching_black_24dp);
979                                 bisSyncStateStr =
980                                         res.getString(R.string.broadcast_bis_sync_state_bis_sync);
981                             }
982 
983                             holder.bassReceiverPaStateText.setText(paStateStr);
984                             holder.bassReceiverEncStateText.setText(encStateStr);
985                             holder.bassReceiverBisStateText.setText(bisSyncStateStr);
986                         }
987                     }
988                 });
989     }
990 
991     @Override
getItemId(int position)992     public long getItemId(int position) {
993         return devices.get(position).device.getAddress().hashCode();
994     }
995 
996     @Override
getItemCount()997     public int getItemCount() {
998         return devices != null ? devices.size() : 0;
999     }
1000 
1001     // Listeners registration routines
1002     // -------------------------------
setOnItemClickListener(@ullable OnItemClickListener listener)1003     public void setOnItemClickListener(@Nullable OnItemClickListener listener) {
1004         this.clickListener = listener;
1005     }
1006 
setOnLeAudioInteractionListener(@ullable OnLeAudioInteractionListener listener)1007     public void setOnLeAudioInteractionListener(@Nullable OnLeAudioInteractionListener listener) {
1008         this.leAudioInteractionListener = listener;
1009     }
1010 
setOnVolumeControlInteractionListener( @ullable OnVolumeControlInteractionListener listener)1011     public void setOnVolumeControlInteractionListener(
1012             @Nullable OnVolumeControlInteractionListener listener) {
1013         this.volumeControlInteractionListener = listener;
1014     }
1015 
setOnBassInteractionListener(@ullable OnBassInteractionListener listener)1016     public void setOnBassInteractionListener(@Nullable OnBassInteractionListener listener) {
1017         this.bassInteractionListener = listener;
1018     }
1019 
setOnHapInteractionListener(@ullable OnHapInteractionListener listener)1020     public void setOnHapInteractionListener(@Nullable OnHapInteractionListener listener) {
1021         this.hapInteractionListener = listener;
1022     }
1023 
setOnGattBrConnectListener(@ullable OnGattBrListener listener)1024     public void setOnGattBrConnectListener(@Nullable OnGattBrListener listener) {
1025         this.gattBrListener = listener;
1026     }
1027 
1028     // Device list update routine
1029     // -----------------------------
updateLeAudioDeviceList(@ullable List<LeAudioDeviceStateWrapper> devices)1030     public void updateLeAudioDeviceList(@Nullable List<LeAudioDeviceStateWrapper> devices) {
1031         this.devices.clear();
1032         this.devices.addAll(devices);
1033 
1034         // FIXME: Is this the right way of doing it?
1035         for (LeAudioDeviceStateWrapper dev_state : this.devices) {
1036             if (dev_state.volumeControlData != null)
1037                 if (dev_state.volumeControlData.viewsData == null)
1038                     dev_state.volumeControlData.viewsData = new ViewHolderVcPersistentData();
1039             if (dev_state.bassData != null)
1040                 if (dev_state.bassData.viewsData == null)
1041                     dev_state.bassData.viewsData = new ViewHolderBassPersistentData();
1042             if (dev_state.leAudioData != null)
1043                 if (dev_state.leAudioData.viewsData == null)
1044                     dev_state.leAudioData.viewsData = new ViewHolderHapPersistentData();
1045         }
1046 
1047         notifyDataSetChanged();
1048     }
1049 
1050     public interface OnItemClickListener {
onItemClick(LeAudioDeviceStateWrapper leAudioDeviceStateWrapper)1051         void onItemClick(LeAudioDeviceStateWrapper leAudioDeviceStateWrapper);
1052     }
1053 
1054     public interface OnLeAudioInteractionListener {
onConnectClick(LeAudioDeviceStateWrapper leAudioDeviceStateWrapper)1055         void onConnectClick(LeAudioDeviceStateWrapper leAudioDeviceStateWrapper);
1056 
onDisconnectClick(LeAudioDeviceStateWrapper leAudioDeviceStateWrapper)1057         void onDisconnectClick(LeAudioDeviceStateWrapper leAudioDeviceStateWrapper);
1058 
onStreamActionClicked( LeAudioDeviceStateWrapper leAudioDeviceStateWrapper, Integer group_id, Integer content_type, Integer action)1059         void onStreamActionClicked(
1060                 LeAudioDeviceStateWrapper leAudioDeviceStateWrapper,
1061                 Integer group_id,
1062                 Integer content_type,
1063                 Integer action);
1064 
onGroupSetClicked( LeAudioDeviceStateWrapper leAudioDeviceStateWrapper, Integer group_id)1065         void onGroupSetClicked(
1066                 LeAudioDeviceStateWrapper leAudioDeviceStateWrapper, Integer group_id);
1067 
onGroupUnsetClicked( LeAudioDeviceStateWrapper leAudioDeviceStateWrapper, Integer group_id)1068         void onGroupUnsetClicked(
1069                 LeAudioDeviceStateWrapper leAudioDeviceStateWrapper, Integer group_id);
1070 
onGroupDestroyClicked( LeAudioDeviceStateWrapper leAudioDeviceStateWrapper, Integer group_id)1071         void onGroupDestroyClicked(
1072                 LeAudioDeviceStateWrapper leAudioDeviceStateWrapper, Integer group_id);
1073 
onGroupSetLockClicked( LeAudioDeviceStateWrapper leAudioDeviceStateWrapper, Integer group_id, boolean lock)1074         void onGroupSetLockClicked(
1075                 LeAudioDeviceStateWrapper leAudioDeviceStateWrapper,
1076                 Integer group_id,
1077                 boolean lock);
1078 
onMicrophoneMuteChanged( LeAudioDeviceStateWrapper leAudioDeviceStateWrapper, boolean mute, boolean is_from_user)1079         void onMicrophoneMuteChanged(
1080                 LeAudioDeviceStateWrapper leAudioDeviceStateWrapper,
1081                 boolean mute,
1082                 boolean is_from_user);
1083     }
1084 
1085     public interface OnVolumeControlInteractionListener {
onConnectClick(LeAudioDeviceStateWrapper leAudioDeviceStateWrapper)1086         void onConnectClick(LeAudioDeviceStateWrapper leAudioDeviceStateWrapper);
1087 
onDisconnectClick(LeAudioDeviceStateWrapper leAudioDeviceStateWrapper)1088         void onDisconnectClick(LeAudioDeviceStateWrapper leAudioDeviceStateWrapper);
1089 
onVolumeChanged( LeAudioDeviceStateWrapper leAudioDeviceStateWrapper, int value, boolean is_from_user)1090         void onVolumeChanged(
1091                 LeAudioDeviceStateWrapper leAudioDeviceStateWrapper,
1092                 int value,
1093                 boolean is_from_user);
1094 
onCheckedChanged( LeAudioDeviceStateWrapper leAudioDeviceStateWrapper, boolean is_checked)1095         void onCheckedChanged(
1096                 LeAudioDeviceStateWrapper leAudioDeviceStateWrapper, boolean is_checked);
1097 
onInputGetStateButtonClicked( LeAudioDeviceStateWrapper leAudioDeviceStateWrapper, int input_id)1098         void onInputGetStateButtonClicked(
1099                 LeAudioDeviceStateWrapper leAudioDeviceStateWrapper, int input_id);
1100 
onInputGainValueChanged( LeAudioDeviceStateWrapper leAudioDeviceStateWrapper, int input_id, int value)1101         void onInputGainValueChanged(
1102                 LeAudioDeviceStateWrapper leAudioDeviceStateWrapper, int input_id, int value);
1103 
onInputMuteSwitched( LeAudioDeviceStateWrapper leAudioDeviceStateWrapper, int input_id, boolean is_muted)1104         void onInputMuteSwitched(
1105                 LeAudioDeviceStateWrapper leAudioDeviceStateWrapper,
1106                 int input_id,
1107                 boolean is_muted);
1108 
onInputSetGainModeButtonClicked( LeAudioDeviceStateWrapper leAudioDeviceStateWrapper, int input_id, boolean is_auto)1109         void onInputSetGainModeButtonClicked(
1110                 LeAudioDeviceStateWrapper leAudioDeviceStateWrapper, int input_id, boolean is_auto);
1111 
onInputGetGainPropsButtonClicked( LeAudioDeviceStateWrapper leAudioDeviceStateWrapper, int input_id)1112         void onInputGetGainPropsButtonClicked(
1113                 LeAudioDeviceStateWrapper leAudioDeviceStateWrapper, int input_id);
1114 
onInputGetTypeButtonClicked( LeAudioDeviceStateWrapper leAudioDeviceStateWrapper, int input_id)1115         void onInputGetTypeButtonClicked(
1116                 LeAudioDeviceStateWrapper leAudioDeviceStateWrapper, int input_id);
1117 
onInputGetStatusButton( LeAudioDeviceStateWrapper leAudioDeviceStateWrapper, int input_id)1118         void onInputGetStatusButton(
1119                 LeAudioDeviceStateWrapper leAudioDeviceStateWrapper, int input_id);
1120 
onInputGetDescriptionButtonClicked( LeAudioDeviceStateWrapper leAudioDeviceStateWrapper, int input_id)1121         void onInputGetDescriptionButtonClicked(
1122                 LeAudioDeviceStateWrapper leAudioDeviceStateWrapper, int input_id);
1123 
onInputSetDescriptionButtonClicked( LeAudioDeviceStateWrapper leAudioDeviceStateWrapper, int input_id, String description)1124         void onInputSetDescriptionButtonClicked(
1125                 LeAudioDeviceStateWrapper leAudioDeviceStateWrapper,
1126                 int input_id,
1127                 String description);
1128 
onOutputGetGainButtonClicked( LeAudioDeviceStateWrapper leAudioDeviceStateWrapper, int output_id)1129         void onOutputGetGainButtonClicked(
1130                 LeAudioDeviceStateWrapper leAudioDeviceStateWrapper, int output_id);
1131 
onOutputGainOffsetGainValueChanged( LeAudioDeviceStateWrapper leAudioDeviceStateWrapper, int output_id, int value)1132         void onOutputGainOffsetGainValueChanged(
1133                 LeAudioDeviceStateWrapper leAudioDeviceStateWrapper, int output_id, int value);
1134 
onOutputGetLocationButtonClicked( LeAudioDeviceStateWrapper leAudioDeviceStateWrapper, int output_id)1135         void onOutputGetLocationButtonClicked(
1136                 LeAudioDeviceStateWrapper leAudioDeviceStateWrapper, int output_id);
1137 
onOutputSetLocationButtonClicked( LeAudioDeviceStateWrapper leAudioDeviceStateWrapper, int output_id, int location)1138         void onOutputSetLocationButtonClicked(
1139                 LeAudioDeviceStateWrapper leAudioDeviceStateWrapper, int output_id, int location);
1140 
onOutputGetDescriptionButtonClicked( LeAudioDeviceStateWrapper leAudioDeviceStateWrapper, int output_id)1141         void onOutputGetDescriptionButtonClicked(
1142                 LeAudioDeviceStateWrapper leAudioDeviceStateWrapper, int output_id);
1143 
onOutputSetDescriptionButton( LeAudioDeviceStateWrapper leAudioDeviceStateWrapper, int output_id, String description)1144         void onOutputSetDescriptionButton(
1145                 LeAudioDeviceStateWrapper leAudioDeviceStateWrapper,
1146                 int output_id,
1147                 String description);
1148     }
1149 
1150     public interface OnGattBrListener {
onToggleSwitch(LeAudioDeviceStateWrapper device, boolean switchState)1151         void onToggleSwitch(LeAudioDeviceStateWrapper device, boolean switchState);
1152     }
1153 
1154     public interface OnHapInteractionListener {
onConnectClick(LeAudioDeviceStateWrapper leAudioDeviceStateWrapper)1155         void onConnectClick(LeAudioDeviceStateWrapper leAudioDeviceStateWrapper);
1156 
onDisconnectClick(LeAudioDeviceStateWrapper leAudioDeviceStateWrapper)1157         void onDisconnectClick(LeAudioDeviceStateWrapper leAudioDeviceStateWrapper);
1158 
onChangePresetNameClicked(BluetoothDevice device, int preset_index, String name)1159         void onChangePresetNameClicked(BluetoothDevice device, int preset_index, String name);
1160 
onReadPresetInfoClicked(BluetoothDevice device, int preset_index)1161         void onReadPresetInfoClicked(BluetoothDevice device, int preset_index);
1162 
onSetActivePresetClicked(BluetoothDevice device, int preset_index)1163         void onSetActivePresetClicked(BluetoothDevice device, int preset_index);
1164 
onSetActivePresetForGroupClicked(BluetoothDevice device, int preset_index)1165         void onSetActivePresetForGroupClicked(BluetoothDevice device, int preset_index);
1166 
onNextDevicePresetClicked(BluetoothDevice device)1167         void onNextDevicePresetClicked(BluetoothDevice device);
1168 
onPreviousDevicePresetClicked(BluetoothDevice device)1169         void onPreviousDevicePresetClicked(BluetoothDevice device);
1170 
onNextGroupPresetClicked(BluetoothDevice device)1171         void onNextGroupPresetClicked(BluetoothDevice device);
1172 
onPreviousGroupPresetClicked(BluetoothDevice device)1173         void onPreviousGroupPresetClicked(BluetoothDevice device);
1174     }
1175 
1176     public interface OnBassInteractionListener {
onConnectClick(LeAudioDeviceStateWrapper leAudioDeviceStateWrapper)1177         void onConnectClick(LeAudioDeviceStateWrapper leAudioDeviceStateWrapper);
1178 
onDisconnectClick(LeAudioDeviceStateWrapper leAudioDeviceStateWrapper)1179         void onDisconnectClick(LeAudioDeviceStateWrapper leAudioDeviceStateWrapper);
1180 
onReceiverSelected( LeAudioDeviceStateWrapper leAudioDeviceStateWrapper, int receiver_id)1181         void onReceiverSelected(
1182                 LeAudioDeviceStateWrapper leAudioDeviceStateWrapper, int receiver_id);
1183 
onBroadcastCodeEntered(BluetoothDevice device, int receiver_id, byte[] broadcast_code)1184         void onBroadcastCodeEntered(BluetoothDevice device, int receiver_id, byte[] broadcast_code);
1185 
onStopSyncReq(BluetoothDevice device, int receiver_id)1186         void onStopSyncReq(BluetoothDevice device, int receiver_id);
1187 
onRemoveSourceReq(BluetoothDevice device, int receiver_id)1188         void onRemoveSourceReq(BluetoothDevice device, int receiver_id);
1189 
onStopObserving()1190         void onStopObserving();
1191     }
1192 
1193     public class ViewHolder extends RecyclerView.ViewHolder {
1194         private final TextView deviceName;
1195 
1196         // Le Audio View stuff
1197         private Switch leAudioConnectionSwitch;
1198         private Button leAudioStartStreamButton;
1199         private Button leAudioStopStreamButton;
1200         private Button leAudioSuspendStreamButton;
1201         private Button leAudioGroupSetButton;
1202         private Button leAudioGroupUnsetButton;
1203         private Button leAudioGroupDestroyButton;
1204         private TextView leAudioGroupIdText;
1205         private TextView leAudioGroupStatusText;
1206         private TextView leAudioGroupFlagsText;
1207 
1208         // Iso Set stuff
1209         private Button leAudioSetLockButton;
1210         private Button leAudioSetUnlockButton;
1211         private TextView leAudioSetLockStateText;
1212 
1213         // LeAudio Microphone stuff
1214         private Switch leAudioGroupMicrophoneSwitch;
1215         private TextView leAudioGroupMicrophoneState;
1216 
1217         // LeAudio HAP stuff
1218         private Switch hapConnectionSwitch;
1219         private Switch gattBrConnectionSwitch;
1220         private TextView leAudioHapState;
1221         private TextView leAudioHapFeatures;
1222         private TextView leAudioHapActivePresetIndex;
1223         private Spinner leAudioHapPresetsSpinner;
1224         private Button leAudioHapChangePresetNameButton;
1225         private Button leAudioHapSetActivePresetButton;
1226         private Button leAudioHapSetActivePresetForGroupButton;
1227         private Button leAudioHapReadPresetInfoButton;
1228         private Button leAudioHapNextDevicePresetButton;
1229         private Button leAudioHapPreviousDevicePresetButton;
1230         private Button leAudioHapNextGroupPresetButton;
1231         private Button leAudioHapPreviousGroupPresetButton;
1232 
1233         // VC View stuff
1234         private Switch vcConnectionSwitch;
1235         private SeekBar volumeSeekBar;
1236         private Switch muteSwitch;
1237         // VC Ext Input stuff
1238         private ImageButton inputFoldableIcon;
1239         private View inputFoldable;
1240         private Spinner inputIdxSpinner;
1241         private ImageButton inputGetStateButton;
1242         private SeekBar inputGainSeekBar;
1243         private Switch inputMuteSwitch;
1244         private ImageButton inputSetGainModeButton;
1245         private ImageButton inputGetGainPropsButton;
1246         private ImageButton inputGetTypeButton;
1247         private ImageButton inputGetStatusButton;
1248         private ImageButton inputGetDescriptionButton;
1249         private ImageButton inputSetDescriptionButton;
1250         private TextView inputGainModeText;
1251         private TextView inputGainPropsUnitText;
1252         private TextView inputGainPropsMinText;
1253         private TextView inputGainPropsMaxText;
1254         private TextView inputTypeText;
1255         private TextView inputStatusText;
1256         private TextView inputDescriptionText;
1257         // VC Ext Output stuff
1258         private ImageButton outputFoldableIcon;
1259         private View outputFoldable;
1260         private Spinner outputIdxSpinner;
1261         private ImageButton outpuGetGainButton;
1262         private SeekBar outputGainOffsetSeekBar;
1263         private ImageButton outputGetLocationButton;
1264         private ImageButton outputSetLocationButton;
1265         private ImageButton outputGetDescriptionButton;
1266         private ImageButton outputSetDescriptionButton;
1267         private TextView outputLocationText;
1268         private TextView outputDescriptionText;
1269 
1270         // BASS View stuff
1271         private Switch bassConnectionSwitch;
1272         private Spinner bassReceiverIdSpinner;
1273         private TextView bassReceiverPaStateText;
1274         private TextView bassReceiverEncStateText;
1275         private TextView bassReceiverBisStateText;
1276         private ImageButton bassScanButton;
1277 
ViewHolder(@onNull View itemView)1278         public ViewHolder(@NonNull View itemView) {
1279             super(itemView);
1280             deviceName = itemView.findViewById(R.id.device_name);
1281 
1282             SetupLeAudioView(itemView);
1283             setupVcView(itemView);
1284             setupHapView(itemView);
1285             setupGattBrView(itemView);
1286             setupBassView(itemView);
1287 
1288             // Notify viewmodel via parent's click listener
1289             itemView.setOnClickListener(
1290                     view -> {
1291                         Integer position = getAdapterPosition();
1292                         if (clickListener != null && position != RecyclerView.NO_POSITION) {
1293                             clickListener.onItemClick(devices.get(position));
1294                         }
1295                     });
1296         }
1297 
setupGattBrView(@onNull View itemView)1298         private void setupGattBrView(@NonNull View itemView) {
1299             gattBrConnectionSwitch = itemView.findViewById(R.id.gatt_br_switch);
1300             gattBrConnectionSwitch.setActivated(true);
1301 
1302             gattBrConnectionSwitch.setOnCheckedChangeListener(
1303                     (compoundButton, b) -> {
1304                         if (!compoundButton.isActivated()) return;
1305 
1306                         if (gattBrListener != null) {
1307                             gattBrListener.onToggleSwitch(
1308                                     devices.get(ViewHolder.this.getAdapterPosition()), b);
1309                         }
1310                     });
1311         }
1312 
setupHapView(@onNull View itemView)1313         private void setupHapView(@NonNull View itemView) {
1314             hapConnectionSwitch = itemView.findViewById(R.id.hap_switch);
1315             hapConnectionSwitch.setActivated(true);
1316 
1317             hapConnectionSwitch.setOnCheckedChangeListener(
1318                     (compoundButton, b) -> {
1319                         if (!compoundButton.isActivated()) return;
1320 
1321                         if (hapInteractionListener != null) {
1322                             if (b)
1323                                 hapInteractionListener.onConnectClick(
1324                                         devices.get(ViewHolder.this.getAdapterPosition()));
1325                             else
1326                                 hapInteractionListener.onDisconnectClick(
1327                                         devices.get(ViewHolder.this.getAdapterPosition()));
1328                         }
1329                     });
1330 
1331             leAudioHapState = itemView.findViewById(R.id.hap_profile_state_text);
1332             leAudioHapFeatures = itemView.findViewById(R.id.hap_profile_features_text);
1333             leAudioHapActivePresetIndex =
1334                     itemView.findViewById(R.id.hap_profile_active_preset_index_text);
1335             leAudioHapPresetsSpinner = itemView.findViewById(R.id.hap_presets_spinner);
1336             leAudioHapChangePresetNameButton =
1337                     itemView.findViewById(R.id.hap_change_preset_name_button);
1338             leAudioHapSetActivePresetButton =
1339                     itemView.findViewById(R.id.hap_set_active_preset_button);
1340             leAudioHapSetActivePresetForGroupButton =
1341                     itemView.findViewById(R.id.hap_set_active_preset_for_group_button);
1342             leAudioHapReadPresetInfoButton =
1343                     itemView.findViewById(R.id.hap_read_preset_info_button);
1344             leAudioHapNextDevicePresetButton =
1345                     itemView.findViewById(R.id.hap_next_device_preset_button);
1346             leAudioHapPreviousDevicePresetButton =
1347                     itemView.findViewById(R.id.hap_previous_device_preset_button);
1348             leAudioHapNextGroupPresetButton =
1349                     itemView.findViewById(R.id.hap_next_group_preset_button);
1350             leAudioHapPreviousGroupPresetButton =
1351                     itemView.findViewById(R.id.hap_previous_group_preset_button);
1352 
1353             leAudioHapPresetsSpinner.setOnItemSelectedListener(
1354                     new AdapterView.OnItemSelectedListener() {
1355                         @Override
1356                         public void onItemSelected(
1357                                 AdapterView<?> adapterView, View view, int position, long l) {
1358                             LeAudioDeviceStateWrapper device =
1359                                     devices.get(ViewHolder.this.getAdapterPosition());
1360                             ((ViewHolderHapPersistentData) device.leAudioData.viewsData)
1361                                     .selectedPresetPositionMutable.setValue(position);
1362                         }
1363 
1364                         @Override
1365                         public void onNothingSelected(AdapterView<?> adapterView) {
1366                             // Nothing to do here
1367                         }
1368                     });
1369 
1370             leAudioHapChangePresetNameButton.setOnClickListener(
1371                     view -> {
1372                         if (hapInteractionListener != null) {
1373                             if (leAudioHapPresetsSpinner.getSelectedItem() == null) {
1374                                 Toast.makeText(
1375                                                 view.getContext(),
1376                                                 "No known preset, please reconnect.",
1377                                                 Toast.LENGTH_SHORT)
1378                                         .show();
1379                                 return;
1380                             }
1381 
1382                             AlertDialog.Builder alert =
1383                                     new AlertDialog.Builder(itemView.getContext());
1384                             alert.setTitle("Set a name");
1385                             final EditText input = new EditText(itemView.getContext());
1386                             alert.setView(input);
1387                             alert.setPositiveButton(
1388                                     "Ok",
1389                                     (dialog, whichButton) -> {
1390                                         Integer index =
1391                                                 Integer.valueOf(
1392                                                         leAudioHapPresetsSpinner
1393                                                                 .getSelectedItem()
1394                                                                 .toString()
1395                                                                 .split("\\s")[0]);
1396                                         hapInteractionListener.onChangePresetNameClicked(
1397                                                 devices.get(ViewHolder.this.getAdapterPosition())
1398                                                         .device,
1399                                                 index,
1400                                                 input.getText().toString());
1401                                     });
1402                             alert.setNegativeButton(
1403                                     "Cancel",
1404                                     (dialog, whichButton) -> {
1405                                         // Do nothing
1406                                     });
1407                             alert.show();
1408                         }
1409                     });
1410 
1411             leAudioHapSetActivePresetButton.setOnClickListener(
1412                     view -> {
1413                         if (hapInteractionListener != null) {
1414                             if (leAudioHapPresetsSpinner.getSelectedItem() == null) {
1415                                 Toast.makeText(
1416                                                 view.getContext(),
1417                                                 "No known preset, please reconnect.",
1418                                                 Toast.LENGTH_SHORT)
1419                                         .show();
1420                                 return;
1421                             }
1422 
1423                             Integer index =
1424                                     Integer.valueOf(
1425                                             leAudioHapPresetsSpinner
1426                                                     .getSelectedItem()
1427                                                     .toString()
1428                                                     .split("\\s")[0]);
1429                             hapInteractionListener.onSetActivePresetClicked(
1430                                     devices.get(ViewHolder.this.getAdapterPosition()).device,
1431                                     index);
1432                         }
1433                     });
1434 
1435             leAudioHapSetActivePresetForGroupButton.setOnClickListener(
1436                     view -> {
1437                         if (hapInteractionListener != null) {
1438                             if (leAudioHapPresetsSpinner.getSelectedItem() == null) {
1439                                 Toast.makeText(
1440                                                 view.getContext(),
1441                                                 "No known preset, please reconnect.",
1442                                                 Toast.LENGTH_SHORT)
1443                                         .show();
1444                                 return;
1445                             }
1446 
1447                             Integer index =
1448                                     Integer.valueOf(
1449                                             leAudioHapPresetsSpinner
1450                                                     .getSelectedItem()
1451                                                     .toString()
1452                                                     .split("\\s")[0]);
1453                             hapInteractionListener.onSetActivePresetForGroupClicked(
1454                                     devices.get(ViewHolder.this.getAdapterPosition()).device,
1455                                     index);
1456                         }
1457                     });
1458 
1459             leAudioHapReadPresetInfoButton.setOnClickListener(
1460                     view -> {
1461                         if (hapInteractionListener != null) {
1462                             if (leAudioHapPresetsSpinner.getSelectedItem() == null) {
1463                                 Toast.makeText(
1464                                                 view.getContext(),
1465                                                 "No known preset, please reconnect.",
1466                                                 Toast.LENGTH_SHORT)
1467                                         .show();
1468                                 return;
1469                             }
1470 
1471                             Integer index =
1472                                     Integer.valueOf(
1473                                             leAudioHapPresetsSpinner
1474                                                     .getSelectedItem()
1475                                                     .toString()
1476                                                     .split("\\s")[0]);
1477                             hapInteractionListener.onReadPresetInfoClicked(
1478                                     devices.get(ViewHolder.this.getAdapterPosition()).device,
1479                                     index);
1480                         }
1481                     });
1482 
1483             leAudioHapNextDevicePresetButton.setOnClickListener(
1484                     view -> {
1485                         if (hapInteractionListener != null) {
1486                             hapInteractionListener.onNextDevicePresetClicked(
1487                                     devices.get(ViewHolder.this.getAdapterPosition()).device);
1488                         }
1489                     });
1490 
1491             leAudioHapPreviousDevicePresetButton.setOnClickListener(
1492                     view -> {
1493                         if (hapInteractionListener != null) {
1494                             hapInteractionListener.onPreviousDevicePresetClicked(
1495                                     devices.get(ViewHolder.this.getAdapterPosition()).device);
1496                         }
1497                     });
1498 
1499             leAudioHapNextGroupPresetButton.setOnClickListener(
1500                     view -> {
1501                         if (hapInteractionListener != null) {
1502                             hapInteractionListener.onNextGroupPresetClicked(
1503                                     devices.get(ViewHolder.this.getAdapterPosition()).device);
1504                         }
1505                     });
1506 
1507             leAudioHapPreviousGroupPresetButton.setOnClickListener(
1508                     view -> {
1509                         if (hapInteractionListener != null) {
1510                             hapInteractionListener.onPreviousGroupPresetClicked(
1511                                     devices.get(ViewHolder.this.getAdapterPosition()).device);
1512                         }
1513                     });
1514         }
1515 
SetupLeAudioView(@onNull View itemView)1516         private void SetupLeAudioView(@NonNull View itemView) {
1517             leAudioConnectionSwitch = itemView.findViewById(R.id.le_audio_switch);
1518             leAudioStartStreamButton = itemView.findViewById(R.id.start_stream_button);
1519             leAudioStopStreamButton = itemView.findViewById(R.id.stop_stream_button);
1520             leAudioSuspendStreamButton = itemView.findViewById(R.id.suspend_stream_button);
1521             leAudioGroupSetButton = itemView.findViewById(R.id.group_set_button);
1522             leAudioGroupUnsetButton = itemView.findViewById(R.id.group_unset_button);
1523             leAudioGroupDestroyButton = itemView.findViewById(R.id.group_destroy_button);
1524             leAudioGroupIdText = itemView.findViewById(R.id.group_id_text);
1525             leAudioGroupStatusText = itemView.findViewById(R.id.group_status_text);
1526             leAudioGroupFlagsText = itemView.findViewById(R.id.group_flags_text);
1527             leAudioSetLockButton = itemView.findViewById(R.id.set_lock_button);
1528             leAudioSetUnlockButton = itemView.findViewById(R.id.set_unlock_button);
1529             leAudioSetLockStateText = itemView.findViewById(R.id.lock_state_text);
1530             leAudioGroupMicrophoneSwitch = itemView.findViewById(R.id.group_mic_mute_state_switch);
1531             leAudioGroupMicrophoneState = itemView.findViewById(R.id.group_mic_mute_state_text);
1532 
1533             leAudioConnectionSwitch.setOnCheckedChangeListener(
1534                     (compoundButton, b) -> {
1535                         if (!compoundButton.isActivated()) return;
1536 
1537                         if (leAudioInteractionListener != null) {
1538                             if (b)
1539                                 leAudioInteractionListener.onConnectClick(
1540                                         devices.get(ViewHolder.this.getAdapterPosition()));
1541                             else
1542                                 leAudioInteractionListener.onDisconnectClick(
1543                                         devices.get(ViewHolder.this.getAdapterPosition()));
1544                         }
1545                     });
1546 
1547             leAudioStartStreamButton.setOnClickListener(
1548                     view -> {
1549                         AlertDialog.Builder alert = new AlertDialog.Builder(itemView.getContext());
1550                         alert.setTitle("Pick a content type");
1551                         NumberPicker input = new NumberPicker(itemView.getContext());
1552                         input.setMinValue(1);
1553                         input.setMaxValue(
1554                                 itemView.getResources().getStringArray(R.array.content_types).length
1555                                         - 1);
1556                         input.setDisplayedValues(
1557                                 itemView.getResources().getStringArray(R.array.content_types));
1558                         alert.setView(input);
1559                         alert.setPositiveButton(
1560                                 "Ok",
1561                                 (dialog, whichButton) -> {
1562                                     final Integer group_id =
1563                                             Integer.parseInt(
1564                                                     ViewHolder.this
1565                                                             .leAudioGroupIdText
1566                                                             .getText()
1567                                                             .toString());
1568                                     if (leAudioInteractionListener != null && group_id != null)
1569                                         leAudioInteractionListener.onStreamActionClicked(
1570                                                 devices.get(ViewHolder.this.getAdapterPosition()),
1571                                                 group_id,
1572                                                 1 << (input.getValue() - 1),
1573                                                 0);
1574                                 });
1575                         alert.setNegativeButton(
1576                                 "Cancel",
1577                                 (dialog, whichButton) -> {
1578                                     // Do nothing
1579                                 });
1580                         alert.show();
1581                     });
1582 
1583             leAudioSuspendStreamButton.setOnClickListener(
1584                     view -> {
1585                         final Integer group_id =
1586                                 Integer.parseInt(
1587                                         ViewHolder.this.leAudioGroupIdText.getText().toString());
1588                         if (leAudioInteractionListener != null && group_id != null)
1589                             leAudioInteractionListener.onStreamActionClicked(
1590                                     devices.get(ViewHolder.this.getAdapterPosition()),
1591                                     group_id,
1592                                     0,
1593                                     1);
1594                     });
1595 
1596             leAudioStopStreamButton.setOnClickListener(
1597                     view -> {
1598                         final Integer group_id =
1599                                 Integer.parseInt(
1600                                         ViewHolder.this.leAudioGroupIdText.getText().toString());
1601                         if (leAudioInteractionListener != null && group_id != null)
1602                             leAudioInteractionListener.onStreamActionClicked(
1603                                     devices.get(ViewHolder.this.getAdapterPosition()),
1604                                     group_id,
1605                                     0,
1606                                     2);
1607                     });
1608 
1609             leAudioGroupSetButton.setOnClickListener(
1610                     view -> {
1611                         AlertDialog.Builder alert = new AlertDialog.Builder(itemView.getContext());
1612                         alert.setTitle("Pick a group ID");
1613                         final EditText input = new EditText(itemView.getContext());
1614                         input.setInputType(InputType.TYPE_CLASS_NUMBER);
1615                         input.setRawInputType(Configuration.KEYBOARD_12KEY);
1616                         alert.setView(input);
1617                         alert.setPositiveButton(
1618                                 "Ok",
1619                                 (dialog, whichButton) -> {
1620                                     final Integer group_id =
1621                                             Integer.valueOf(input.getText().toString());
1622                                     leAudioInteractionListener.onGroupSetClicked(
1623                                             devices.get(ViewHolder.this.getAdapterPosition()),
1624                                             group_id);
1625                                 });
1626                         alert.setNegativeButton(
1627                                 "Cancel",
1628                                 (dialog, whichButton) -> {
1629                                     // Do nothing
1630                                 });
1631                         alert.show();
1632                     });
1633 
1634             leAudioGroupUnsetButton.setOnClickListener(
1635                     view -> {
1636                         final Integer group_id =
1637                                 Integer.parseInt(
1638                                         ViewHolder.this
1639                                                         .leAudioGroupIdText
1640                                                         .getText()
1641                                                         .toString()
1642                                                         .equals("Unknown")
1643                                                 ? "0"
1644                                                 : ViewHolder.this
1645                                                         .leAudioGroupIdText
1646                                                         .getText()
1647                                                         .toString());
1648                         if (leAudioInteractionListener != null)
1649                             leAudioInteractionListener.onGroupUnsetClicked(
1650                                     devices.get(ViewHolder.this.getAdapterPosition()), group_id);
1651                     });
1652 
1653             leAudioGroupDestroyButton.setOnClickListener(
1654                     view -> {
1655                         final Integer group_id =
1656                                 Integer.parseInt(
1657                                         ViewHolder.this.leAudioGroupIdText.getText().toString());
1658                         if (leAudioInteractionListener != null)
1659                             leAudioInteractionListener.onGroupDestroyClicked(
1660                                     devices.get(ViewHolder.this.getAdapterPosition()), group_id);
1661                     });
1662 
1663             leAudioSetLockButton.setOnClickListener(
1664                     view -> {
1665                         AlertDialog.Builder alert = new AlertDialog.Builder(itemView.getContext());
1666                         alert.setTitle("Pick a group ID");
1667                         final EditText input = new EditText(itemView.getContext());
1668                         input.setInputType(InputType.TYPE_CLASS_NUMBER);
1669                         input.setRawInputType(Configuration.KEYBOARD_12KEY);
1670                         alert.setView(input);
1671                         alert.setPositiveButton(
1672                                 "Ok",
1673                                 (dialog, whichButton) -> {
1674                                     final Integer group_id =
1675                                             Integer.valueOf(input.getText().toString());
1676                                     if (leAudioInteractionListener != null)
1677                                         leAudioInteractionListener.onGroupSetLockClicked(
1678                                                 devices.get(ViewHolder.this.getAdapterPosition()),
1679                                                 group_id,
1680                                                 true);
1681                                 });
1682                         alert.setNegativeButton(
1683                                 "Cancel",
1684                                 (dialog, whichButton) -> {
1685                                     // Do nothing
1686                                 });
1687                         alert.show();
1688                     });
1689 
1690             leAudioSetUnlockButton.setOnClickListener(
1691                     view -> {
1692                         AlertDialog.Builder alert = new AlertDialog.Builder(itemView.getContext());
1693                         alert.setTitle("Pick a group ID");
1694                         final EditText input = new EditText(itemView.getContext());
1695                         input.setInputType(InputType.TYPE_CLASS_NUMBER);
1696                         input.setRawInputType(Configuration.KEYBOARD_12KEY);
1697                         alert.setView(input);
1698                         alert.setPositiveButton(
1699                                 "Ok",
1700                                 (dialog, whichButton) -> {
1701                                     final Integer group_id =
1702                                             Integer.valueOf(input.getText().toString());
1703                                     if (leAudioInteractionListener != null)
1704                                         leAudioInteractionListener.onGroupSetLockClicked(
1705                                                 devices.get(ViewHolder.this.getAdapterPosition()),
1706                                                 group_id,
1707                                                 false);
1708                                 });
1709                         alert.setNegativeButton(
1710                                 "Cancel",
1711                                 (dialog, whichButton) -> {
1712                                     // Do nothing
1713                                 });
1714                         alert.show();
1715                     });
1716 
1717             leAudioGroupMicrophoneSwitch.setOnCheckedChangeListener(
1718                     (compoundButton, b) -> {
1719                         if (!compoundButton.isActivated()) return;
1720 
1721                         if (leAudioInteractionListener != null)
1722                             leAudioInteractionListener.onMicrophoneMuteChanged(
1723                                     devices.get(ViewHolder.this.getAdapterPosition()), b, true);
1724                     });
1725         }
1726 
setupVcView(@onNull View itemView)1727         private void setupVcView(@NonNull View itemView) {
1728             vcConnectionSwitch = itemView.findViewById(R.id.vc_switch);
1729             vcConnectionSwitch.setActivated(true);
1730             volumeSeekBar = itemView.findViewById(R.id.volume_seek_bar);
1731             muteSwitch = itemView.findViewById(R.id.mute_switch);
1732             muteSwitch.setActivated(true);
1733             inputFoldableIcon = itemView.findViewById(R.id.vc_input_foldable_icon);
1734             inputFoldable = itemView.findViewById(R.id.ext_input_foldable);
1735             inputIdxSpinner = itemView.findViewById(R.id.num_inputs_spinner);
1736             inputGetStateButton = itemView.findViewById(R.id.inputGetStateButton);
1737             inputGainSeekBar = itemView.findViewById(R.id.inputGainSeekBar);
1738             inputMuteSwitch = itemView.findViewById(R.id.inputMuteSwitch);
1739             inputMuteSwitch.setActivated(true);
1740             inputSetGainModeButton = itemView.findViewById(R.id.inputSetGainModeButton);
1741             inputGetGainPropsButton = itemView.findViewById(R.id.inputGetGainPropsButton);
1742             inputGetTypeButton = itemView.findViewById(R.id.inputGetTypeButton);
1743             inputGetStatusButton = itemView.findViewById(R.id.inputGetStatusButton);
1744             inputGetDescriptionButton = itemView.findViewById(R.id.inputGetDescriptionButton);
1745             inputSetDescriptionButton = itemView.findViewById(R.id.inputSetDescriptionButton);
1746             inputGainModeText = itemView.findViewById(R.id.inputGainModeText);
1747             inputGainPropsUnitText = itemView.findViewById(R.id.inputGainPropsUnitText);
1748             inputGainPropsMinText = itemView.findViewById(R.id.inputGainPropsMinText);
1749             inputGainPropsMaxText = itemView.findViewById(R.id.inputGainPropsMaxText);
1750             inputTypeText = itemView.findViewById(R.id.inputTypeText);
1751             inputStatusText = itemView.findViewById(R.id.inputStatusText);
1752             inputDescriptionText = itemView.findViewById(R.id.inputDescriptionText);
1753 
1754             outputFoldableIcon = itemView.findViewById(R.id.vc_output_foldable_icon);
1755             outputFoldable = itemView.findViewById(R.id.ext_output_foldable);
1756             outputIdxSpinner = itemView.findViewById(R.id.num_outputs_spinner);
1757             outpuGetGainButton = itemView.findViewById(R.id.outputGetGainButton);
1758             outputGainOffsetSeekBar = itemView.findViewById(R.id.outputGainSeekBar);
1759             outputGetLocationButton = itemView.findViewById(R.id.outputGetLocationButton);
1760             outputSetLocationButton = itemView.findViewById(R.id.outputSetLocationButton);
1761             outputGetDescriptionButton = itemView.findViewById(R.id.outputGetDescriptionButton);
1762             outputSetDescriptionButton = itemView.findViewById(R.id.outputSetDescriptionButton);
1763             outputLocationText = itemView.findViewById(R.id.outputLocationText);
1764             outputDescriptionText = itemView.findViewById(R.id.outputDescriptionText);
1765 
1766             vcConnectionSwitch.setOnCheckedChangeListener(
1767                     (compoundButton, b) -> {
1768                         if (!compoundButton.isActivated()) return;
1769 
1770                         if (volumeControlInteractionListener != null) {
1771                             if (b)
1772                                 volumeControlInteractionListener.onConnectClick(
1773                                         devices.get(ViewHolder.this.getAdapterPosition()));
1774                             else
1775                                 volumeControlInteractionListener.onDisconnectClick(
1776                                         devices.get(ViewHolder.this.getAdapterPosition()));
1777                         }
1778                     });
1779 
1780             volumeSeekBar.setOnSeekBarChangeListener(
1781                     new SeekBar.OnSeekBarChangeListener() {
1782                         @Override
1783                         public void onProgressChanged(SeekBar seekBar, int i, boolean b) {
1784                             // Nothing to do here
1785                         }
1786 
1787                         @Override
1788                         public void onStartTrackingTouch(SeekBar seekBar) {
1789                             // Nothing to do here
1790                         }
1791 
1792                         @Override
1793                         public void onStopTrackingTouch(SeekBar seekBar) {
1794                             // Set value only on release
1795                             if (volumeControlInteractionListener != null)
1796                                 volumeControlInteractionListener.onVolumeChanged(
1797                                         devices.get(ViewHolder.this.getAdapterPosition()),
1798                                         seekBar.getProgress(),
1799                                         true);
1800                         }
1801                     });
1802 
1803             muteSwitch.setOnCheckedChangeListener(
1804                     (compoundButton, b) -> {
1805                         if (!compoundButton.isActivated()) return;
1806 
1807                         if (volumeControlInteractionListener != null)
1808                             volumeControlInteractionListener.onCheckedChanged(
1809                                     devices.get(ViewHolder.this.getAdapterPosition()), b);
1810                     });
1811 
1812             inputFoldableIcon.setOnClickListener(
1813                     view -> {
1814                         ViewHolderVcPersistentData vData =
1815                                 (ViewHolderVcPersistentData)
1816                                         devices.get(ViewHolder.this.getAdapterPosition())
1817                                                 .volumeControlData
1818                                                 .viewsData;
1819                         if (vData != null)
1820                             vData.isInputsCollapsedMutable.setValue(
1821                                     !vData.isInputsCollapsedMutable.getValue());
1822                     });
1823 
1824             inputIdxSpinner.setOnItemSelectedListener(
1825                     new AdapterView.OnItemSelectedListener() {
1826                         @Override
1827                         public void onItemSelected(
1828                                 AdapterView<?> adapterView, View view, int position, long l) {
1829                             Integer index = ViewHolder.this.getAdapterPosition();
1830                             ((ViewHolderVcPersistentData)
1831                                                     devices.get(index).volumeControlData.viewsData)
1832                                             .selectedInputPosition =
1833                                     position;
1834                         }
1835 
1836                         @Override
1837                         public void onNothingSelected(AdapterView<?> adapterView) {
1838                             // Nothing to do here
1839                         }
1840                     });
1841 
1842             outputFoldableIcon.setOnClickListener(
1843                     view -> {
1844                         ViewHolderVcPersistentData vData =
1845                                 (ViewHolderVcPersistentData)
1846                                         devices.get(ViewHolder.this.getAdapterPosition())
1847                                                 .volumeControlData
1848                                                 .viewsData;
1849                         vData.isOutputsCollapsedMutable.setValue(
1850                                 !vData.isOutputsCollapsedMutable.getValue());
1851                     });
1852 
1853             outputIdxSpinner.setOnItemSelectedListener(
1854                     new AdapterView.OnItemSelectedListener() {
1855                         @Override
1856                         public void onItemSelected(
1857                                 AdapterView<?> adapterView, View view, int position, long l) {
1858                             Integer index = ViewHolder.this.getAdapterPosition();
1859                             ((ViewHolderVcPersistentData)
1860                                                     devices.get(index).volumeControlData.viewsData)
1861                                             .selectedOutputPosition =
1862                                     position;
1863                         }
1864 
1865                         @Override
1866                         public void onNothingSelected(AdapterView<?> adapterView) {
1867                             // Nothing to do here
1868                         }
1869                     });
1870 
1871             inputGetStateButton.setOnClickListener(
1872                     view -> {
1873                         if (volumeControlInteractionListener != null) {
1874                             if (inputIdxSpinner.getSelectedItem() == null) {
1875                                 Toast.makeText(
1876                                                 view.getContext(),
1877                                                 "No known ext. input, please reconnect.",
1878                                                 Toast.LENGTH_SHORT)
1879                                         .show();
1880                                 return;
1881                             }
1882                             Integer input_id =
1883                                     Integer.valueOf(inputIdxSpinner.getSelectedItem().toString());
1884                             volumeControlInteractionListener.onInputGetStateButtonClicked(
1885                                     devices.get(ViewHolder.this.getAdapterPosition()), input_id);
1886                         }
1887                     });
1888 
1889             inputGainSeekBar.setOnSeekBarChangeListener(
1890                     new SeekBar.OnSeekBarChangeListener() {
1891                         @Override
1892                         public void onProgressChanged(SeekBar seekBar, int i, boolean is_user_set) {
1893                             // Nothing to do here
1894                         }
1895 
1896                         @Override
1897                         public void onStartTrackingTouch(SeekBar seekBar) {
1898                             // Nothing to do here
1899                         }
1900 
1901                         @Override
1902                         public void onStopTrackingTouch(SeekBar seekBar) {
1903                             if (volumeControlInteractionListener != null) {
1904                                 if (inputIdxSpinner.getSelectedItem() == null) {
1905                                     Toast.makeText(
1906                                                     seekBar.getContext(),
1907                                                     "No known ext. input, please reconnect.",
1908                                                     Toast.LENGTH_SHORT)
1909                                             .show();
1910                                     return;
1911                                 }
1912                                 Integer input_id =
1913                                         Integer.valueOf(
1914                                                 inputIdxSpinner.getSelectedItem().toString());
1915                                 volumeControlInteractionListener.onInputGainValueChanged(
1916                                         devices.get(ViewHolder.this.getAdapterPosition()),
1917                                         input_id,
1918                                         seekBar.getProgress());
1919                             }
1920                         }
1921                     });
1922 
1923             inputMuteSwitch.setOnCheckedChangeListener(
1924                     (compoundButton, b) -> {
1925                         if (!compoundButton.isActivated()) return;
1926 
1927                         if (volumeControlInteractionListener != null) {
1928                             if (inputIdxSpinner.getSelectedItem() == null) {
1929                                 Toast.makeText(
1930                                                 compoundButton.getContext(),
1931                                                 "No known ext. input, please reconnect.",
1932                                                 Toast.LENGTH_SHORT)
1933                                         .show();
1934                                 return;
1935                             }
1936                             Integer input_id =
1937                                     Integer.valueOf(inputIdxSpinner.getSelectedItem().toString());
1938                             volumeControlInteractionListener.onInputMuteSwitched(
1939                                     devices.get(ViewHolder.this.getAdapterPosition()), input_id, b);
1940                         }
1941                     });
1942 
1943             inputSetGainModeButton.setOnClickListener(
1944                     view -> {
1945                         if (volumeControlInteractionListener != null) {
1946                             if (inputIdxSpinner.getSelectedItem() == null) {
1947                                 Toast.makeText(
1948                                                 view.getContext(),
1949                                                 "No known ext. input, please reconnect.",
1950                                                 Toast.LENGTH_SHORT)
1951                                         .show();
1952                                 return;
1953                             }
1954 
1955                             AlertDialog.Builder alert =
1956                                     new AlertDialog.Builder(itemView.getContext());
1957                             alert.setTitle("Select Gain mode");
1958                             NumberPicker input = new NumberPicker(itemView.getContext());
1959                             input.setMinValue(0);
1960                             input.setMaxValue(2);
1961                             input.setDisplayedValues(
1962                                     itemView.getResources().getStringArray(R.array.gain_modes));
1963                             alert.setView(input);
1964                             alert.setPositiveButton(
1965                                     "Ok",
1966                                     (dialog, whichButton) -> {
1967                                         Integer input_id =
1968                                                 Integer.valueOf(
1969                                                         inputIdxSpinner
1970                                                                 .getSelectedItem()
1971                                                                 .toString());
1972                                         volumeControlInteractionListener
1973                                                 .onInputSetGainModeButtonClicked(
1974                                                         devices.get(
1975                                                                 ViewHolder.this
1976                                                                         .getAdapterPosition()),
1977                                                         input_id,
1978                                                         input.getValue() == 2);
1979                                     });
1980                             alert.setNegativeButton(
1981                                     "Cancel",
1982                                     (dialog, whichButton) -> {
1983                                         // Do nothing
1984                                     });
1985                             alert.show();
1986                         }
1987                     });
1988 
1989             inputGetGainPropsButton.setOnClickListener(
1990                     view -> {
1991                         if (volumeControlInteractionListener != null) {
1992                             if (inputIdxSpinner.getSelectedItem() == null) {
1993                                 Toast.makeText(
1994                                                 view.getContext(),
1995                                                 "No known ext. input, please reconnect.",
1996                                                 Toast.LENGTH_SHORT)
1997                                         .show();
1998                                 return;
1999                             }
2000                             Integer input_id =
2001                                     Integer.valueOf(inputIdxSpinner.getSelectedItem().toString());
2002                             volumeControlInteractionListener.onInputGetGainPropsButtonClicked(
2003                                     devices.get(ViewHolder.this.getAdapterPosition()), input_id);
2004                         }
2005                     });
2006 
2007             inputGetTypeButton.setOnClickListener(
2008                     view -> {
2009                         if (volumeControlInteractionListener != null) {
2010                             if (inputIdxSpinner.getSelectedItem() == null) {
2011                                 Toast.makeText(
2012                                                 view.getContext(),
2013                                                 "No known ext. input, please reconnect.",
2014                                                 Toast.LENGTH_SHORT)
2015                                         .show();
2016                                 return;
2017                             }
2018                             Integer input_id =
2019                                     Integer.valueOf(inputIdxSpinner.getSelectedItem().toString());
2020                             volumeControlInteractionListener.onInputGetTypeButtonClicked(
2021                                     devices.get(ViewHolder.this.getAdapterPosition()), input_id);
2022                         }
2023                     });
2024 
2025             inputGetStatusButton.setOnClickListener(
2026                     view -> {
2027                         if (volumeControlInteractionListener != null) {
2028                             if (inputIdxSpinner.getSelectedItem() == null) {
2029                                 Toast.makeText(
2030                                                 view.getContext(),
2031                                                 "No known ext. input, please reconnect.",
2032                                                 Toast.LENGTH_SHORT)
2033                                         .show();
2034                                 return;
2035                             }
2036                             Integer input_id =
2037                                     Integer.valueOf(inputIdxSpinner.getSelectedItem().toString());
2038                             volumeControlInteractionListener.onInputGetStatusButton(
2039                                     devices.get(ViewHolder.this.getAdapterPosition()), input_id);
2040                         }
2041                     });
2042 
2043             inputGetDescriptionButton.setOnClickListener(
2044                     view -> {
2045                         if (volumeControlInteractionListener != null) {
2046                             if (inputIdxSpinner.getSelectedItem() == null) {
2047                                 Toast.makeText(
2048                                                 view.getContext(),
2049                                                 "No known ext. input, please reconnect.",
2050                                                 Toast.LENGTH_SHORT)
2051                                         .show();
2052                                 return;
2053                             }
2054                             Integer input_id =
2055                                     Integer.valueOf(inputIdxSpinner.getSelectedItem().toString());
2056                             volumeControlInteractionListener.onInputGetDescriptionButtonClicked(
2057                                     devices.get(ViewHolder.this.getAdapterPosition()), input_id);
2058                         }
2059                     });
2060 
2061             inputSetDescriptionButton.setOnClickListener(
2062                     view -> {
2063                         if (volumeControlInteractionListener != null) {
2064                             if (inputIdxSpinner.getSelectedItem() == null) {
2065                                 Toast.makeText(
2066                                                 view.getContext(),
2067                                                 "No known ext. input, please reconnect.",
2068                                                 Toast.LENGTH_SHORT)
2069                                         .show();
2070                                 return;
2071                             }
2072 
2073                             AlertDialog.Builder alert =
2074                                     new AlertDialog.Builder(itemView.getContext());
2075                             alert.setTitle("Set a description");
2076                             final EditText input = new EditText(itemView.getContext());
2077                             alert.setView(input);
2078                             alert.setPositiveButton(
2079                                     "Ok",
2080                                     (dialog, whichButton) -> {
2081                                         Integer input_id =
2082                                                 Integer.valueOf(
2083                                                         inputIdxSpinner
2084                                                                 .getSelectedItem()
2085                                                                 .toString());
2086                                         volumeControlInteractionListener
2087                                                 .onInputSetDescriptionButtonClicked(
2088                                                         devices.get(
2089                                                                 ViewHolder.this
2090                                                                         .getAdapterPosition()),
2091                                                         input_id,
2092                                                         input.getText().toString());
2093                                     });
2094                             alert.setNegativeButton(
2095                                     "Cancel",
2096                                     (dialog, whichButton) -> {
2097                                         // Do nothing
2098                                     });
2099                             alert.show();
2100                         }
2101                     });
2102 
2103             outpuGetGainButton.setOnClickListener(
2104                     view -> {
2105                         if (outputIdxSpinner.getSelectedItem() == null) {
2106                             Toast.makeText(
2107                                             view.getContext(),
2108                                             "No known ext. output, please reconnect.",
2109                                             Toast.LENGTH_SHORT)
2110                                     .show();
2111                             return;
2112                         }
2113 
2114                         Integer output_id =
2115                                 Integer.valueOf(outputIdxSpinner.getSelectedItem().toString());
2116                         if (volumeControlInteractionListener != null)
2117                             volumeControlInteractionListener.onOutputGetGainButtonClicked(
2118                                     devices.get(ViewHolder.this.getAdapterPosition()), output_id);
2119                     });
2120 
2121             outputGainOffsetSeekBar.setOnSeekBarChangeListener(
2122                     new SeekBar.OnSeekBarChangeListener() {
2123                         @Override
2124                         public void onProgressChanged(
2125                                 SeekBar seekBar, int value, boolean is_from_user) {
2126                             // Do nothing here
2127                         }
2128 
2129                         @Override
2130                         public void onStartTrackingTouch(SeekBar seekBar) {
2131                             // Do nothing here
2132                         }
2133 
2134                         @Override
2135                         public void onStopTrackingTouch(SeekBar seekBar) {
2136                             if (outputIdxSpinner.getSelectedItem() == null) {
2137                                 Toast.makeText(
2138                                                 seekBar.getContext(),
2139                                                 "No known ext. output, please reconnect.",
2140                                                 Toast.LENGTH_SHORT)
2141                                         .show();
2142                                 return;
2143                             }
2144 
2145                             Integer output_id =
2146                                     Integer.valueOf(outputIdxSpinner.getSelectedItem().toString());
2147                             if (volumeControlInteractionListener != null)
2148                                 volumeControlInteractionListener.onOutputGainOffsetGainValueChanged(
2149                                         devices.get(ViewHolder.this.getAdapterPosition()),
2150                                         output_id,
2151                                         seekBar.getProgress());
2152                         }
2153                     });
2154 
2155             outputGetLocationButton.setOnClickListener(
2156                     view -> {
2157                         if (volumeControlInteractionListener != null) {
2158                             if (outputIdxSpinner.getSelectedItem() == null) {
2159                                 Toast.makeText(
2160                                                 view.getContext(),
2161                                                 "No known ext. output, please reconnect.",
2162                                                 Toast.LENGTH_SHORT)
2163                                         .show();
2164                                 return;
2165                             }
2166 
2167                             Integer output_id =
2168                                     Integer.valueOf(outputIdxSpinner.getSelectedItem().toString());
2169                             volumeControlInteractionListener.onOutputGetLocationButtonClicked(
2170                                     devices.get(ViewHolder.this.getAdapterPosition()), output_id);
2171                         }
2172                     });
2173 
2174             outputSetLocationButton.setOnClickListener(
2175                     view -> {
2176                         if (volumeControlInteractionListener != null) {
2177                             if (outputIdxSpinner.getSelectedItem() == null) {
2178                                 Toast.makeText(
2179                                                 view.getContext(),
2180                                                 "No known ext. output, please reconnect.",
2181                                                 Toast.LENGTH_SHORT)
2182                                         .show();
2183                                 return;
2184                             }
2185 
2186                             AlertDialog.Builder alert =
2187                                     new AlertDialog.Builder(itemView.getContext());
2188                             alert.setTitle("Pick an Audio Location");
2189                             NumberPicker input = new NumberPicker(itemView.getContext());
2190                             input.setMinValue(0);
2191                             input.setMaxValue(
2192                                     itemView.getResources()
2193                                                     .getStringArray(R.array.audio_locations)
2194                                                     .length
2195                                             - 1);
2196                             input.setDisplayedValues(
2197                                     itemView.getResources()
2198                                             .getStringArray(R.array.audio_locations));
2199                             alert.setView(input);
2200                             alert.setPositiveButton(
2201                                     "Ok",
2202                                     (dialog, whichButton) -> {
2203                                         Integer output_id =
2204                                                 Integer.valueOf(
2205                                                         outputIdxSpinner
2206                                                                 .getSelectedItem()
2207                                                                 .toString());
2208                                         volumeControlInteractionListener
2209                                                 .onOutputSetLocationButtonClicked(
2210                                                         devices.get(
2211                                                                 ViewHolder.this
2212                                                                         .getAdapterPosition()),
2213                                                         output_id,
2214                                                         input.getValue());
2215                                     });
2216                             alert.setNegativeButton(
2217                                     "Cancel",
2218                                     (dialog, whichButton) -> {
2219                                         // Do nothing
2220                                     });
2221                             alert.show();
2222                         }
2223                     });
2224 
2225             outputGetDescriptionButton.setOnClickListener(
2226                     view -> {
2227                         if (volumeControlInteractionListener != null) {
2228                             if (outputIdxSpinner.getSelectedItem() == null) {
2229                                 Toast.makeText(
2230                                                 view.getContext(),
2231                                                 "No known ext. output, please reconnect.",
2232                                                 Toast.LENGTH_SHORT)
2233                                         .show();
2234                                 return;
2235                             }
2236 
2237                             Integer output_id =
2238                                     Integer.valueOf(outputIdxSpinner.getSelectedItem().toString());
2239                             volumeControlInteractionListener.onOutputGetDescriptionButtonClicked(
2240                                     devices.get(ViewHolder.this.getAdapterPosition()), output_id);
2241                         }
2242                     });
2243 
2244             outputSetDescriptionButton.setOnClickListener(
2245                     view -> {
2246                         if (volumeControlInteractionListener != null) {
2247                             if (outputIdxSpinner.getSelectedItem() == null) {
2248                                 Toast.makeText(
2249                                                 view.getContext(),
2250                                                 "No known ext. output, please reconnect.",
2251                                                 Toast.LENGTH_SHORT)
2252                                         .show();
2253                                 return;
2254                             }
2255 
2256                             AlertDialog.Builder alert =
2257                                     new AlertDialog.Builder(itemView.getContext());
2258                             alert.setTitle("Set a description");
2259                             final EditText input = new EditText(itemView.getContext());
2260                             alert.setView(input);
2261                             alert.setPositiveButton(
2262                                     "Ok",
2263                                     (dialog, whichButton) -> {
2264                                         Integer output_id =
2265                                                 Integer.valueOf(
2266                                                         outputIdxSpinner
2267                                                                 .getSelectedItem()
2268                                                                 .toString());
2269                                         volumeControlInteractionListener
2270                                                 .onOutputSetDescriptionButton(
2271                                                         devices.get(
2272                                                                 ViewHolder.this
2273                                                                         .getAdapterPosition()),
2274                                                         output_id,
2275                                                         input.getText().toString());
2276                                     });
2277                             alert.setNegativeButton(
2278                                     "Cancel",
2279                                     (dialog, whichButton) -> {
2280                                         // Do nothing
2281                                     });
2282                             alert.show();
2283                         }
2284                     });
2285         }
2286 
setupBassView(@onNull View itemView)2287         private void setupBassView(@NonNull View itemView) {
2288             bassConnectionSwitch = itemView.findViewById(R.id.bass_switch);
2289             bassConnectionSwitch.setActivated(true);
2290             bassReceiverIdSpinner = itemView.findViewById(R.id.num_receiver_spinner);
2291             bassReceiverPaStateText = itemView.findViewById(R.id.receiver_pa_state_text);
2292             bassReceiverEncStateText = itemView.findViewById(R.id.receiver_enc_state_text);
2293             bassReceiverBisStateText = itemView.findViewById(R.id.receiver_bis_state_text);
2294             bassScanButton = itemView.findViewById(R.id.broadcast_button);
2295 
2296             bassConnectionSwitch.setOnCheckedChangeListener(
2297                     (compoundButton, b) -> {
2298                         if (!compoundButton.isActivated()) return;
2299 
2300                         if (bassInteractionListener != null) {
2301                             if (b)
2302                                 bassInteractionListener.onConnectClick(
2303                                         devices.get(ViewHolder.this.getAdapterPosition()));
2304                             else
2305                                 bassInteractionListener.onDisconnectClick(
2306                                         devices.get(ViewHolder.this.getAdapterPosition()));
2307                         }
2308                     });
2309 
2310             bassReceiverIdSpinner.setOnItemSelectedListener(
2311                     new AdapterView.OnItemSelectedListener() {
2312                         @Override
2313                         public void onItemSelected(
2314                                 AdapterView<?> adapterView, View view, int position, long l) {
2315                             LeAudioDeviceStateWrapper device =
2316                                     devices.get(ViewHolder.this.getAdapterPosition());
2317                             ((ViewHolderBassPersistentData) device.bassData.viewsData)
2318                                     .selectedReceiverPositionMutable.setValue(position);
2319                         }
2320 
2321                         @Override
2322                         public void onNothingSelected(AdapterView<?> adapterView) {
2323                             // Nothing to do here
2324                         }
2325                     });
2326 
2327             bassScanButton.setOnClickListener(
2328                     view -> {
2329                         /* Broadcast receiver is not selected */
2330                         if (bassReceiverIdSpinner.getSelectedItem() == null) {
2331                             Toast.makeText(
2332                                             view.getContext(),
2333                                             "Receiver not selected",
2334                                             Toast.LENGTH_SHORT)
2335                                     .show();
2336                             return;
2337                         }
2338 
2339                         int receiver_id =
2340                                 Integer.parseInt(
2341                                         bassReceiverIdSpinner.getSelectedItem().toString());
2342                         LeAudioDeviceStateWrapper leAudioDeviceStateWrapper =
2343                                 devices.get(ViewHolder.this.getAdapterPosition());
2344                         bassInteractionListener.onReceiverSelected(
2345                                 leAudioDeviceStateWrapper, receiver_id);
2346 
2347                         Map<Integer, BluetoothLeBroadcastReceiveState> states =
2348                                 leAudioDeviceStateWrapper.bassData.receiverStatesMutable.getValue();
2349 
2350                         if (states != null) {
2351                             if (states.containsKey(receiver_id)) {
2352                                 BluetoothLeBroadcastReceiveState state =
2353                                         states.get(bassReceiverIdSpinner.getSelectedItem());
2354                                 int paSyncState = state.getPaSyncState();
2355                                 int bigEncryptionState = state.getBigEncryptionState();
2356                                 long bisSyncState = 0;
2357 
2358                                 if (state.getNumSubgroups() == 1) {
2359                                     bisSyncState = state.getBisSyncState().get(0);
2360                                 } else if (state.getNumSubgroups() > 1) {
2361                                     // TODO: Add multiple subgroup support
2362                                     Log.w(
2363                                             "LeAudioRecycleViewAdapter",
2364                                             "There is more than one subgroup in " + "BIG");
2365                                     bisSyncState = state.getBisSyncState().get(0);
2366                                 }
2367 
2368                                 /* Stop synchronization */
2369                                 if ((paSyncState != PA_SYNC_STATE_IDLE)
2370                                         || (bisSyncState != 0x00000000)) {
2371                                     AlertDialog.Builder alert =
2372                                             new AlertDialog.Builder(itemView.getContext());
2373                                     alert.setTitle("Stop the synchronization?");
2374 
2375                                     BluetoothDevice device =
2376                                             devices.get(ViewHolder.this.getAdapterPosition())
2377                                                     .device;
2378                                     if (bassReceiverIdSpinner.getSelectedItem() == null) {
2379                                         Toast.makeText(
2380                                                         view.getContext(),
2381                                                         "Not available",
2382                                                         Toast.LENGTH_SHORT)
2383                                                 .show();
2384                                         return;
2385                                     }
2386 
2387                                     alert.setPositiveButton(
2388                                             "Yes",
2389                                             (dialog, whichButton) -> {
2390                                                 bassInteractionListener.onRemoveSourceReq(
2391                                                         device, receiver_id);
2392                                             });
2393                                     // FIXME: To modify source we need the valid broadcaster_id
2394                                     // context so
2395                                     //        we should start scan here again
2396                                     // alert.setNeutralButton("Modify", (dialog, whichButton) -> {
2397                                     //
2398                                     // TODO: Open the scan dialog to get the broadcast_id
2399                                     //       bassInteractionListener.onStopSyncReq(device,
2400                                     // receiver_id,
2401                                     //       broadcast_id);
2402                                     // });
2403                                     alert.setNegativeButton(
2404                                             "No",
2405                                             (dialog, whichButton) -> {
2406                                                 // Do nothing
2407                                             });
2408                                     alert.show();
2409                                 } else if (bisSyncState == 0x00000000) {
2410                                     /* Add or Remove source */
2411                                     AlertDialog.Builder alert =
2412                                             new AlertDialog.Builder(itemView.getContext());
2413                                     alert.setTitle(
2414                                             "Scan and add a source or remove the currently set "
2415                                                     + "one.");
2416 
2417                                     BluetoothDevice device =
2418                                             devices.get(ViewHolder.this.getAdapterPosition())
2419                                                     .device;
2420 
2421                                     alert.setPositiveButton(
2422                                             "Scan",
2423                                             (dialog, whichButton) -> {
2424                                                 // Scan for new announcements
2425                                                 Intent intent =
2426                                                         new Intent(
2427                                                                 this.itemView.getContext(),
2428                                                                 BroadcastScanActivity.class);
2429                                                 intent.addFlags(
2430                                                         Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
2431                                                 intent.putExtra(
2432                                                         BluetoothDevice.EXTRA_DEVICE,
2433                                                         devices.get(
2434                                                                         ViewHolder.this
2435                                                                                 .getAdapterPosition())
2436                                                                 .device);
2437                                                 parent.startActivityForResult(intent, 666);
2438                                             });
2439                                     alert.setNeutralButton(
2440                                             "Cancel",
2441                                             (dialog, whichButton) -> {
2442                                                 // Do nothing
2443                                             });
2444                                     if (receiver_id != -1) {
2445                                         final int remove_receiver_id = receiver_id;
2446                                         alert.setNegativeButton(
2447                                                 "Remove",
2448                                                 (dialog, whichButton) -> {
2449                                                     bassInteractionListener.onRemoveSourceReq(
2450                                                             device, remove_receiver_id);
2451                                                 });
2452                                     }
2453                                     alert.show();
2454                                 } else if ((bigEncryptionState == BIG_ENCRYPTION_STATE_BAD_CODE)
2455                                         || (bigEncryptionState
2456                                                 == BIG_ENCRYPTION_STATE_CODE_REQUIRED)) {
2457                                     /* Deliver broadcast key */
2458                                     AlertDialog.Builder alert =
2459                                             new AlertDialog.Builder(itemView.getContext());
2460                                     alert.setTitle("Please enter broadcast encryption code...");
2461                                     EditText pass_input_view = new EditText(itemView.getContext());
2462                                     pass_input_view.setFilters(
2463                                             new InputFilter[] {new InputFilter.LengthFilter(16)});
2464                                     alert.setView(pass_input_view);
2465 
2466                                     BluetoothDevice device =
2467                                             devices.get(ViewHolder.this.getAdapterPosition())
2468                                                     .device;
2469                                     if (bassReceiverIdSpinner.getSelectedItem() == null) {
2470                                         Toast.makeText(
2471                                                         view.getContext(),
2472                                                         "Not available",
2473                                                         Toast.LENGTH_SHORT)
2474                                                 .show();
2475                                         return;
2476                                     }
2477 
2478                                     alert.setPositiveButton(
2479                                             "Set",
2480                                             (dialog, whichButton) -> {
2481                                                 byte[] code =
2482                                                         pass_input_view
2483                                                                 .getText()
2484                                                                 .toString()
2485                                                                 .getBytes();
2486                                                 bassInteractionListener.onBroadcastCodeEntered(
2487                                                         device, receiver_id, code);
2488                                             });
2489                                     alert.setNegativeButton(
2490                                             "Cancel",
2491                                             (dialog, whichButton) -> {
2492                                                 // Do nothing
2493                                             });
2494                                     alert.show();
2495                                 }
2496                             }
2497                         }
2498                     });
2499         }
2500     }
2501 
2502     private class ViewHolderVcPersistentData {
2503         Integer selectedInputPosition;
2504         Integer selectedOutputPosition;
2505 
2506         MutableLiveData<Boolean> isInputsCollapsedMutable = new MutableLiveData<>();
2507         MutableLiveData<Boolean> isOutputsCollapsedMutable = new MutableLiveData<>();
2508     }
2509 
2510     private class ViewHolderBassPersistentData {
2511         MutableLiveData<Integer> selectedReceiverPositionMutable = new MutableLiveData<>();
2512     }
2513 
2514     private class ViewHolderHapPersistentData {
2515         MutableLiveData<Integer> selectedPresetPositionMutable = new MutableLiveData<>();
2516     }
2517 }
2518