1 /* 2 * Copyright 2021 HIMSA II K/S - www.himsa.com. 3 * Represented by EHIMA - www.ehima.com 4 * 5 * Licensed under the Apache License, Version 2.0 (the "License"); 6 * you may not use this file except in compliance with the License. 7 * You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18 package com.android.bluetooth.leaudio; 19 20 import android.bluetooth.BluetoothDevice; 21 import android.bluetooth.BluetoothLeAudioContentMetadata; 22 import android.bluetooth.BluetoothLeBroadcastChannel; 23 import android.bluetooth.BluetoothLeBroadcastMetadata; 24 import android.bluetooth.BluetoothLeBroadcastSubgroup; 25 import android.content.Intent; 26 import android.os.Bundle; 27 import android.view.LayoutInflater; 28 import android.view.View; 29 import android.widget.EditText; 30 import android.widget.TextView; 31 import android.widget.Toast; 32 33 import androidx.appcompat.app.AlertDialog; 34 import androidx.appcompat.app.AppCompatActivity; 35 import androidx.lifecycle.ViewModelProviders; 36 import androidx.recyclerview.widget.LinearLayoutManager; 37 import androidx.recyclerview.widget.RecyclerView; 38 39 import java.util.List; 40 import java.util.Objects; 41 42 public class BroadcastScanActivity extends AppCompatActivity { 43 private BluetoothDevice device; 44 private BroadcastScanViewModel mViewModel; 45 private BroadcastItemsAdapter adapter; 46 47 @Override onCreate(Bundle savedInstanceState)48 protected void onCreate(Bundle savedInstanceState) { 49 super.onCreate(savedInstanceState); 50 setContentView(R.layout.broadcast_scan_activity); 51 52 RecyclerView recyclerView = findViewById(R.id.broadcast_recycler_view); 53 recyclerView.setLayoutManager(new LinearLayoutManager(this)); 54 recyclerView.setHasFixedSize(true); 55 56 adapter = new BroadcastItemsAdapter(); 57 adapter.setOnItemClickListener( 58 broadcastId -> { 59 mViewModel.scanForBroadcasts(device, false); 60 61 BluetoothLeBroadcastMetadata broadcast = null; 62 for (BluetoothLeBroadcastMetadata b : 63 mViewModel.getAllBroadcasts().getValue()) { 64 if (Objects.equals(b.getBroadcastId(), broadcastId)) { 65 broadcast = b; 66 break; 67 } 68 } 69 70 if (broadcast == null) { 71 Toast.makeText( 72 recyclerView.getContext(), 73 "Matching broadcast not found." 74 + " broadcastId=" 75 + broadcastId, 76 Toast.LENGTH_SHORT) 77 .show(); 78 return; 79 } 80 81 // Set broadcast source on peer only if scan delegator device context is 82 // available 83 if (device != null) { 84 // Start Dialog with the broadcast input details 85 AlertDialog.Builder alert = new AlertDialog.Builder(this); 86 LayoutInflater inflater = getLayoutInflater(); 87 alert.setTitle("Add the Broadcast:"); 88 89 View alertView = 90 inflater.inflate( 91 R.layout.broadcast_scan_add_encrypted_source_dialog, null); 92 93 boolean isPublic = broadcast.isPublicBroadcast(); 94 TextView addr_text = alertView.findViewById(R.id.broadcast_with_pbp_text); 95 addr_text.setText("Broadcast with PBP: " + (isPublic ? "Yes" : "No")); 96 97 String name = broadcast.getBroadcastName(); 98 addr_text = alertView.findViewById(R.id.broadcast_name_text); 99 if (isPublic && name != null) { 100 addr_text.setText("Public Name: " + name); 101 } else { 102 addr_text.setVisibility(View.INVISIBLE); 103 } 104 105 BluetoothLeAudioContentMetadata publicMetadata = 106 broadcast.getPublicBroadcastMetadata(); 107 addr_text = alertView.findViewById(R.id.public_program_info_text); 108 if (isPublic && publicMetadata != null) { 109 addr_text.setText("Public Info: " + publicMetadata.getProgramInfo()); 110 } else { 111 addr_text.setVisibility(View.INVISIBLE); 112 } 113 114 final EditText channels_input_text = 115 alertView.findViewById(R.id.broadcast_channel_map); 116 117 final EditText code_input_text = 118 alertView.findViewById(R.id.broadcast_code_input); 119 BluetoothLeBroadcastMetadata.Builder builder = 120 new BluetoothLeBroadcastMetadata.Builder(broadcast); 121 122 alert.setView(alertView) 123 .setNegativeButton( 124 "Cancel", 125 (dialog, which) -> { 126 // Do nothing 127 }) 128 .setPositiveButton( 129 "Add", 130 (dialog, which) -> { 131 BluetoothLeBroadcastMetadata metadata; 132 if (code_input_text.getText() == null) { 133 Toast.makeText( 134 recyclerView.getContext(), 135 "Invalid broadcast code", 136 Toast.LENGTH_SHORT) 137 .show(); 138 return; 139 } 140 if (code_input_text.getText().length() == 0) { 141 Toast.makeText( 142 recyclerView.getContext(), 143 "Adding not encrypted broadcast " 144 + "source broadcastId=" 145 + broadcastId, 146 Toast.LENGTH_SHORT) 147 .show(); 148 metadata = builder.setEncrypted(false).build(); 149 } else { 150 if ((code_input_text.getText().length() > 16) 151 || (code_input_text.getText().length() 152 < 4)) { 153 Toast.makeText( 154 recyclerView.getContext(), 155 "Invalid Broadcast code length", 156 Toast.LENGTH_SHORT) 157 .show(); 158 159 return; 160 } 161 162 metadata = 163 builder.setBroadcastCode( 164 code_input_text 165 .getText() 166 .toString() 167 .getBytes()) 168 .setEncrypted(true) 169 .build(); 170 } 171 172 if ((channels_input_text.getText() != null) 173 && (channels_input_text.getText().length() 174 != 0)) { 175 int channelMap = 176 Integer.parseInt( 177 channels_input_text 178 .getText() 179 .toString()); 180 // Apply a single channel map preference to all 181 // subgroups 182 for (BluetoothLeBroadcastSubgroup subGroup : 183 metadata.getSubgroups()) { 184 List<BluetoothLeBroadcastChannel> channels = 185 subGroup.getChannels(); 186 for (int i = 0; i < channels.size(); i++) { 187 BluetoothLeBroadcastChannel channel = 188 channels.get(i); 189 // Set the channel preference value 190 // according to the map 191 if (channel.getChannelIndex() != 0) { 192 if ((channelMap 193 & (1 194 << (channel 195 .getChannelIndex() 196 - 1))) 197 != 0) { 198 BluetoothLeBroadcastChannel.Builder 199 bob = 200 new BluetoothLeBroadcastChannel 201 .Builder( 202 channel); 203 bob.setSelected(true); 204 channels.set(i, bob.build()); 205 } 206 } 207 } 208 } 209 } 210 211 Toast.makeText( 212 recyclerView.getContext(), 213 "Adding broadcast source" 214 + " broadcastId=" 215 + broadcastId, 216 Toast.LENGTH_SHORT) 217 .show(); 218 mViewModel.addBroadcastSource(device, metadata); 219 }); 220 221 alert.show(); 222 } 223 }); 224 recyclerView.setAdapter(adapter); 225 226 mViewModel = ViewModelProviders.of(this).get(BroadcastScanViewModel.class); 227 mViewModel 228 .getAllBroadcasts() 229 .observe( 230 this, 231 audioBroadcasts -> { 232 // Update Broadcast list in the adapter 233 adapter.setBroadcasts(audioBroadcasts); 234 }); 235 236 Intent intent = getIntent(); 237 device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); 238 } 239 240 @Override onPause()241 protected void onPause() { 242 super.onPause(); 243 244 mViewModel.scanForBroadcasts(device, false); 245 } 246 247 @Override onResume()248 protected void onResume() { 249 super.onResume(); 250 251 if (mViewModel.getAllBroadcasts().getValue() != null) 252 adapter.setBroadcasts(mViewModel.getAllBroadcasts().getValue()); 253 254 mViewModel.scanForBroadcasts(device, true); 255 mViewModel.refreshBroadcasts(); 256 } 257 258 @Override onBackPressed()259 public void onBackPressed() { 260 Intent intent = new Intent(this, MainActivity.class); 261 intent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT); 262 startActivity(intent); 263 finish(); 264 } 265 } 266