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.BluetoothLeAudioContentMetadata;
21 import android.bluetooth.BluetoothLeBroadcastMetadata;
22 import android.bluetooth.BluetoothLeBroadcastSettings;
23 import android.bluetooth.BluetoothLeBroadcastSubgroupSettings;
24 import android.content.Intent;
25 import android.content.SharedPreferences;
26 import android.os.Bundle;
27 import android.util.Log;
28 import android.view.LayoutInflater;
29 import android.view.View;
30 import android.widget.Button;
31 import android.widget.CheckBox;
32 import android.widget.EditText;
33 import android.widget.NumberPicker;
34 import android.widget.TextView;
35 import android.widget.Toast;
36 
37 import androidx.appcompat.app.AlertDialog;
38 import androidx.appcompat.app.AppCompatActivity;
39 import androidx.lifecycle.ViewModelProviders;
40 import androidx.recyclerview.widget.LinearLayoutManager;
41 import androidx.recyclerview.widget.RecyclerView;
42 
43 import com.google.android.material.floatingactionbutton.FloatingActionButton;
44 
45 import java.io.ByteArrayOutputStream;
46 import java.nio.charset.StandardCharsets;
47 import java.util.ArrayList;
48 import java.util.List;
49 import java.util.Map;
50 
51 public class BroadcasterActivity extends AppCompatActivity {
52     private BroadcasterViewModel mViewModel;
53 
54     private final String BROADCAST_PREFS_KEY = "BROADCAST_PREFS_KEY";
55     private final String PREF_SEP = ":";
56     private final String VALUE_NOT_SET = "undefined";
57 
58     @Override
onCreate(Bundle savedInstanceState)59     protected void onCreate(Bundle savedInstanceState) {
60         super.onCreate(savedInstanceState);
61         setContentView(R.layout.broadcaster_activity);
62 
63         FloatingActionButton fab = findViewById(R.id.broadcast_fab);
64         fab.setOnClickListener(
65                 view -> {
66                     if (mViewModel.getBroadcastCount() < mViewModel.getMaximumNumberOfBroadcast()) {
67                         // Start Dialog with the broadcast input details
68                         AlertDialog.Builder alert = new AlertDialog.Builder(this);
69                         LayoutInflater inflater = getLayoutInflater();
70                         alert.setTitle("Add the Broadcast:");
71 
72                         View alertView =
73                                 inflater.inflate(R.layout.broadcaster_add_broadcast_dialog, null);
74                         final EditText code_input_text =
75                                 alertView.findViewById(R.id.broadcast_code_input);
76                         final EditText program_info =
77                                 alertView.findViewById(R.id.broadcast_program_info_input);
78                         final NumberPicker contextPicker =
79                                 alertView.findViewById(R.id.context_picker);
80                         final EditText broadcast_name =
81                                 alertView.findViewById(R.id.broadcast_name_input);
82                         final CheckBox publicCheckbox =
83                                 alertView.findViewById(R.id.is_public_checkbox);
84                         final EditText public_content =
85                                 alertView.findViewById(R.id.broadcast_public_content_input);
86                         // Add context type selector
87                         contextPicker.setMinValue(1);
88                         contextPicker.setMaxValue(
89                                 alertView
90                                                 .getResources()
91                                                 .getStringArray(R.array.content_types)
92                                                 .length
93                                         - 1);
94                         contextPicker.setDisplayedValues(
95                                 alertView.getResources().getStringArray(R.array.content_types));
96                         final Button loadButton = alertView.findViewById(R.id.load_button);
97                         loadButton.setOnClickListener(
98                                 new View.OnClickListener() {
99                                     @Override
100                                     public void onClick(View v) {
101                                         showSelectSavedBroadcastAlert(
102                                                 code_input_text,
103                                                 program_info,
104                                                 contextPicker,
105                                                 broadcast_name,
106                                                 publicCheckbox,
107                                                 public_content);
108                                     }
109                                 });
110                         final Button clearButton = alertView.findViewById(R.id.clear_button);
111                         clearButton.setOnClickListener(
112                                 new View.OnClickListener() {
113                                     @Override
114                                     public void onClick(View v) {
115                                         SharedPreferences broadcastsPrefs =
116                                                 getSharedPreferences(BROADCAST_PREFS_KEY, 0);
117                                         SharedPreferences.Editor editor = broadcastsPrefs.edit();
118                                         editor.clear();
119                                         editor.commit();
120                                         Toast.makeText(
121                                                         BroadcasterActivity.this,
122                                                         "Saved broadcasts cleared",
123                                                         Toast.LENGTH_SHORT)
124                                                 .show();
125                                     }
126                                 });
127                         alert.setView(alertView)
128                                 .setNegativeButton(
129                                         "Cancel",
130                                         (dialog, which) -> {
131                                             // Do nothing
132                                         })
133                                 .setNeutralButton(
134                                         "Start",
135                                         (dialog, which) -> {
136                                             BluetoothLeBroadcastSettings broadcastSettings =
137                                                     createBroadcastSettingsFromUI(
138                                                             program_info.getText().toString(),
139                                                             public_content.getText().toString(),
140                                                             contextPicker.getValue(),
141                                                             publicCheckbox.isChecked(),
142                                                             broadcast_name.getText().toString(),
143                                                             code_input_text.getText().toString());
144 
145                                             if (mViewModel.startBroadcast(broadcastSettings))
146                                                 Toast.makeText(
147                                                                 BroadcasterActivity.this,
148                                                                 "Broadcast was created.",
149                                                                 Toast.LENGTH_SHORT)
150                                                         .show();
151                                         })
152                                 .setPositiveButton(
153                                         "Start & save",
154                                         (dialog, which) -> {
155                                             BluetoothLeBroadcastSettings broadcastSettings =
156                                                     createBroadcastSettingsFromUI(
157                                                             program_info.getText().toString(),
158                                                             public_content.getText().toString(),
159                                                             contextPicker.getValue(),
160                                                             publicCheckbox.isChecked(),
161                                                             broadcast_name.getText().toString(),
162                                                             code_input_text.getText().toString());
163 
164                                             if (mViewModel.startBroadcast(broadcastSettings)) {
165                                                 // Save only if started successfully
166                                                 if (saveBroadcastToSharedPref(
167                                                         program_info.getText().toString(),
168                                                         public_content.getText().toString(),
169                                                         contextPicker.getValue(),
170                                                         publicCheckbox.isChecked(),
171                                                         broadcast_name.getText().toString(),
172                                                         code_input_text.getText().toString())) {
173                                                     Toast.makeText(
174                                                                     BroadcasterActivity.this,
175                                                                     "Broadcast was created and"
176                                                                             + " saved",
177                                                                     Toast.LENGTH_SHORT)
178                                                             .show();
179                                                 } else {
180                                                     Toast.makeText(
181                                                                     BroadcasterActivity.this,
182                                                                     "Broadcast was created, but not"
183                                                                             + " saved (already"
184                                                                             + " exists).",
185                                                                     Toast.LENGTH_SHORT)
186                                                             .show();
187                                                 }
188                                             }
189                                         });
190 
191                         alert.show();
192                     } else {
193                         Toast.makeText(
194                                         BroadcasterActivity.this,
195                                         "Maximum number of broadcasts reached: "
196                                                 + Integer.valueOf(
197                                                                 mViewModel
198                                                                         .getMaximumNumberOfBroadcast())
199                                                         .toString(),
200                                         Toast.LENGTH_SHORT)
201                                 .show();
202                     }
203                 });
204 
205         RecyclerView recyclerView = findViewById(R.id.broadcaster_recycle_view);
206         recyclerView.setLayoutManager(new LinearLayoutManager(this));
207         recyclerView.setHasFixedSize(true);
208 
209         final BroadcastItemsAdapter itemsAdapter = new BroadcastItemsAdapter();
210         itemsAdapter.setOnItemClickListener(
211                 broadcastId -> {
212                     AlertDialog.Builder alert = new AlertDialog.Builder(this);
213                     alert.setTitle("Broadcast Info:");
214 
215                     // Load and fill in the metadata layout
216                     final View metaLayout =
217                             getLayoutInflater().inflate(R.layout.broadcast_metadata, null);
218                     alert.setView(metaLayout);
219 
220                     BluetoothLeBroadcastMetadata metadata = null;
221                     for (BluetoothLeBroadcastMetadata b : mViewModel.getAllBroadcastMetadata()) {
222                         if (b.getBroadcastId() == broadcastId) {
223                             metadata = b;
224                             break;
225                         }
226                     }
227 
228                     if (metadata != null) {
229                         TextView addr_text = metaLayout.findViewById(R.id.device_addr_text);
230                         addr_text.setText(
231                                 "Device Address: " + metadata.getSourceDevice().toString());
232 
233                         addr_text = metaLayout.findViewById(R.id.adv_sid_text);
234                         addr_text.setText("Advertising SID: " + metadata.getSourceAdvertisingSid());
235 
236                         addr_text = metaLayout.findViewById(R.id.pasync_interval_text);
237                         addr_text.setText("Pa Sync Interval: " + metadata.getPaSyncInterval());
238 
239                         addr_text = metaLayout.findViewById(R.id.is_encrypted_text);
240                         addr_text.setText(
241                                 "Is Encrypted: " + (metadata.isEncrypted() ? "Yes" : "No"));
242 
243                         boolean isPublic = metadata.isPublicBroadcast();
244                         addr_text = metaLayout.findViewById(R.id.is_public_text);
245                         addr_text.setText("Is Public Broadcast: " + (isPublic ? "Yes" : "No"));
246 
247                         String name = metadata.getBroadcastName();
248                         addr_text = metaLayout.findViewById(R.id.broadcast_name_text);
249                         if (isPublic && name != null) {
250                             addr_text.setText("Public Name: " + name);
251                         } else {
252                             addr_text.setVisibility(View.INVISIBLE);
253                         }
254 
255                         BluetoothLeAudioContentMetadata publicMetadata =
256                                 metadata.getPublicBroadcastMetadata();
257                         addr_text = metaLayout.findViewById(R.id.public_program_info_text);
258                         if (isPublic && publicMetadata != null) {
259                             addr_text.setText("Public Info: " + publicMetadata.getProgramInfo());
260                         } else {
261                             addr_text.setVisibility(View.INVISIBLE);
262                         }
263 
264                         byte[] code = metadata.getBroadcastCode();
265                         addr_text = metaLayout.findViewById(R.id.broadcast_code_text);
266                         if (code != null) {
267                             addr_text.setText(
268                                     "Broadcast Code: " + new String(code, StandardCharsets.UTF_8));
269                         } else {
270                             addr_text.setVisibility(View.INVISIBLE);
271                         }
272 
273                         addr_text = metaLayout.findViewById(R.id.presentation_delay_text);
274                         addr_text.setText(
275                                 "Presentation Delay: "
276                                         + metadata.getPresentationDelayMicros()
277                                         + " [us]");
278                     }
279 
280                     alert.setNeutralButton(
281                             "Stop",
282                             (dialog, which) -> {
283                                 mViewModel.stopBroadcast(broadcastId);
284                             });
285                     alert.setPositiveButton(
286                             "Modify",
287                             (dialog, which) -> {
288                                 // Open activity for progam info
289                                 AlertDialog.Builder modifyAlert = new AlertDialog.Builder(this);
290                                 modifyAlert.setTitle("Modify the Broadcast:");
291 
292                                 LayoutInflater inflater = getLayoutInflater();
293                                 View alertView =
294                                         inflater.inflate(
295                                                 R.layout.broadcaster_add_broadcast_dialog, null);
296                                 EditText program_info_input_text =
297                                         alertView.findViewById(R.id.broadcast_program_info_input);
298                                 EditText broadcast_name_input_text =
299                                         alertView.findViewById(R.id.broadcast_name_input);
300                                 EditText public_content_input_text =
301                                         alertView.findViewById(R.id.broadcast_public_content_input);
302 
303                                 // The Code cannot be changed, so just hide it
304                                 final EditText code_input_text =
305                                         alertView.findViewById(R.id.broadcast_code_input);
306                                 code_input_text.setVisibility(View.GONE);
307                                 // Public broadcast flag cannot be changed, so just hide it
308                                 final CheckBox public_input_checkbox =
309                                         alertView.findViewById(R.id.is_public_checkbox);
310                                 public_input_checkbox.setVisibility(View.GONE);
311                                 // Context picker cannot be changed, so just hide it
312                                 final NumberPicker content_input_text =
313                                         alertView.findViewById(R.id.context_picker);
314                                 content_input_text.setVisibility(View.GONE);
315                                 // Can't load when modify, so just hide buttons
316                                 final Button loadButton = alertView.findViewById(R.id.load_button);
317                                 loadButton.setVisibility(View.GONE);
318                                 final Button clearButton =
319                                         alertView.findViewById(R.id.clear_button);
320                                 clearButton.setVisibility(View.GONE);
321 
322                                 modifyAlert
323                                         .setView(alertView)
324                                         .setNegativeButton(
325                                                 "Cancel",
326                                                 (modifyDialog, modifyWhich) -> {
327                                                     // Do nothing
328                                                 })
329                                         .setPositiveButton(
330                                                 "Update",
331                                                 (modifyDialog, modifyWhich) -> {
332                                                     BluetoothLeAudioContentMetadata.Builder
333                                                             contentBuilder =
334                                                                     new BluetoothLeAudioContentMetadata
335                                                                             .Builder();
336                                                     String programInfo =
337                                                             program_info_input_text
338                                                                     .getText()
339                                                                     .toString();
340                                                     if (!programInfo.isEmpty()) {
341                                                         contentBuilder.setProgramInfo(programInfo);
342                                                     }
343 
344                                                     final BluetoothLeAudioContentMetadata.Builder
345                                                             publicContentBuilder =
346                                                                     new BluetoothLeAudioContentMetadata
347                                                                             .Builder();
348                                                     final String publicContent =
349                                                             public_content_input_text
350                                                                     .getText()
351                                                                     .toString();
352                                                     if (!publicContent.isEmpty()) {
353                                                         publicContentBuilder.setProgramInfo(
354                                                                 publicContent);
355                                                     }
356 
357                                                     BluetoothLeBroadcastSubgroupSettings.Builder
358                                                             subgroupBuilder =
359                                                                     new BluetoothLeBroadcastSubgroupSettings
360                                                                                     .Builder()
361                                                                             .setContentMetadata(
362                                                                                     contentBuilder
363                                                                                             .build());
364 
365                                                     final String broadcastName =
366                                                             broadcast_name_input_text
367                                                                     .getText()
368                                                                     .toString();
369                                                     BluetoothLeBroadcastSettings.Builder builder =
370                                                             new BluetoothLeBroadcastSettings
371                                                                             .Builder()
372                                                                     .setBroadcastName(
373                                                                             broadcastName.isEmpty()
374                                                                                     ? null
375                                                                                     : broadcastName)
376                                                                     .setPublicBroadcastMetadata(
377                                                                             publicContentBuilder
378                                                                                     .build());
379 
380                                                     // builder expect at least one subgroup setting
381                                                     builder.addSubgroupSettings(
382                                                             subgroupBuilder.build());
383 
384                                                     if (mViewModel.updateBroadcast(
385                                                             broadcastId, builder.build()))
386                                                         Toast.makeText(
387                                                                         BroadcasterActivity.this,
388                                                                         "Broadcast was updated.",
389                                                                         Toast.LENGTH_SHORT)
390                                                                 .show();
391                                                 });
392 
393                                 modifyAlert.show();
394                             });
395 
396                     alert.show();
397                     Log.d("CC", "Num broadcasts: " + mViewModel.getBroadcastCount());
398                 });
399         recyclerView.setAdapter(itemsAdapter);
400 
401         // Get the initial state
402         mViewModel = ViewModelProviders.of(this).get(BroadcasterViewModel.class);
403         final List<BluetoothLeBroadcastMetadata> metadata = mViewModel.getAllBroadcastMetadata();
404         itemsAdapter.updateBroadcastsMetadata(metadata.isEmpty() ? new ArrayList<>() : metadata);
405 
406         // Put a watch on updates
407         mViewModel
408                 .getBroadcastUpdateMetadataLive()
409                 .observe(
410                         this,
411                         audioBroadcast -> {
412                             itemsAdapter.updateBroadcastMetadata(audioBroadcast);
413 
414                             Toast.makeText(
415                                             BroadcasterActivity.this,
416                                             "Updated broadcast " + audioBroadcast.getBroadcastId(),
417                                             Toast.LENGTH_SHORT)
418                                     .show();
419                         });
420 
421         // Put a watch on any error reports
422         mViewModel
423                 .getBroadcastStatusMutableLive()
424                 .observe(
425                         this,
426                         msg -> {
427                             Toast.makeText(BroadcasterActivity.this, msg, Toast.LENGTH_SHORT)
428                                     .show();
429                         });
430 
431         // Put a watch on broadcast playback states
432         mViewModel
433                 .getBroadcastPlaybackStartedMutableLive()
434                 .observe(
435                         this,
436                         reasonAndBidPair -> {
437                             Toast.makeText(
438                                             BroadcasterActivity.this,
439                                             "Playing broadcast "
440                                                     + reasonAndBidPair.second
441                                                     + ", reason "
442                                                     + reasonAndBidPair.first,
443                                             Toast.LENGTH_SHORT)
444                                     .show();
445 
446                             itemsAdapter.updateBroadcastPlayback(reasonAndBidPair.second, true);
447                         });
448 
449         mViewModel
450                 .getBroadcastPlaybackStoppedMutableLive()
451                 .observe(
452                         this,
453                         reasonAndBidPair -> {
454                             Toast.makeText(
455                                             BroadcasterActivity.this,
456                                             "Paused broadcast "
457                                                     + reasonAndBidPair.second
458                                                     + ", reason "
459                                                     + reasonAndBidPair.first,
460                                             Toast.LENGTH_SHORT)
461                                     .show();
462 
463                             itemsAdapter.updateBroadcastPlayback(reasonAndBidPair.second, false);
464                         });
465 
466         mViewModel
467                 .getBroadcastAddedMutableLive()
468                 .observe(
469                         this,
470                         broadcastId -> {
471                             itemsAdapter.addBroadcasts(broadcastId);
472 
473                             Toast.makeText(
474                                             BroadcasterActivity.this,
475                                             "Broadcast was added broadcastId: " + broadcastId,
476                                             Toast.LENGTH_SHORT)
477                                     .show();
478                         });
479 
480         // Put a watch on broadcast removal
481         mViewModel
482                 .getBroadcastRemovedMutableLive()
483                 .observe(
484                         this,
485                         reasonAndBidPair -> {
486                             itemsAdapter.removeBroadcast(reasonAndBidPair.second);
487 
488                             Toast.makeText(
489                                             BroadcasterActivity.this,
490                                             "Broadcast was removed "
491                                                     + " broadcastId: "
492                                                     + reasonAndBidPair.second
493                                                     + ", reason: "
494                                                     + reasonAndBidPair.first,
495                                             Toast.LENGTH_SHORT)
496                                     .show();
497                         });
498 
499         // Prevent destruction when loses focus
500         this.setFinishOnTouchOutside(false);
501     }
502 
503     @Override
onBackPressed()504     public void onBackPressed() {
505         Intent intent = new Intent(this, MainActivity.class);
506         intent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
507         startActivity(intent);
508     }
509 
createBroadcastSettingsFromUI( String programInfo, String publicContent, int contextTypeUI, boolean isPublic, String broadcastName, String broadcastCode)510     private BluetoothLeBroadcastSettings createBroadcastSettingsFromUI(
511             String programInfo,
512             String publicContent,
513             int contextTypeUI,
514             boolean isPublic,
515             String broadcastName,
516             String broadcastCode) {
517 
518         final BluetoothLeAudioContentMetadata.Builder contentBuilder =
519                 new BluetoothLeAudioContentMetadata.Builder();
520         if (!programInfo.isEmpty()) {
521             contentBuilder.setProgramInfo(programInfo);
522         }
523 
524         final BluetoothLeAudioContentMetadata.Builder publicContentBuilder =
525                 new BluetoothLeAudioContentMetadata.Builder();
526         if (!publicContent.isEmpty()) {
527             publicContentBuilder.setProgramInfo(publicContent);
528         }
529 
530         // Extract raw metadata
531         byte[] metaBuffer = contentBuilder.build().getRawMetadata();
532         ByteArrayOutputStream stream = new ByteArrayOutputStream();
533         stream.write(metaBuffer, 0, metaBuffer.length);
534 
535         // Extend raw metadata with context type
536         final int contextValue = 1 << (contextTypeUI - 1);
537         stream.write((byte) 0x03); // Length
538         stream.write((byte) 0x02); // Type for the Streaming Audio Context
539         stream.write((byte) (contextValue & 0x00FF)); // Value LSB
540         stream.write((byte) ((contextValue & 0xFF00) >> 8)); // Value MSB
541 
542         BluetoothLeBroadcastSubgroupSettings.Builder subgroupBuilder =
543                 new BluetoothLeBroadcastSubgroupSettings.Builder()
544                         .setContentMetadata(
545                                 BluetoothLeAudioContentMetadata.fromRawBytes(stream.toByteArray()));
546         BluetoothLeBroadcastSettings.Builder builder =
547                 new BluetoothLeBroadcastSettings.Builder()
548                         .setPublicBroadcast(isPublic)
549                         .setBroadcastName(broadcastName.isEmpty() ? null : broadcastName)
550                         .setBroadcastCode(broadcastCode.isEmpty() ? null : broadcastCode.getBytes())
551                         .setPublicBroadcastMetadata(publicContentBuilder.build());
552 
553         // builder expect at least one subgroup setting
554         builder.addSubgroupSettings(subgroupBuilder.build());
555         return builder.build();
556     }
557 
saveBroadcastToSharedPref( String programInfo, String publicContent, int contextTypeUI, boolean isPublic, String broadcastName, String broadcastCode)558     private boolean saveBroadcastToSharedPref(
559             String programInfo,
560             String publicContent,
561             int contextTypeUI,
562             boolean isPublic,
563             String broadcastName,
564             String broadcastCode) {
565 
566         SharedPreferences broadcastsPrefs = getSharedPreferences(BROADCAST_PREFS_KEY, 0);
567         if (broadcastsPrefs.contains(broadcastName)) {
568             return false;
569         } else {
570             String toStore =
571                     programInfo
572                             + PREF_SEP
573                             + publicContent
574                             + PREF_SEP
575                             + contextTypeUI
576                             + PREF_SEP
577                             + isPublic
578                             + PREF_SEP
579                             + broadcastName
580                             + PREF_SEP;
581             if (broadcastCode.isEmpty()) {
582                 toStore += VALUE_NOT_SET;
583             } else {
584                 toStore += broadcastCode;
585             }
586             SharedPreferences.Editor editor = broadcastsPrefs.edit();
587             editor.putString(broadcastName, toStore);
588             editor.commit();
589         }
590         return true;
591     }
592 
showSelectSavedBroadcastAlert( final EditText code_input_text, final EditText program_info, final NumberPicker contextPicker, final EditText broadcast_name, final CheckBox publicCheckbox, final EditText public_content)593     private final void showSelectSavedBroadcastAlert(
594             final EditText code_input_text,
595             final EditText program_info,
596             final NumberPicker contextPicker,
597             final EditText broadcast_name,
598             final CheckBox publicCheckbox,
599             final EditText public_content) {
600 
601         ArrayList<String> listSavedBroadcast = new ArrayList();
602 
603         final SharedPreferences broadcastsPrefs = getSharedPreferences(BROADCAST_PREFS_KEY, 0);
604         Map<String, ?> allEntries = broadcastsPrefs.getAll();
605         for (Map.Entry<String, ?> entry : allEntries.entrySet()) {
606             listSavedBroadcast.add(entry.getKey());
607         }
608 
609         AlertDialog.Builder alertDialog = new AlertDialog.Builder(this);
610         alertDialog.setTitle("Select saved broadcast");
611         alertDialog
612                 .setSingleChoiceItems(
613                         listSavedBroadcast.toArray(new String[listSavedBroadcast.size()]),
614                         0,
615                         (dialog, which) -> {
616                             String[] broadcastValues =
617                                     broadcastsPrefs
618                                             .getString(listSavedBroadcast.get(which), "")
619                                             .split(PREF_SEP);
620                             if (broadcastValues.length != 6) {
621                                 Toast.makeText(
622                                                 this,
623                                                 "Could not retrieve "
624                                                         + listSavedBroadcast.get(which)
625                                                         + ".",
626                                                 Toast.LENGTH_SHORT)
627                                         .show();
628                                 return;
629                             }
630                             program_info.setText(broadcastValues[0]);
631                             public_content.setText(broadcastValues[1]);
632                             contextPicker.setValue(Integer.valueOf(broadcastValues[2]));
633                             publicCheckbox.setChecked(Boolean.parseBoolean(broadcastValues[3]));
634                             broadcast_name.setText(broadcastValues[4]);
635                             if (!VALUE_NOT_SET.equals(broadcastValues[5])) {
636                                 code_input_text.setText(broadcastValues[5]);
637                             }
638                             dialog.dismiss();
639                         })
640                 .setNegativeButton("Cancel", (dialog, which) -> {});
641         AlertDialog savedBroadcastsAlertDialog = alertDialog.create();
642         savedBroadcastsAlertDialog.show();
643     }
644 }
645