1 /*
2  *  Copyright 2014 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.appspot.apprtc;
12 
13 import android.app.Activity;
14 import android.app.AlertDialog;
15 import android.content.DialogInterface;
16 import android.content.Intent;
17 import android.content.SharedPreferences;
18 import android.net.Uri;
19 import android.os.Bundle;
20 import android.preference.PreferenceManager;
21 import android.util.Log;
22 import android.view.KeyEvent;
23 import android.view.Menu;
24 import android.view.MenuItem;
25 import android.view.View;
26 import android.view.View.OnClickListener;
27 import android.view.inputmethod.EditorInfo;
28 import android.webkit.URLUtil;
29 import android.widget.AdapterView;
30 import android.widget.ArrayAdapter;
31 import android.widget.EditText;
32 import android.widget.ImageButton;
33 import android.widget.ListView;
34 import android.widget.TextView;
35 
36 import org.json.JSONArray;
37 import org.json.JSONException;
38 
39 import java.util.ArrayList;
40 import java.util.Random;
41 
42 /**
43  * Handles the initial setup where the user selects which room to join.
44  */
45 public class ConnectActivity extends Activity {
46   private static final String TAG = "ConnectActivity";
47   private static final int CONNECTION_REQUEST = 1;
48   private static boolean commandLineRun = false;
49 
50   private ImageButton addRoomButton;
51   private ImageButton removeRoomButton;
52   private ImageButton connectButton;
53   private ImageButton connectLoopbackButton;
54   private EditText roomEditText;
55   private ListView roomListView;
56   private SharedPreferences sharedPref;
57   private String keyprefVideoCallEnabled;
58   private String keyprefResolution;
59   private String keyprefFps;
60   private String keyprefCaptureQualitySlider;
61   private String keyprefVideoBitrateType;
62   private String keyprefVideoBitrateValue;
63   private String keyprefVideoCodec;
64   private String keyprefAudioBitrateType;
65   private String keyprefAudioBitrateValue;
66   private String keyprefAudioCodec;
67   private String keyprefHwCodecAcceleration;
68   private String keyprefCaptureToTexture;
69   private String keyprefNoAudioProcessingPipeline;
70   private String keyprefAecDump;
71   private String keyprefOpenSLES;
72   private String keyprefDisplayHud;
73   private String keyprefTracing;
74   private String keyprefRoomServerUrl;
75   private String keyprefRoom;
76   private String keyprefRoomList;
77   private ArrayList<String> roomList;
78   private ArrayAdapter<String> adapter;
79 
80   @Override
onCreate(Bundle savedInstanceState)81   public void onCreate(Bundle savedInstanceState) {
82     super.onCreate(savedInstanceState);
83 
84     // Get setting keys.
85     PreferenceManager.setDefaultValues(this, R.xml.preferences, false);
86     sharedPref = PreferenceManager.getDefaultSharedPreferences(this);
87     keyprefVideoCallEnabled = getString(R.string.pref_videocall_key);
88     keyprefResolution = getString(R.string.pref_resolution_key);
89     keyprefFps = getString(R.string.pref_fps_key);
90     keyprefCaptureQualitySlider = getString(R.string.pref_capturequalityslider_key);
91     keyprefVideoBitrateType = getString(R.string.pref_startvideobitrate_key);
92     keyprefVideoBitrateValue = getString(R.string.pref_startvideobitratevalue_key);
93     keyprefVideoCodec = getString(R.string.pref_videocodec_key);
94     keyprefHwCodecAcceleration = getString(R.string.pref_hwcodec_key);
95     keyprefCaptureToTexture = getString(R.string.pref_capturetotexture_key);
96     keyprefAudioBitrateType = getString(R.string.pref_startaudiobitrate_key);
97     keyprefAudioBitrateValue = getString(R.string.pref_startaudiobitratevalue_key);
98     keyprefAudioCodec = getString(R.string.pref_audiocodec_key);
99     keyprefNoAudioProcessingPipeline = getString(R.string.pref_noaudioprocessing_key);
100     keyprefAecDump = getString(R.string.pref_aecdump_key);
101     keyprefOpenSLES = getString(R.string.pref_opensles_key);
102     keyprefDisplayHud = getString(R.string.pref_displayhud_key);
103     keyprefTracing = getString(R.string.pref_tracing_key);
104     keyprefRoomServerUrl = getString(R.string.pref_room_server_url_key);
105     keyprefRoom = getString(R.string.pref_room_key);
106     keyprefRoomList = getString(R.string.pref_room_list_key);
107 
108     setContentView(R.layout.activity_connect);
109 
110     roomEditText = (EditText) findViewById(R.id.room_edittext);
111     roomEditText.setOnEditorActionListener(
112       new TextView.OnEditorActionListener() {
113         @Override
114         public boolean onEditorAction(
115             TextView textView, int i, KeyEvent keyEvent) {
116           if (i == EditorInfo.IME_ACTION_DONE) {
117             addRoomButton.performClick();
118             return true;
119           }
120           return false;
121         }
122     });
123     roomEditText.requestFocus();
124 
125     roomListView = (ListView) findViewById(R.id.room_listview);
126     roomListView.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
127 
128     addRoomButton = (ImageButton) findViewById(R.id.add_room_button);
129     addRoomButton.setOnClickListener(addRoomListener);
130     removeRoomButton = (ImageButton) findViewById(R.id.remove_room_button);
131     removeRoomButton.setOnClickListener(removeRoomListener);
132     connectButton = (ImageButton) findViewById(R.id.connect_button);
133     connectButton.setOnClickListener(connectListener);
134     connectLoopbackButton =
135         (ImageButton) findViewById(R.id.connect_loopback_button);
136     connectLoopbackButton.setOnClickListener(connectListener);
137 
138     // If an implicit VIEW intent is launching the app, go directly to that URL.
139     final Intent intent = getIntent();
140     if ("android.intent.action.VIEW".equals(intent.getAction())
141         && !commandLineRun) {
142       commandLineRun = true;
143       boolean loopback = intent.getBooleanExtra(
144           CallActivity.EXTRA_LOOPBACK, false);
145       int runTimeMs = intent.getIntExtra(
146           CallActivity.EXTRA_RUNTIME, 0);
147       String room = sharedPref.getString(keyprefRoom, "");
148       roomEditText.setText(room);
149       connectToRoom(loopback, runTimeMs);
150       return;
151     }
152   }
153 
154   @Override
onCreateOptionsMenu(Menu menu)155   public boolean onCreateOptionsMenu(Menu menu) {
156     getMenuInflater().inflate(R.menu.connect_menu, menu);
157     return true;
158   }
159 
160   @Override
onOptionsItemSelected(MenuItem item)161   public boolean onOptionsItemSelected(MenuItem item) {
162     // Handle presses on the action bar items.
163     if (item.getItemId() == R.id.action_settings) {
164       Intent intent = new Intent(this, SettingsActivity.class);
165       startActivity(intent);
166       return true;
167     } else {
168       return super.onOptionsItemSelected(item);
169     }
170   }
171 
172   @Override
onPause()173   public void onPause() {
174     super.onPause();
175     String room = roomEditText.getText().toString();
176     String roomListJson = new JSONArray(roomList).toString();
177     SharedPreferences.Editor editor = sharedPref.edit();
178     editor.putString(keyprefRoom, room);
179     editor.putString(keyprefRoomList, roomListJson);
180     editor.commit();
181   }
182 
183   @Override
onResume()184   public void onResume() {
185     super.onResume();
186     String room = sharedPref.getString(keyprefRoom, "");
187     roomEditText.setText(room);
188     roomList = new ArrayList<String>();
189     String roomListJson = sharedPref.getString(keyprefRoomList, null);
190     if (roomListJson != null) {
191       try {
192         JSONArray jsonArray = new JSONArray(roomListJson);
193         for (int i = 0; i < jsonArray.length(); i++) {
194           roomList.add(jsonArray.get(i).toString());
195         }
196       } catch (JSONException e) {
197         Log.e(TAG, "Failed to load room list: " + e.toString());
198       }
199     }
200     adapter = new ArrayAdapter<String>(
201         this, android.R.layout.simple_list_item_1, roomList);
202     roomListView.setAdapter(adapter);
203     if (adapter.getCount() > 0) {
204       roomListView.requestFocus();
205       roomListView.setItemChecked(0, true);
206     }
207   }
208 
209   @Override
onActivityResult( int requestCode, int resultCode, Intent data)210   protected void onActivityResult(
211       int requestCode, int resultCode, Intent data) {
212     if (requestCode == CONNECTION_REQUEST && commandLineRun) {
213       Log.d(TAG, "Return: " + resultCode);
214       setResult(resultCode);
215       commandLineRun = false;
216       finish();
217     }
218   }
219 
220   private final OnClickListener connectListener = new OnClickListener() {
221     @Override
222     public void onClick(View view) {
223       boolean loopback = false;
224       if (view.getId() == R.id.connect_loopback_button) {
225         loopback = true;
226       }
227       commandLineRun = false;
228       connectToRoom(loopback, 0);
229     }
230   };
231 
connectToRoom(boolean loopback, int runTimeMs)232   private void connectToRoom(boolean loopback, int runTimeMs) {
233     // Get room name (random for loopback).
234     String roomId;
235     if (loopback) {
236       roomId = Integer.toString((new Random()).nextInt(100000000));
237     } else {
238       roomId = getSelectedItem();
239       if (roomId == null) {
240         roomId = roomEditText.getText().toString();
241       }
242     }
243 
244     String roomUrl = sharedPref.getString(
245         keyprefRoomServerUrl,
246         getString(R.string.pref_room_server_url_default));
247 
248     // Video call enabled flag.
249     boolean videoCallEnabled = sharedPref.getBoolean(keyprefVideoCallEnabled,
250         Boolean.valueOf(getString(R.string.pref_videocall_default)));
251 
252     // Get default codecs.
253     String videoCodec = sharedPref.getString(keyprefVideoCodec,
254         getString(R.string.pref_videocodec_default));
255     String audioCodec = sharedPref.getString(keyprefAudioCodec,
256         getString(R.string.pref_audiocodec_default));
257 
258     // Check HW codec flag.
259     boolean hwCodec = sharedPref.getBoolean(keyprefHwCodecAcceleration,
260         Boolean.valueOf(getString(R.string.pref_hwcodec_default)));
261 
262     // Check Capture to texture.
263     boolean captureToTexture = sharedPref.getBoolean(keyprefCaptureToTexture,
264         Boolean.valueOf(getString(R.string.pref_capturetotexture_default)));
265 
266     // Check Disable Audio Processing flag.
267     boolean noAudioProcessing = sharedPref.getBoolean(
268         keyprefNoAudioProcessingPipeline,
269         Boolean.valueOf(getString(R.string.pref_noaudioprocessing_default)));
270 
271     // Check Disable Audio Processing flag.
272     boolean aecDump = sharedPref.getBoolean(
273         keyprefAecDump,
274         Boolean.valueOf(getString(R.string.pref_aecdump_default)));
275 
276     // Check OpenSL ES enabled flag.
277     boolean useOpenSLES = sharedPref.getBoolean(
278         keyprefOpenSLES,
279         Boolean.valueOf(getString(R.string.pref_opensles_default)));
280 
281     // Get video resolution from settings.
282     int videoWidth = 0;
283     int videoHeight = 0;
284     String resolution = sharedPref.getString(keyprefResolution,
285         getString(R.string.pref_resolution_default));
286     String[] dimensions = resolution.split("[ x]+");
287     if (dimensions.length == 2) {
288       try {
289         videoWidth = Integer.parseInt(dimensions[0]);
290         videoHeight = Integer.parseInt(dimensions[1]);
291       } catch (NumberFormatException e) {
292         videoWidth = 0;
293         videoHeight = 0;
294         Log.e(TAG, "Wrong video resolution setting: " + resolution);
295       }
296     }
297 
298     // Get camera fps from settings.
299     int cameraFps = 0;
300     String fps = sharedPref.getString(keyprefFps,
301         getString(R.string.pref_fps_default));
302     String[] fpsValues = fps.split("[ x]+");
303     if (fpsValues.length == 2) {
304       try {
305         cameraFps = Integer.parseInt(fpsValues[0]);
306       } catch (NumberFormatException e) {
307         Log.e(TAG, "Wrong camera fps setting: " + fps);
308       }
309     }
310 
311     // Check capture quality slider flag.
312     boolean captureQualitySlider = sharedPref.getBoolean(keyprefCaptureQualitySlider,
313         Boolean.valueOf(getString(R.string.pref_capturequalityslider_default)));
314 
315     // Get video and audio start bitrate.
316     int videoStartBitrate = 0;
317     String bitrateTypeDefault = getString(
318         R.string.pref_startvideobitrate_default);
319     String bitrateType = sharedPref.getString(
320         keyprefVideoBitrateType, bitrateTypeDefault);
321     if (!bitrateType.equals(bitrateTypeDefault)) {
322       String bitrateValue = sharedPref.getString(keyprefVideoBitrateValue,
323           getString(R.string.pref_startvideobitratevalue_default));
324       videoStartBitrate = Integer.parseInt(bitrateValue);
325     }
326     int audioStartBitrate = 0;
327     bitrateTypeDefault = getString(R.string.pref_startaudiobitrate_default);
328     bitrateType = sharedPref.getString(
329         keyprefAudioBitrateType, bitrateTypeDefault);
330     if (!bitrateType.equals(bitrateTypeDefault)) {
331       String bitrateValue = sharedPref.getString(keyprefAudioBitrateValue,
332           getString(R.string.pref_startaudiobitratevalue_default));
333       audioStartBitrate = Integer.parseInt(bitrateValue);
334     }
335 
336     // Check statistics display option.
337     boolean displayHud = sharedPref.getBoolean(keyprefDisplayHud,
338         Boolean.valueOf(getString(R.string.pref_displayhud_default)));
339 
340     boolean tracing = sharedPref.getBoolean(
341             keyprefTracing, Boolean.valueOf(getString(R.string.pref_tracing_default)));
342 
343     // Start AppRTCDemo activity.
344     Log.d(TAG, "Connecting to room " + roomId + " at URL " + roomUrl);
345     if (validateUrl(roomUrl)) {
346       Uri uri = Uri.parse(roomUrl);
347       Intent intent = new Intent(this, CallActivity.class);
348       intent.setData(uri);
349       intent.putExtra(CallActivity.EXTRA_ROOMID, roomId);
350       intent.putExtra(CallActivity.EXTRA_LOOPBACK, loopback);
351       intent.putExtra(CallActivity.EXTRA_VIDEO_CALL, videoCallEnabled);
352       intent.putExtra(CallActivity.EXTRA_VIDEO_WIDTH, videoWidth);
353       intent.putExtra(CallActivity.EXTRA_VIDEO_HEIGHT, videoHeight);
354       intent.putExtra(CallActivity.EXTRA_VIDEO_FPS, cameraFps);
355       intent.putExtra(CallActivity.EXTRA_VIDEO_CAPTUREQUALITYSLIDER_ENABLED,
356           captureQualitySlider);
357       intent.putExtra(CallActivity.EXTRA_VIDEO_BITRATE, videoStartBitrate);
358       intent.putExtra(CallActivity.EXTRA_VIDEOCODEC, videoCodec);
359       intent.putExtra(CallActivity.EXTRA_HWCODEC_ENABLED, hwCodec);
360       intent.putExtra(CallActivity.EXTRA_CAPTURETOTEXTURE_ENABLED, captureToTexture);
361       intent.putExtra(CallActivity.EXTRA_NOAUDIOPROCESSING_ENABLED,
362           noAudioProcessing);
363       intent.putExtra(CallActivity.EXTRA_AECDUMP_ENABLED, aecDump);
364       intent.putExtra(CallActivity.EXTRA_OPENSLES_ENABLED, useOpenSLES);
365       intent.putExtra(CallActivity.EXTRA_AUDIO_BITRATE, audioStartBitrate);
366       intent.putExtra(CallActivity.EXTRA_AUDIOCODEC, audioCodec);
367       intent.putExtra(CallActivity.EXTRA_DISPLAY_HUD, displayHud);
368       intent.putExtra(CallActivity.EXTRA_TRACING, tracing);
369       intent.putExtra(CallActivity.EXTRA_CMDLINE, commandLineRun);
370       intent.putExtra(CallActivity.EXTRA_RUNTIME, runTimeMs);
371 
372       startActivityForResult(intent, CONNECTION_REQUEST);
373     }
374   }
375 
validateUrl(String url)376   private boolean validateUrl(String url) {
377     if (URLUtil.isHttpsUrl(url) || URLUtil.isHttpUrl(url)) {
378       return true;
379     }
380 
381     new AlertDialog.Builder(this)
382         .setTitle(getText(R.string.invalid_url_title))
383         .setMessage(getString(R.string.invalid_url_text, url))
384         .setCancelable(false)
385         .setNeutralButton(R.string.ok, new DialogInterface.OnClickListener() {
386             public void onClick(DialogInterface dialog, int id) {
387               dialog.cancel();
388             }
389           }).create().show();
390     return false;
391   }
392 
393   private final OnClickListener addRoomListener = new OnClickListener() {
394     @Override
395     public void onClick(View view) {
396       String newRoom = roomEditText.getText().toString();
397       if (newRoom.length() > 0 && !roomList.contains(newRoom)) {
398         adapter.add(newRoom);
399         adapter.notifyDataSetChanged();
400       }
401     }
402   };
403 
404   private final OnClickListener removeRoomListener = new OnClickListener() {
405     @Override
406     public void onClick(View view) {
407       String selectedRoom = getSelectedItem();
408       if (selectedRoom != null) {
409         adapter.remove(selectedRoom);
410         adapter.notifyDataSetChanged();
411       }
412     }
413   };
414 
getSelectedItem()415   private String getSelectedItem() {
416     int position = AdapterView.INVALID_POSITION;
417     if (roomListView.getCheckedItemCount() > 0 && adapter.getCount() > 0) {
418       position = roomListView.getCheckedItemPosition();
419       if (position >= adapter.getCount()) {
420         position = AdapterView.INVALID_POSITION;
421       }
422     }
423     if (position != AdapterView.INVALID_POSITION) {
424       return adapter.getItem(position);
425     } else {
426       return null;
427     }
428   }
429 
430 }
431