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