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
11package org.appspot.apprtc;
12
13import android.app.Activity;
14import android.app.AlertDialog;
15import android.content.DialogInterface;
16import android.content.Intent;
17import android.content.SharedPreferences;
18import android.net.Uri;
19import android.os.Bundle;
20import android.preference.PreferenceManager;
21import android.util.Log;
22import android.view.KeyEvent;
23import android.view.Menu;
24import android.view.MenuItem;
25import android.view.View;
26import android.view.View.OnClickListener;
27import android.view.inputmethod.EditorInfo;
28import android.webkit.URLUtil;
29import android.widget.AdapterView;
30import android.widget.ArrayAdapter;
31import android.widget.EditText;
32import android.widget.ImageButton;
33import android.widget.ListView;
34import android.widget.TextView;
35
36import org.json.JSONArray;
37import org.json.JSONException;
38
39import java.util.ArrayList;
40import java.util.Random;
41
42/**
43 * Handles the initial setup where the user selects which room to join.
44 */
45public 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
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
155  public boolean onCreateOptionsMenu(Menu menu) {
156    getMenuInflater().inflate(R.menu.connect_menu, menu);
157    return true;
158  }
159
160  @Override
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
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
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
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
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
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
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