1 /* 2 * Copyright 2015 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 org.appspot.apprtc.AppRTCClient.RoomConnectionParameters; 14 import org.appspot.apprtc.AppRTCClient.SignalingParameters; 15 import org.appspot.apprtc.PeerConnectionClient.PeerConnectionParameters; 16 import org.appspot.apprtc.util.LooperExecutor; 17 18 import android.app.Activity; 19 import android.app.AlertDialog; 20 import android.app.FragmentTransaction; 21 import android.content.DialogInterface; 22 import android.content.Intent; 23 import android.content.pm.PackageManager; 24 import android.net.Uri; 25 import android.os.Bundle; 26 import android.os.Handler; 27 import android.util.Log; 28 import android.view.View; 29 import android.view.Window; 30 import android.view.WindowManager.LayoutParams; 31 import android.widget.Toast; 32 33 import org.webrtc.EglBase; 34 import org.webrtc.IceCandidate; 35 import org.webrtc.SessionDescription; 36 import org.webrtc.StatsReport; 37 import org.webrtc.RendererCommon.ScalingType; 38 import org.webrtc.SurfaceViewRenderer; 39 40 /** 41 * Activity for peer connection call setup, call waiting 42 * and call view. 43 */ 44 public class CallActivity extends Activity 45 implements AppRTCClient.SignalingEvents, 46 PeerConnectionClient.PeerConnectionEvents, 47 CallFragment.OnCallEvents { 48 49 public static final String EXTRA_ROOMID = 50 "org.appspot.apprtc.ROOMID"; 51 public static final String EXTRA_LOOPBACK = 52 "org.appspot.apprtc.LOOPBACK"; 53 public static final String EXTRA_VIDEO_CALL = 54 "org.appspot.apprtc.VIDEO_CALL"; 55 public static final String EXTRA_VIDEO_WIDTH = 56 "org.appspot.apprtc.VIDEO_WIDTH"; 57 public static final String EXTRA_VIDEO_HEIGHT = 58 "org.appspot.apprtc.VIDEO_HEIGHT"; 59 public static final String EXTRA_VIDEO_FPS = 60 "org.appspot.apprtc.VIDEO_FPS"; 61 public static final String EXTRA_VIDEO_CAPTUREQUALITYSLIDER_ENABLED = 62 "org.appsopt.apprtc.VIDEO_CAPTUREQUALITYSLIDER"; 63 public static final String EXTRA_VIDEO_BITRATE = 64 "org.appspot.apprtc.VIDEO_BITRATE"; 65 public static final String EXTRA_VIDEOCODEC = 66 "org.appspot.apprtc.VIDEOCODEC"; 67 public static final String EXTRA_HWCODEC_ENABLED = 68 "org.appspot.apprtc.HWCODEC"; 69 public static final String EXTRA_CAPTURETOTEXTURE_ENABLED = 70 "org.appspot.apprtc.CAPTURETOTEXTURE"; 71 public static final String EXTRA_AUDIO_BITRATE = 72 "org.appspot.apprtc.AUDIO_BITRATE"; 73 public static final String EXTRA_AUDIOCODEC = 74 "org.appspot.apprtc.AUDIOCODEC"; 75 public static final String EXTRA_NOAUDIOPROCESSING_ENABLED = 76 "org.appspot.apprtc.NOAUDIOPROCESSING"; 77 public static final String EXTRA_AECDUMP_ENABLED = 78 "org.appspot.apprtc.AECDUMP"; 79 public static final String EXTRA_OPENSLES_ENABLED = 80 "org.appspot.apprtc.OPENSLES"; 81 public static final String EXTRA_DISPLAY_HUD = 82 "org.appspot.apprtc.DISPLAY_HUD"; 83 public static final String EXTRA_TRACING = "org.appspot.apprtc.TRACING"; 84 public static final String EXTRA_CMDLINE = 85 "org.appspot.apprtc.CMDLINE"; 86 public static final String EXTRA_RUNTIME = 87 "org.appspot.apprtc.RUNTIME"; 88 private static final String TAG = "CallRTCClient"; 89 90 // List of mandatory application permissions. 91 private static final String[] MANDATORY_PERMISSIONS = { 92 "android.permission.MODIFY_AUDIO_SETTINGS", 93 "android.permission.RECORD_AUDIO", 94 "android.permission.INTERNET" 95 }; 96 97 // Peer connection statistics callback period in ms. 98 private static final int STAT_CALLBACK_PERIOD = 1000; 99 // Local preview screen position before call is connected. 100 private static final int LOCAL_X_CONNECTING = 0; 101 private static final int LOCAL_Y_CONNECTING = 0; 102 private static final int LOCAL_WIDTH_CONNECTING = 100; 103 private static final int LOCAL_HEIGHT_CONNECTING = 100; 104 // Local preview screen position after call is connected. 105 private static final int LOCAL_X_CONNECTED = 72; 106 private static final int LOCAL_Y_CONNECTED = 72; 107 private static final int LOCAL_WIDTH_CONNECTED = 25; 108 private static final int LOCAL_HEIGHT_CONNECTED = 25; 109 // Remote video screen position 110 private static final int REMOTE_X = 0; 111 private static final int REMOTE_Y = 0; 112 private static final int REMOTE_WIDTH = 100; 113 private static final int REMOTE_HEIGHT = 100; 114 private PeerConnectionClient peerConnectionClient = null; 115 private AppRTCClient appRtcClient; 116 private SignalingParameters signalingParameters; 117 private AppRTCAudioManager audioManager = null; 118 private EglBase rootEglBase; 119 private SurfaceViewRenderer localRender; 120 private SurfaceViewRenderer remoteRender; 121 private PercentFrameLayout localRenderLayout; 122 private PercentFrameLayout remoteRenderLayout; 123 private ScalingType scalingType; 124 private Toast logToast; 125 private boolean commandLineRun; 126 private int runTimeMs; 127 private boolean activityRunning; 128 private RoomConnectionParameters roomConnectionParameters; 129 private PeerConnectionParameters peerConnectionParameters; 130 private boolean iceConnected; 131 private boolean isError; 132 private boolean callControlFragmentVisible = true; 133 private long callStartedTimeMs = 0; 134 135 // Controls 136 CallFragment callFragment; 137 HudFragment hudFragment; 138 139 @Override onCreate(Bundle savedInstanceState)140 public void onCreate(Bundle savedInstanceState) { 141 super.onCreate(savedInstanceState); 142 Thread.setDefaultUncaughtExceptionHandler( 143 new UnhandledExceptionHandler(this)); 144 145 // Set window styles for fullscreen-window size. Needs to be done before 146 // adding content. 147 requestWindowFeature(Window.FEATURE_NO_TITLE); 148 getWindow().addFlags( 149 LayoutParams.FLAG_FULLSCREEN 150 | LayoutParams.FLAG_KEEP_SCREEN_ON 151 | LayoutParams.FLAG_DISMISS_KEYGUARD 152 | LayoutParams.FLAG_SHOW_WHEN_LOCKED 153 | LayoutParams.FLAG_TURN_SCREEN_ON); 154 getWindow().getDecorView().setSystemUiVisibility( 155 View.SYSTEM_UI_FLAG_HIDE_NAVIGATION 156 | View.SYSTEM_UI_FLAG_FULLSCREEN 157 | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY); 158 setContentView(R.layout.activity_call); 159 160 iceConnected = false; 161 signalingParameters = null; 162 scalingType = ScalingType.SCALE_ASPECT_FILL; 163 164 // Create UI controls. 165 localRender = (SurfaceViewRenderer) findViewById(R.id.local_video_view); 166 remoteRender = (SurfaceViewRenderer) findViewById(R.id.remote_video_view); 167 localRenderLayout = (PercentFrameLayout) findViewById(R.id.local_video_layout); 168 remoteRenderLayout = (PercentFrameLayout) findViewById(R.id.remote_video_layout); 169 callFragment = new CallFragment(); 170 hudFragment = new HudFragment(); 171 172 // Show/hide call control fragment on view click. 173 View.OnClickListener listener = new View.OnClickListener() { 174 @Override 175 public void onClick(View view) { 176 toggleCallControlFragmentVisibility(); 177 } 178 }; 179 180 localRender.setOnClickListener(listener); 181 remoteRender.setOnClickListener(listener); 182 183 // Create video renderers. 184 rootEglBase = EglBase.create(); 185 localRender.init(rootEglBase.getEglBaseContext(), null); 186 remoteRender.init(rootEglBase.getEglBaseContext(), null); 187 localRender.setZOrderMediaOverlay(true); 188 updateVideoView(); 189 190 // Check for mandatory permissions. 191 for (String permission : MANDATORY_PERMISSIONS) { 192 if (checkCallingOrSelfPermission(permission) != PackageManager.PERMISSION_GRANTED) { 193 logAndToast("Permission " + permission + " is not granted"); 194 setResult(RESULT_CANCELED); 195 finish(); 196 return; 197 } 198 } 199 200 // Get Intent parameters. 201 final Intent intent = getIntent(); 202 Uri roomUri = intent.getData(); 203 if (roomUri == null) { 204 logAndToast(getString(R.string.missing_url)); 205 Log.e(TAG, "Didn't get any URL in intent!"); 206 setResult(RESULT_CANCELED); 207 finish(); 208 return; 209 } 210 String roomId = intent.getStringExtra(EXTRA_ROOMID); 211 if (roomId == null || roomId.length() == 0) { 212 logAndToast(getString(R.string.missing_url)); 213 Log.e(TAG, "Incorrect room ID in intent!"); 214 setResult(RESULT_CANCELED); 215 finish(); 216 return; 217 } 218 boolean loopback = intent.getBooleanExtra(EXTRA_LOOPBACK, false); 219 boolean tracing = intent.getBooleanExtra(EXTRA_TRACING, false); 220 peerConnectionParameters = new PeerConnectionParameters( 221 intent.getBooleanExtra(EXTRA_VIDEO_CALL, true), 222 loopback, 223 tracing, 224 intent.getIntExtra(EXTRA_VIDEO_WIDTH, 0), 225 intent.getIntExtra(EXTRA_VIDEO_HEIGHT, 0), 226 intent.getIntExtra(EXTRA_VIDEO_FPS, 0), 227 intent.getIntExtra(EXTRA_VIDEO_BITRATE, 0), 228 intent.getStringExtra(EXTRA_VIDEOCODEC), 229 intent.getBooleanExtra(EXTRA_HWCODEC_ENABLED, true), 230 intent.getBooleanExtra(EXTRA_CAPTURETOTEXTURE_ENABLED, false), 231 intent.getIntExtra(EXTRA_AUDIO_BITRATE, 0), 232 intent.getStringExtra(EXTRA_AUDIOCODEC), 233 intent.getBooleanExtra(EXTRA_NOAUDIOPROCESSING_ENABLED, false), 234 intent.getBooleanExtra(EXTRA_AECDUMP_ENABLED, false), 235 intent.getBooleanExtra(EXTRA_OPENSLES_ENABLED, false)); 236 commandLineRun = intent.getBooleanExtra(EXTRA_CMDLINE, false); 237 runTimeMs = intent.getIntExtra(EXTRA_RUNTIME, 0); 238 239 // Create connection client and connection parameters. 240 appRtcClient = new WebSocketRTCClient(this, new LooperExecutor()); 241 roomConnectionParameters = new RoomConnectionParameters( 242 roomUri.toString(), roomId, loopback); 243 244 // Send intent arguments to fragments. 245 callFragment.setArguments(intent.getExtras()); 246 hudFragment.setArguments(intent.getExtras()); 247 // Activate call and HUD fragments and start the call. 248 FragmentTransaction ft = getFragmentManager().beginTransaction(); 249 ft.add(R.id.call_fragment_container, callFragment); 250 ft.add(R.id.hud_fragment_container, hudFragment); 251 ft.commit(); 252 startCall(); 253 254 // For command line execution run connection for <runTimeMs> and exit. 255 if (commandLineRun && runTimeMs > 0) { 256 (new Handler()).postDelayed(new Runnable() { 257 @Override 258 public void run() { 259 disconnect(); 260 } 261 }, runTimeMs); 262 } 263 264 peerConnectionClient = PeerConnectionClient.getInstance(); 265 peerConnectionClient.createPeerConnectionFactory( 266 CallActivity.this, peerConnectionParameters, CallActivity.this); 267 } 268 269 // Activity interfaces 270 @Override onPause()271 public void onPause() { 272 super.onPause(); 273 activityRunning = false; 274 if (peerConnectionClient != null) { 275 peerConnectionClient.stopVideoSource(); 276 } 277 } 278 279 @Override onResume()280 public void onResume() { 281 super.onResume(); 282 activityRunning = true; 283 if (peerConnectionClient != null) { 284 peerConnectionClient.startVideoSource(); 285 } 286 } 287 288 @Override onDestroy()289 protected void onDestroy() { 290 disconnect(); 291 if (logToast != null) { 292 logToast.cancel(); 293 } 294 activityRunning = false; 295 rootEglBase.release(); 296 super.onDestroy(); 297 } 298 299 // CallFragment.OnCallEvents interface implementation. 300 @Override onCallHangUp()301 public void onCallHangUp() { 302 disconnect(); 303 } 304 305 @Override onCameraSwitch()306 public void onCameraSwitch() { 307 if (peerConnectionClient != null) { 308 peerConnectionClient.switchCamera(); 309 } 310 } 311 312 @Override onVideoScalingSwitch(ScalingType scalingType)313 public void onVideoScalingSwitch(ScalingType scalingType) { 314 this.scalingType = scalingType; 315 updateVideoView(); 316 } 317 318 @Override onCaptureFormatChange(int width, int height, int framerate)319 public void onCaptureFormatChange(int width, int height, int framerate) { 320 if (peerConnectionClient != null) { 321 peerConnectionClient.changeCaptureFormat(width, height, framerate); 322 } 323 } 324 325 // Helper functions. toggleCallControlFragmentVisibility()326 private void toggleCallControlFragmentVisibility() { 327 if (!iceConnected || !callFragment.isAdded()) { 328 return; 329 } 330 // Show/hide call control fragment 331 callControlFragmentVisible = !callControlFragmentVisible; 332 FragmentTransaction ft = getFragmentManager().beginTransaction(); 333 if (callControlFragmentVisible) { 334 ft.show(callFragment); 335 ft.show(hudFragment); 336 } else { 337 ft.hide(callFragment); 338 ft.hide(hudFragment); 339 } 340 ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE); 341 ft.commit(); 342 } 343 updateVideoView()344 private void updateVideoView() { 345 remoteRenderLayout.setPosition(REMOTE_X, REMOTE_Y, REMOTE_WIDTH, REMOTE_HEIGHT); 346 remoteRender.setScalingType(scalingType); 347 remoteRender.setMirror(false); 348 349 if (iceConnected) { 350 localRenderLayout.setPosition( 351 LOCAL_X_CONNECTED, LOCAL_Y_CONNECTED, LOCAL_WIDTH_CONNECTED, LOCAL_HEIGHT_CONNECTED); 352 localRender.setScalingType(ScalingType.SCALE_ASPECT_FIT); 353 } else { 354 localRenderLayout.setPosition( 355 LOCAL_X_CONNECTING, LOCAL_Y_CONNECTING, LOCAL_WIDTH_CONNECTING, LOCAL_HEIGHT_CONNECTING); 356 localRender.setScalingType(scalingType); 357 } 358 localRender.setMirror(true); 359 360 localRender.requestLayout(); 361 remoteRender.requestLayout(); 362 } 363 startCall()364 private void startCall() { 365 if (appRtcClient == null) { 366 Log.e(TAG, "AppRTC client is not allocated for a call."); 367 return; 368 } 369 callStartedTimeMs = System.currentTimeMillis(); 370 371 // Start room connection. 372 logAndToast(getString(R.string.connecting_to, 373 roomConnectionParameters.roomUrl)); 374 appRtcClient.connectToRoom(roomConnectionParameters); 375 376 // Create and audio manager that will take care of audio routing, 377 // audio modes, audio device enumeration etc. 378 audioManager = AppRTCAudioManager.create(this, new Runnable() { 379 // This method will be called each time the audio state (number and 380 // type of devices) has been changed. 381 @Override 382 public void run() { 383 onAudioManagerChangedState(); 384 } 385 } 386 ); 387 // Store existing audio settings and change audio mode to 388 // MODE_IN_COMMUNICATION for best possible VoIP performance. 389 Log.d(TAG, "Initializing the audio manager..."); 390 audioManager.init(); 391 } 392 393 // Should be called from UI thread callConnected()394 private void callConnected() { 395 final long delta = System.currentTimeMillis() - callStartedTimeMs; 396 Log.i(TAG, "Call connected: delay=" + delta + "ms"); 397 if (peerConnectionClient == null || isError) { 398 Log.w(TAG, "Call is connected in closed or error state"); 399 return; 400 } 401 // Update video view. 402 updateVideoView(); 403 // Enable statistics callback. 404 peerConnectionClient.enableStatsEvents(true, STAT_CALLBACK_PERIOD); 405 } 406 onAudioManagerChangedState()407 private void onAudioManagerChangedState() { 408 // TODO(henrika): disable video if AppRTCAudioManager.AudioDevice.EARPIECE 409 // is active. 410 } 411 412 // Disconnect from remote resources, dispose of local resources, and exit. disconnect()413 private void disconnect() { 414 activityRunning = false; 415 if (appRtcClient != null) { 416 appRtcClient.disconnectFromRoom(); 417 appRtcClient = null; 418 } 419 if (peerConnectionClient != null) { 420 peerConnectionClient.close(); 421 peerConnectionClient = null; 422 } 423 if (localRender != null) { 424 localRender.release(); 425 localRender = null; 426 } 427 if (remoteRender != null) { 428 remoteRender.release(); 429 remoteRender = null; 430 } 431 if (audioManager != null) { 432 audioManager.close(); 433 audioManager = null; 434 } 435 if (iceConnected && !isError) { 436 setResult(RESULT_OK); 437 } else { 438 setResult(RESULT_CANCELED); 439 } 440 finish(); 441 } 442 disconnectWithErrorMessage(final String errorMessage)443 private void disconnectWithErrorMessage(final String errorMessage) { 444 if (commandLineRun || !activityRunning) { 445 Log.e(TAG, "Critical error: " + errorMessage); 446 disconnect(); 447 } else { 448 new AlertDialog.Builder(this) 449 .setTitle(getText(R.string.channel_error_title)) 450 .setMessage(errorMessage) 451 .setCancelable(false) 452 .setNeutralButton(R.string.ok, new DialogInterface.OnClickListener() { 453 @Override 454 public void onClick(DialogInterface dialog, int id) { 455 dialog.cancel(); 456 disconnect(); 457 } 458 }).create().show(); 459 } 460 } 461 462 // Log |msg| and Toast about it. logAndToast(String msg)463 private void logAndToast(String msg) { 464 Log.d(TAG, msg); 465 if (logToast != null) { 466 logToast.cancel(); 467 } 468 logToast = Toast.makeText(this, msg, Toast.LENGTH_SHORT); 469 logToast.show(); 470 } 471 reportError(final String description)472 private void reportError(final String description) { 473 runOnUiThread(new Runnable() { 474 @Override 475 public void run() { 476 if (!isError) { 477 isError = true; 478 disconnectWithErrorMessage(description); 479 } 480 } 481 }); 482 } 483 484 // -----Implementation of AppRTCClient.AppRTCSignalingEvents --------------- 485 // All callbacks are invoked from websocket signaling looper thread and 486 // are routed to UI thread. onConnectedToRoomInternal(final SignalingParameters params)487 private void onConnectedToRoomInternal(final SignalingParameters params) { 488 final long delta = System.currentTimeMillis() - callStartedTimeMs; 489 490 signalingParameters = params; 491 logAndToast("Creating peer connection, delay=" + delta + "ms"); 492 peerConnectionClient.createPeerConnection(rootEglBase.getEglBaseContext(), 493 localRender, remoteRender, signalingParameters); 494 495 if (signalingParameters.initiator) { 496 logAndToast("Creating OFFER..."); 497 // Create offer. Offer SDP will be sent to answering client in 498 // PeerConnectionEvents.onLocalDescription event. 499 peerConnectionClient.createOffer(); 500 } else { 501 if (params.offerSdp != null) { 502 peerConnectionClient.setRemoteDescription(params.offerSdp); 503 logAndToast("Creating ANSWER..."); 504 // Create answer. Answer SDP will be sent to offering client in 505 // PeerConnectionEvents.onLocalDescription event. 506 peerConnectionClient.createAnswer(); 507 } 508 if (params.iceCandidates != null) { 509 // Add remote ICE candidates from room. 510 for (IceCandidate iceCandidate : params.iceCandidates) { 511 peerConnectionClient.addRemoteIceCandidate(iceCandidate); 512 } 513 } 514 } 515 } 516 517 @Override onConnectedToRoom(final SignalingParameters params)518 public void onConnectedToRoom(final SignalingParameters params) { 519 runOnUiThread(new Runnable() { 520 @Override 521 public void run() { 522 onConnectedToRoomInternal(params); 523 } 524 }); 525 } 526 527 @Override onRemoteDescription(final SessionDescription sdp)528 public void onRemoteDescription(final SessionDescription sdp) { 529 final long delta = System.currentTimeMillis() - callStartedTimeMs; 530 runOnUiThread(new Runnable() { 531 @Override 532 public void run() { 533 if (peerConnectionClient == null) { 534 Log.e(TAG, "Received remote SDP for non-initilized peer connection."); 535 return; 536 } 537 logAndToast("Received remote " + sdp.type + ", delay=" + delta + "ms"); 538 peerConnectionClient.setRemoteDescription(sdp); 539 if (!signalingParameters.initiator) { 540 logAndToast("Creating ANSWER..."); 541 // Create answer. Answer SDP will be sent to offering client in 542 // PeerConnectionEvents.onLocalDescription event. 543 peerConnectionClient.createAnswer(); 544 } 545 } 546 }); 547 } 548 549 @Override onRemoteIceCandidate(final IceCandidate candidate)550 public void onRemoteIceCandidate(final IceCandidate candidate) { 551 runOnUiThread(new Runnable() { 552 @Override 553 public void run() { 554 if (peerConnectionClient == null) { 555 Log.e(TAG, 556 "Received ICE candidate for non-initilized peer connection."); 557 return; 558 } 559 peerConnectionClient.addRemoteIceCandidate(candidate); 560 } 561 }); 562 } 563 564 @Override onChannelClose()565 public void onChannelClose() { 566 runOnUiThread(new Runnable() { 567 @Override 568 public void run() { 569 logAndToast("Remote end hung up; dropping PeerConnection"); 570 disconnect(); 571 } 572 }); 573 } 574 575 @Override onChannelError(final String description)576 public void onChannelError(final String description) { 577 reportError(description); 578 } 579 580 // -----Implementation of PeerConnectionClient.PeerConnectionEvents.--------- 581 // Send local peer connection SDP and ICE candidates to remote party. 582 // All callbacks are invoked from peer connection client looper thread and 583 // are routed to UI thread. 584 @Override onLocalDescription(final SessionDescription sdp)585 public void onLocalDescription(final SessionDescription sdp) { 586 final long delta = System.currentTimeMillis() - callStartedTimeMs; 587 runOnUiThread(new Runnable() { 588 @Override 589 public void run() { 590 if (appRtcClient != null) { 591 logAndToast("Sending " + sdp.type + ", delay=" + delta + "ms"); 592 if (signalingParameters.initiator) { 593 appRtcClient.sendOfferSdp(sdp); 594 } else { 595 appRtcClient.sendAnswerSdp(sdp); 596 } 597 } 598 } 599 }); 600 } 601 602 @Override onIceCandidate(final IceCandidate candidate)603 public void onIceCandidate(final IceCandidate candidate) { 604 runOnUiThread(new Runnable() { 605 @Override 606 public void run() { 607 if (appRtcClient != null) { 608 appRtcClient.sendLocalIceCandidate(candidate); 609 } 610 } 611 }); 612 } 613 614 @Override onIceConnected()615 public void onIceConnected() { 616 final long delta = System.currentTimeMillis() - callStartedTimeMs; 617 runOnUiThread(new Runnable() { 618 @Override 619 public void run() { 620 logAndToast("ICE connected, delay=" + delta + "ms"); 621 iceConnected = true; 622 callConnected(); 623 } 624 }); 625 } 626 627 @Override onIceDisconnected()628 public void onIceDisconnected() { 629 runOnUiThread(new Runnable() { 630 @Override 631 public void run() { 632 logAndToast("ICE disconnected"); 633 iceConnected = false; 634 disconnect(); 635 } 636 }); 637 } 638 639 @Override onPeerConnectionClosed()640 public void onPeerConnectionClosed() { 641 } 642 643 @Override onPeerConnectionStatsReady(final StatsReport[] reports)644 public void onPeerConnectionStatsReady(final StatsReport[] reports) { 645 runOnUiThread(new Runnable() { 646 @Override 647 public void run() { 648 if (!isError && iceConnected) { 649 hudFragment.updateEncoderStatistics(reports); 650 } 651 } 652 }); 653 } 654 655 @Override onPeerConnectionError(final String description)656 public void onPeerConnectionError(final String description) { 657 reportError(description); 658 } 659 } 660