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