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