• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  *  Copyright (c) 2020 The WebRTC project authors. All Rights Reserved.
3  *
4  *  Use of this source code is governed by a BSD-style license
5  *  that can be found in the LICENSE file in the root of the source
6  *  tree. An additional intellectual property rights grant can be found
7  *  in the file PATENTS.  All contributing project authors may
8  *  be found in the AUTHORS file in the root of the source tree.
9  */
10 
11 package org.webrtc.examples.androidvoip;
12 
13 import android.Manifest.permission;
14 import android.app.Activity;
15 import android.app.AlertDialog;
16 import android.content.Context;
17 import android.content.pm.PackageManager;
18 import android.os.Bundle;
19 import android.view.Gravity;
20 import android.view.View;
21 import android.widget.AdapterView;
22 import android.widget.ArrayAdapter;
23 import android.widget.Button;
24 import android.widget.EditText;
25 import android.widget.RelativeLayout;
26 import android.widget.ScrollView;
27 import android.widget.Spinner;
28 import android.widget.Switch;
29 import android.widget.TextView;
30 import android.widget.Toast;
31 import android.widget.ToggleButton;
32 import androidx.core.app.ActivityCompat;
33 import androidx.core.content.ContextCompat;
34 import java.util.ArrayList;
35 import java.util.Arrays;
36 import java.util.HashSet;
37 import java.util.List;
38 import java.util.Set;
39 import java.util.stream.Collectors;
40 import org.webrtc.ContextUtils;
41 
42 public class MainActivity extends Activity implements OnVoipClientTaskCompleted {
43   private static final int NUM_SUPPORTED_CODECS = 6;
44 
45   private VoipClient voipClient;
46   private List<String> supportedCodecs;
47   private boolean[] isDecoderSelected;
48   private Set<Integer> selectedDecoders;
49 
50   private Toast toast;
51   private ScrollView scrollView;
52   private TextView localIPAddressTextView;
53   private EditText localPortNumberEditText;
54   private EditText remoteIPAddressEditText;
55   private EditText remotePortNumberEditText;
56   private Spinner encoderSpinner;
57   private Button decoderSelectionButton;
58   private TextView decodersTextView;
59   private ToggleButton sessionButton;
60   private RelativeLayout switchLayout;
61   private Switch sendSwitch;
62   private Switch playoutSwitch;
63 
64   @Override
onCreate(Bundle savedInstance)65   protected void onCreate(Bundle savedInstance) {
66     ContextUtils.initialize(getApplicationContext());
67 
68     super.onCreate(savedInstance);
69     setContentView(R.layout.activity_main);
70 
71     System.loadLibrary("examples_androidvoip_jni");
72 
73     voipClient = new VoipClient(getApplicationContext(), this);
74     voipClient.getAndSetUpLocalIPAddress();
75     voipClient.getAndSetUpSupportedCodecs();
76 
77     isDecoderSelected = new boolean[NUM_SUPPORTED_CODECS];
78     selectedDecoders = new HashSet<>();
79 
80     toast = Toast.makeText(this, "", Toast.LENGTH_SHORT);
81 
82     scrollView = (ScrollView) findViewById(R.id.scroll_view);
83     localIPAddressTextView = (TextView) findViewById(R.id.local_ip_address_text_view);
84     localPortNumberEditText = (EditText) findViewById(R.id.local_port_number_edit_text);
85     remoteIPAddressEditText = (EditText) findViewById(R.id.remote_ip_address_edit_text);
86     remotePortNumberEditText = (EditText) findViewById(R.id.remote_port_number_edit_text);
87     encoderSpinner = (Spinner) findViewById(R.id.encoder_spinner);
88     decoderSelectionButton = (Button) findViewById(R.id.decoder_selection_button);
89     decodersTextView = (TextView) findViewById(R.id.decoders_text_view);
90     sessionButton = (ToggleButton) findViewById(R.id.session_button);
91     switchLayout = (RelativeLayout) findViewById(R.id.switch_layout);
92     sendSwitch = (Switch) findViewById(R.id.start_send_switch);
93     playoutSwitch = (Switch) findViewById(R.id.start_playout_switch);
94 
95     setUpSessionButton();
96     setUpSendAndPlayoutSwitch();
97   }
98 
setUpEncoderSpinner(List<String> supportedCodecs)99   private void setUpEncoderSpinner(List<String> supportedCodecs) {
100     ArrayAdapter<String> encoderAdapter =
101         new ArrayAdapter<String>(this, android.R.layout.simple_spinner_item, supportedCodecs);
102     encoderAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
103     encoderSpinner.setAdapter(encoderAdapter);
104     encoderSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
105       @Override
106       public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
107         voipClient.setEncoder((String) parent.getSelectedItem());
108       }
109       @Override
110       public void onNothingSelected(AdapterView<?> parent) {}
111     });
112   }
113 
getSelectedDecoders()114   private List<String> getSelectedDecoders() {
115     List<String> decoders = new ArrayList<>();
116     for (int i = 0; i < supportedCodecs.size(); i++) {
117       if (selectedDecoders.contains(i)) {
118         decoders.add(supportedCodecs.get(i));
119       }
120     }
121     return decoders;
122   }
123 
setUpDecoderSelectionButton(List<String> supportedCodecs)124   private void setUpDecoderSelectionButton(List<String> supportedCodecs) {
125     decoderSelectionButton.setOnClickListener((view) -> {
126       AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(this);
127       dialogBuilder.setTitle(R.string.dialog_title);
128 
129       // Populate multi choice items with supported decoders.
130       String[] supportedCodecsArray = supportedCodecs.toArray(new String[0]);
131       dialogBuilder.setMultiChoiceItems(
132           supportedCodecsArray, isDecoderSelected, (dialog, position, isChecked) -> {
133             if (isChecked) {
134               selectedDecoders.add(position);
135             } else if (!isChecked) {
136               selectedDecoders.remove(position);
137             }
138           });
139 
140       // "Ok" button.
141       dialogBuilder.setPositiveButton(R.string.ok_label, (dialog, position) -> {
142         List<String> decoders = getSelectedDecoders();
143         String result = decoders.stream().collect(Collectors.joining(", "));
144         if (result.isEmpty()) {
145           decodersTextView.setText(R.string.decoders_text_view_default);
146         } else {
147           decodersTextView.setText(result);
148         }
149         voipClient.setDecoders(decoders);
150       });
151 
152       // "Dismiss" button.
153       dialogBuilder.setNegativeButton(
154           R.string.dismiss_label, (dialog, position) -> { dialog.dismiss(); });
155 
156       // "Clear All" button.
157       dialogBuilder.setNeutralButton(R.string.clear_all_label, (dialog, position) -> {
158         Arrays.fill(isDecoderSelected, false);
159         selectedDecoders.clear();
160         decodersTextView.setText(R.string.decoders_text_view_default);
161       });
162 
163       AlertDialog dialog = dialogBuilder.create();
164       dialog.show();
165     });
166   }
167 
setUpSessionButton()168   private void setUpSessionButton() {
169     sessionButton.setOnCheckedChangeListener((button, isChecked) -> {
170       // Ask for permission on RECORD_AUDIO if not granted.
171       if (ContextCompat.checkSelfPermission(this, permission.RECORD_AUDIO)
172           != PackageManager.PERMISSION_GRANTED) {
173         String[] sList = {permission.RECORD_AUDIO};
174         ActivityCompat.requestPermissions(this, sList, 1);
175       }
176 
177       if (isChecked) {
178         // Order matters here, addresses have to be set before starting session
179         // before setting codec.
180         voipClient.setLocalAddress(localIPAddressTextView.getText().toString(),
181             Integer.parseInt(localPortNumberEditText.getText().toString()));
182         voipClient.setRemoteAddress(remoteIPAddressEditText.getText().toString(),
183             Integer.parseInt(remotePortNumberEditText.getText().toString()));
184         voipClient.startSession();
185         voipClient.setEncoder((String) encoderSpinner.getSelectedItem());
186         voipClient.setDecoders(getSelectedDecoders());
187       } else {
188         voipClient.stopSession();
189       }
190     });
191   }
192 
setUpSendAndPlayoutSwitch()193   private void setUpSendAndPlayoutSwitch() {
194     sendSwitch.setOnCheckedChangeListener((button, isChecked) -> {
195       if (isChecked) {
196         voipClient.startSend();
197       } else {
198         voipClient.stopSend();
199       }
200     });
201 
202     playoutSwitch.setOnCheckedChangeListener((button, isChecked) -> {
203       if (isChecked) {
204         voipClient.startPlayout();
205       } else {
206         voipClient.stopPlayout();
207       }
208     });
209   }
210 
setUpIPAddressEditTexts(String localIPAddress)211   private void setUpIPAddressEditTexts(String localIPAddress) {
212     if (localIPAddress.isEmpty()) {
213       showToast("Please check your network configuration");
214     } else {
215       localIPAddressTextView.setText(localIPAddress);
216       // By default remote IP address is the same as local IP address.
217       remoteIPAddressEditText.setText(localIPAddress);
218     }
219   }
220 
showToast(String message)221   private void showToast(String message) {
222     toast.cancel();
223     toast = Toast.makeText(this, message, Toast.LENGTH_SHORT);
224     toast.setGravity(Gravity.TOP, 0, 200);
225     toast.show();
226   }
227 
228   @Override
onDestroy()229   protected void onDestroy() {
230     voipClient.close();
231     voipClient = null;
232 
233     super.onDestroy();
234   }
235 
236   @Override
onGetLocalIPAddressCompleted(String localIPAddress)237   public void onGetLocalIPAddressCompleted(String localIPAddress) {
238     runOnUiThread(() -> { setUpIPAddressEditTexts(localIPAddress); });
239   }
240 
241   @Override
onGetSupportedCodecsCompleted(List<String> supportedCodecs)242   public void onGetSupportedCodecsCompleted(List<String> supportedCodecs) {
243     runOnUiThread(() -> {
244       this.supportedCodecs = supportedCodecs;
245       setUpEncoderSpinner(supportedCodecs);
246       setUpDecoderSelectionButton(supportedCodecs);
247     });
248   }
249 
250   @Override
onVoipClientInitializationCompleted(boolean isSuccessful)251   public void onVoipClientInitializationCompleted(boolean isSuccessful) {
252     runOnUiThread(() -> {
253       if (!isSuccessful) {
254         showToast("Error initializing audio device");
255       }
256     });
257   }
258 
259   @Override
onStartSessionCompleted(boolean isSuccessful)260   public void onStartSessionCompleted(boolean isSuccessful) {
261     runOnUiThread(() -> {
262       if (isSuccessful) {
263         showToast("Session started");
264         switchLayout.setVisibility(View.VISIBLE);
265         scrollView.post(() -> { scrollView.fullScroll(ScrollView.FOCUS_DOWN); });
266       } else {
267         showToast("Failed to start session");
268       }
269     });
270   }
271 
272   @Override
onStopSessionCompleted(boolean isSuccessful)273   public void onStopSessionCompleted(boolean isSuccessful) {
274     runOnUiThread(() -> {
275       if (isSuccessful) {
276         showToast("Session stopped");
277         // Set listeners to null so the checked state can be changed programmatically.
278         sendSwitch.setOnCheckedChangeListener(null);
279         playoutSwitch.setOnCheckedChangeListener(null);
280         sendSwitch.setChecked(false);
281         playoutSwitch.setChecked(false);
282         // Redo the switch listener setup.
283         setUpSendAndPlayoutSwitch();
284         switchLayout.setVisibility(View.GONE);
285       } else {
286         showToast("Failed to stop session");
287       }
288     });
289   }
290 
291   @Override
onStartSendCompleted(boolean isSuccessful)292   public void onStartSendCompleted(boolean isSuccessful) {
293     runOnUiThread(() -> {
294       if (isSuccessful) {
295         showToast("Started sending");
296       } else {
297         showToast("Error initializing microphone");
298       }
299     });
300   }
301 
302   @Override
onStopSendCompleted(boolean isSuccessful)303   public void onStopSendCompleted(boolean isSuccessful) {
304     runOnUiThread(() -> {
305       if (isSuccessful) {
306         showToast("Stopped sending");
307       } else {
308         showToast("Microphone termination failed");
309       }
310     });
311   }
312 
313   @Override
onStartPlayoutCompleted(boolean isSuccessful)314   public void onStartPlayoutCompleted(boolean isSuccessful) {
315     runOnUiThread(() -> {
316       if (isSuccessful) {
317         showToast("Started playout");
318       } else {
319         showToast("Error initializing speaker");
320       }
321     });
322   }
323 
324   @Override
onStopPlayoutCompleted(boolean isSuccessful)325   public void onStopPlayoutCompleted(boolean isSuccessful) {
326     runOnUiThread(() -> {
327       if (isSuccessful) {
328         showToast("Stopped playout");
329       } else {
330         showToast("Speaker termination failed");
331       }
332     });
333   }
334 
335   @Override
onUninitializedVoipClient()336   public void onUninitializedVoipClient() {
337     runOnUiThread(() -> { showToast("Voip client is uninitialized"); });
338   }
339 }
340