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