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