tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

CallActivity.java (35206B)


      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 android.annotation.TargetApi;
     14 import android.app.Activity;
     15 import android.app.AlertDialog;
     16 import android.app.FragmentTransaction;
     17 import android.content.Context;
     18 import android.content.DialogInterface;
     19 import android.content.Intent;
     20 import android.content.pm.PackageManager;
     21 import android.media.projection.MediaProjection;
     22 import android.media.projection.MediaProjectionManager;
     23 import android.net.Uri;
     24 import android.os.Build;
     25 import android.os.Bundle;
     26 import android.os.Handler;
     27 import android.util.DisplayMetrics;
     28 import android.util.Log;
     29 import android.view.View;
     30 import android.view.Window;
     31 import android.view.WindowManager;
     32 import android.view.WindowManager.LayoutParams;
     33 import android.widget.Toast;
     34 import androidx.annotation.Nullable;
     35 import java.io.IOException;
     36 import java.lang.RuntimeException;
     37 import java.util.ArrayList;
     38 import java.util.List;
     39 import java.util.Set;
     40 import org.appspot.apprtc.AppRTCAudioManager.AudioDevice;
     41 import org.appspot.apprtc.AppRTCAudioManager.AudioManagerEvents;
     42 import org.appspot.apprtc.AppRTCClient.RoomConnectionParameters;
     43 import org.appspot.apprtc.AppRTCClient.SignalingParameters;
     44 import org.appspot.apprtc.PeerConnectionClient.DataChannelParameters;
     45 import org.appspot.apprtc.PeerConnectionClient.PeerConnectionParameters;
     46 import org.webrtc.Camera1Enumerator;
     47 import org.webrtc.Camera2Enumerator;
     48 import org.webrtc.CameraEnumerator;
     49 import org.webrtc.EglBase;
     50 import org.webrtc.FileVideoCapturer;
     51 import org.webrtc.IceCandidate;
     52 import org.webrtc.Logging;
     53 import org.webrtc.PeerConnectionFactory;
     54 import org.webrtc.RTCStatsReport;
     55 import org.webrtc.RendererCommon.ScalingType;
     56 import org.webrtc.ScreenCapturerAndroid;
     57 import org.webrtc.SessionDescription;
     58 import org.webrtc.SurfaceViewRenderer;
     59 import org.webrtc.VideoCapturer;
     60 import org.webrtc.VideoFileRenderer;
     61 import org.webrtc.VideoFrame;
     62 import org.webrtc.VideoSink;
     63 
     64 /**
     65 * Activity for peer connection call setup, call waiting
     66 * and call view.
     67 */
     68 public class CallActivity extends Activity implements AppRTCClient.SignalingEvents,
     69                                                      PeerConnectionClient.PeerConnectionEvents,
     70                                                      CallFragment.OnCallEvents {
     71  private static final String TAG = "CallRTCClient";
     72 
     73  public static final String EXTRA_ROOMID = "org.appspot.apprtc.ROOMID";
     74  public static final String EXTRA_URLPARAMETERS = "org.appspot.apprtc.URLPARAMETERS";
     75  public static final String EXTRA_LOOPBACK = "org.appspot.apprtc.LOOPBACK";
     76  public static final String EXTRA_VIDEO_CALL = "org.appspot.apprtc.VIDEO_CALL";
     77  public static final String EXTRA_SCREENCAPTURE = "org.appspot.apprtc.SCREENCAPTURE";
     78  public static final String EXTRA_CAMERA2 = "org.appspot.apprtc.CAMERA2";
     79  public static final String EXTRA_VIDEO_WIDTH = "org.appspot.apprtc.VIDEO_WIDTH";
     80  public static final String EXTRA_VIDEO_HEIGHT = "org.appspot.apprtc.VIDEO_HEIGHT";
     81  public static final String EXTRA_VIDEO_FPS = "org.appspot.apprtc.VIDEO_FPS";
     82  public static final String EXTRA_VIDEO_CAPTUREQUALITYSLIDER_ENABLED =
     83      "org.appsopt.apprtc.VIDEO_CAPTUREQUALITYSLIDER";
     84  public static final String EXTRA_VIDEO_BITRATE = "org.appspot.apprtc.VIDEO_BITRATE";
     85  public static final String EXTRA_VIDEOCODEC = "org.appspot.apprtc.VIDEOCODEC";
     86  public static final String EXTRA_HWCODEC_ENABLED = "org.appspot.apprtc.HWCODEC";
     87  public static final String EXTRA_CAPTURETOTEXTURE_ENABLED = "org.appspot.apprtc.CAPTURETOTEXTURE";
     88  public static final String EXTRA_FLEXFEC_ENABLED = "org.appspot.apprtc.FLEXFEC";
     89  public static final String EXTRA_AUDIO_BITRATE = "org.appspot.apprtc.AUDIO_BITRATE";
     90  public static final String EXTRA_AUDIOCODEC = "org.appspot.apprtc.AUDIOCODEC";
     91  public static final String EXTRA_NOAUDIOPROCESSING_ENABLED =
     92      "org.appspot.apprtc.NOAUDIOPROCESSING";
     93  public static final String EXTRA_AECDUMP_ENABLED = "org.appspot.apprtc.AECDUMP";
     94  public static final String EXTRA_SAVE_INPUT_AUDIO_TO_FILE_ENABLED =
     95      "org.appspot.apprtc.SAVE_INPUT_AUDIO_TO_FILE";
     96  public static final String EXTRA_OPENSLES_ENABLED = "org.appspot.apprtc.OPENSLES";
     97  public static final String EXTRA_DISABLE_BUILT_IN_AEC = "org.appspot.apprtc.DISABLE_BUILT_IN_AEC";
     98  public static final String EXTRA_DISABLE_BUILT_IN_AGC = "org.appspot.apprtc.DISABLE_BUILT_IN_AGC";
     99  public static final String EXTRA_DISABLE_BUILT_IN_NS = "org.appspot.apprtc.DISABLE_BUILT_IN_NS";
    100  public static final String EXTRA_DISABLE_WEBRTC_AGC_AND_HPF =
    101      "org.appspot.apprtc.DISABLE_WEBRTC_GAIN_CONTROL";
    102  public static final String EXTRA_DISPLAY_HUD = "org.appspot.apprtc.DISPLAY_HUD";
    103  public static final String EXTRA_TRACING = "org.appspot.apprtc.TRACING";
    104  public static final String EXTRA_CMDLINE = "org.appspot.apprtc.CMDLINE";
    105  public static final String EXTRA_RUNTIME = "org.appspot.apprtc.RUNTIME";
    106  public static final String EXTRA_VIDEO_FILE_AS_CAMERA = "org.appspot.apprtc.VIDEO_FILE_AS_CAMERA";
    107  public static final String EXTRA_SAVE_REMOTE_VIDEO_TO_FILE =
    108      "org.appspot.apprtc.SAVE_REMOTE_VIDEO_TO_FILE";
    109  public static final String EXTRA_SAVE_REMOTE_VIDEO_TO_FILE_WIDTH =
    110      "org.appspot.apprtc.SAVE_REMOTE_VIDEO_TO_FILE_WIDTH";
    111  public static final String EXTRA_SAVE_REMOTE_VIDEO_TO_FILE_HEIGHT =
    112      "org.appspot.apprtc.SAVE_REMOTE_VIDEO_TO_FILE_HEIGHT";
    113  public static final String EXTRA_USE_VALUES_FROM_INTENT =
    114      "org.appspot.apprtc.USE_VALUES_FROM_INTENT";
    115  public static final String EXTRA_DATA_CHANNEL_ENABLED = "org.appspot.apprtc.DATA_CHANNEL_ENABLED";
    116  public static final String EXTRA_ORDERED = "org.appspot.apprtc.ORDERED";
    117  public static final String EXTRA_MAX_RETRANSMITS_MS = "org.appspot.apprtc.MAX_RETRANSMITS_MS";
    118  public static final String EXTRA_MAX_RETRANSMITS = "org.appspot.apprtc.MAX_RETRANSMITS";
    119  public static final String EXTRA_PROTOCOL = "org.appspot.apprtc.PROTOCOL";
    120  public static final String EXTRA_NEGOTIATED = "org.appspot.apprtc.NEGOTIATED";
    121  public static final String EXTRA_ID = "org.appspot.apprtc.ID";
    122  public static final String EXTRA_ENABLE_RTCEVENTLOG = "org.appspot.apprtc.ENABLE_RTCEVENTLOG";
    123 
    124  private static final int CAPTURE_PERMISSION_REQUEST_CODE = 1;
    125 
    126  // List of mandatory application permissions.
    127  private static final String[] MANDATORY_PERMISSIONS = {"android.permission.MODIFY_AUDIO_SETTINGS",
    128      "android.permission.RECORD_AUDIO", "android.permission.INTERNET"};
    129 
    130  // Peer connection statistics callback period in ms.
    131  private static final int STAT_CALLBACK_PERIOD = 1000;
    132 
    133  private static class ProxyVideoSink implements VideoSink {
    134    private VideoSink target;
    135 
    136    @Override
    137    synchronized public void onFrame(VideoFrame frame) {
    138      if (target == null) {
    139        Logging.d(TAG, "Dropping frame in proxy because target is null.");
    140        return;
    141      }
    142 
    143      target.onFrame(frame);
    144    }
    145 
    146    synchronized public void setTarget(VideoSink target) {
    147      this.target = target;
    148    }
    149  }
    150 
    151  private final ProxyVideoSink remoteProxyRenderer = new ProxyVideoSink();
    152  private final ProxyVideoSink localProxyVideoSink = new ProxyVideoSink();
    153  @Nullable private PeerConnectionClient peerConnectionClient;
    154  @Nullable
    155  private AppRTCClient appRtcClient;
    156  @Nullable
    157  private SignalingParameters signalingParameters;
    158  @Nullable private AppRTCAudioManager audioManager;
    159  @Nullable
    160  private SurfaceViewRenderer pipRenderer;
    161  @Nullable
    162  private SurfaceViewRenderer fullscreenRenderer;
    163  @Nullable
    164  private VideoFileRenderer videoFileRenderer;
    165  private final List<VideoSink> remoteSinks = new ArrayList<>();
    166  private Toast logToast;
    167  private boolean commandLineRun;
    168  private boolean activityRunning;
    169  private RoomConnectionParameters roomConnectionParameters;
    170  @Nullable
    171  private PeerConnectionParameters peerConnectionParameters;
    172  private boolean connected;
    173  private boolean isError;
    174  private boolean callControlFragmentVisible = true;
    175  private long callStartedTimeMs;
    176  private boolean micEnabled = true;
    177  private boolean screencaptureEnabled;
    178  private static Intent mediaProjectionPermissionResultData;
    179  private static int mediaProjectionPermissionResultCode;
    180  // True if local view is in the fullscreen renderer.
    181  private boolean isSwappedFeeds;
    182 
    183  // Controls
    184  private CallFragment callFragment;
    185  private HudFragment hudFragment;
    186  private CpuMonitor cpuMonitor;
    187 
    188  @Override
    189  // TODO(bugs.webrtc.org/8580): LayoutParams.FLAG_TURN_SCREEN_ON and
    190  // LayoutParams.FLAG_SHOW_WHEN_LOCKED are deprecated.
    191  @SuppressWarnings("deprecation")
    192  public void onCreate(Bundle savedInstanceState) {
    193    super.onCreate(savedInstanceState);
    194    Thread.setDefaultUncaughtExceptionHandler(new UnhandledExceptionHandler(this));
    195 
    196    // Set window styles for fullscreen-window size. Needs to be done before
    197    // adding content.
    198    requestWindowFeature(Window.FEATURE_NO_TITLE);
    199    getWindow().addFlags(LayoutParams.FLAG_FULLSCREEN | LayoutParams.FLAG_KEEP_SCREEN_ON
    200        | LayoutParams.FLAG_SHOW_WHEN_LOCKED | LayoutParams.FLAG_TURN_SCREEN_ON);
    201    getWindow().getDecorView().setSystemUiVisibility(getSystemUiVisibility());
    202    setContentView(R.layout.activity_call);
    203 
    204    connected = false;
    205    signalingParameters = null;
    206 
    207    // Create UI controls.
    208    pipRenderer = findViewById(R.id.pip_video_view);
    209    fullscreenRenderer = findViewById(R.id.fullscreen_video_view);
    210    callFragment = new CallFragment();
    211    hudFragment = new HudFragment();
    212 
    213    // Show/hide call control fragment on view click.
    214    View.OnClickListener listener = new View.OnClickListener() {
    215      @Override
    216      public void onClick(View view) {
    217        toggleCallControlFragmentVisibility();
    218      }
    219    };
    220 
    221    // Swap feeds on pip view click.
    222    pipRenderer.setOnClickListener(new View.OnClickListener() {
    223      @Override
    224      public void onClick(View view) {
    225        setSwappedFeeds(!isSwappedFeeds);
    226      }
    227    });
    228 
    229    fullscreenRenderer.setOnClickListener(listener);
    230    remoteSinks.add(remoteProxyRenderer);
    231 
    232    final Intent intent = getIntent();
    233    final EglBase eglBase = EglBase.create();
    234 
    235    // Create video renderers.
    236    pipRenderer.init(eglBase.getEglBaseContext(), null);
    237    pipRenderer.setScalingType(ScalingType.SCALE_ASPECT_FIT);
    238    String saveRemoteVideoToFile = intent.getStringExtra(EXTRA_SAVE_REMOTE_VIDEO_TO_FILE);
    239 
    240    // When saveRemoteVideoToFile is set we save the video from the remote to a file.
    241    if (saveRemoteVideoToFile != null) {
    242      int videoOutWidth = intent.getIntExtra(EXTRA_SAVE_REMOTE_VIDEO_TO_FILE_WIDTH, 0);
    243      int videoOutHeight = intent.getIntExtra(EXTRA_SAVE_REMOTE_VIDEO_TO_FILE_HEIGHT, 0);
    244      try {
    245        videoFileRenderer = new VideoFileRenderer(
    246            saveRemoteVideoToFile, videoOutWidth, videoOutHeight, eglBase.getEglBaseContext());
    247        remoteSinks.add(videoFileRenderer);
    248      } catch (IOException e) {
    249        throw new RuntimeException(
    250            "Failed to open video file for output: " + saveRemoteVideoToFile, e);
    251      }
    252    }
    253    fullscreenRenderer.init(eglBase.getEglBaseContext(), null);
    254    fullscreenRenderer.setScalingType(ScalingType.SCALE_ASPECT_FILL);
    255 
    256    pipRenderer.setZOrderMediaOverlay(true);
    257    pipRenderer.setEnableHardwareScaler(true /* enabled */);
    258    fullscreenRenderer.setEnableHardwareScaler(false /* enabled */);
    259    // Start with local feed in fullscreen and swap it to the pip when the call is connected.
    260    setSwappedFeeds(true /* isSwappedFeeds */);
    261 
    262    // Check for mandatory permissions.
    263    for (String permission : MANDATORY_PERMISSIONS) {
    264      if (checkCallingOrSelfPermission(permission) != PackageManager.PERMISSION_GRANTED) {
    265        logAndToast("Permission " + permission + " is not granted");
    266        setResult(RESULT_CANCELED);
    267        finish();
    268        return;
    269      }
    270    }
    271 
    272    Uri roomUri = intent.getData();
    273    if (roomUri == null) {
    274      logAndToast(getString(R.string.missing_url));
    275      Log.e(TAG, "Didn't get any URL in intent!");
    276      setResult(RESULT_CANCELED);
    277      finish();
    278      return;
    279    }
    280 
    281    // Get Intent parameters.
    282    String roomId = intent.getStringExtra(EXTRA_ROOMID);
    283    Log.d(TAG, "Room ID: " + roomId);
    284    if (roomId == null || roomId.length() == 0) {
    285      logAndToast(getString(R.string.missing_url));
    286      Log.e(TAG, "Incorrect room ID in intent!");
    287      setResult(RESULT_CANCELED);
    288      finish();
    289      return;
    290    }
    291 
    292    boolean loopback = intent.getBooleanExtra(EXTRA_LOOPBACK, false);
    293    boolean tracing = intent.getBooleanExtra(EXTRA_TRACING, false);
    294 
    295    int videoWidth = intent.getIntExtra(EXTRA_VIDEO_WIDTH, 0);
    296    int videoHeight = intent.getIntExtra(EXTRA_VIDEO_HEIGHT, 0);
    297 
    298    screencaptureEnabled = intent.getBooleanExtra(EXTRA_SCREENCAPTURE, false);
    299    // If capturing format is not specified for screencapture, use screen resolution.
    300    if (screencaptureEnabled && videoWidth == 0 && videoHeight == 0) {
    301      DisplayMetrics displayMetrics = getDisplayMetrics();
    302      videoWidth = displayMetrics.widthPixels;
    303      videoHeight = displayMetrics.heightPixels;
    304    }
    305    DataChannelParameters dataChannelParameters = null;
    306    if (intent.getBooleanExtra(EXTRA_DATA_CHANNEL_ENABLED, false)) {
    307      dataChannelParameters = new DataChannelParameters(intent.getBooleanExtra(EXTRA_ORDERED, true),
    308          intent.getIntExtra(EXTRA_MAX_RETRANSMITS_MS, -1),
    309          intent.getIntExtra(EXTRA_MAX_RETRANSMITS, -1), intent.getStringExtra(EXTRA_PROTOCOL),
    310          intent.getBooleanExtra(EXTRA_NEGOTIATED, false), intent.getIntExtra(EXTRA_ID, -1));
    311    }
    312    peerConnectionParameters =
    313        new PeerConnectionParameters(intent.getBooleanExtra(EXTRA_VIDEO_CALL, true), loopback,
    314            tracing, videoWidth, videoHeight, intent.getIntExtra(EXTRA_VIDEO_FPS, 0),
    315            intent.getIntExtra(EXTRA_VIDEO_BITRATE, 0), intent.getStringExtra(EXTRA_VIDEOCODEC),
    316            intent.getBooleanExtra(EXTRA_HWCODEC_ENABLED, true),
    317            intent.getBooleanExtra(EXTRA_FLEXFEC_ENABLED, false),
    318            intent.getIntExtra(EXTRA_AUDIO_BITRATE, 0), intent.getStringExtra(EXTRA_AUDIOCODEC),
    319            intent.getBooleanExtra(EXTRA_NOAUDIOPROCESSING_ENABLED, false),
    320            intent.getBooleanExtra(EXTRA_AECDUMP_ENABLED, false),
    321            intent.getBooleanExtra(EXTRA_SAVE_INPUT_AUDIO_TO_FILE_ENABLED, false),
    322            intent.getBooleanExtra(EXTRA_OPENSLES_ENABLED, false),
    323            intent.getBooleanExtra(EXTRA_DISABLE_BUILT_IN_AEC, false),
    324            intent.getBooleanExtra(EXTRA_DISABLE_BUILT_IN_AGC, false),
    325            intent.getBooleanExtra(EXTRA_DISABLE_BUILT_IN_NS, false),
    326            intent.getBooleanExtra(EXTRA_DISABLE_WEBRTC_AGC_AND_HPF, false),
    327            intent.getBooleanExtra(EXTRA_ENABLE_RTCEVENTLOG, false), dataChannelParameters);
    328    commandLineRun = intent.getBooleanExtra(EXTRA_CMDLINE, false);
    329    int runTimeMs = intent.getIntExtra(EXTRA_RUNTIME, 0);
    330 
    331    Log.d(TAG, "VIDEO_FILE: '" + intent.getStringExtra(EXTRA_VIDEO_FILE_AS_CAMERA) + "'");
    332 
    333    // Create connection client. Use DirectRTCClient if room name is an IP otherwise use the
    334    // standard WebSocketRTCClient.
    335    if (loopback || !DirectRTCClient.IP_PATTERN.matcher(roomId).matches()) {
    336      appRtcClient = new WebSocketRTCClient(this);
    337    } else {
    338      Log.i(TAG, "Using DirectRTCClient because room name looks like an IP.");
    339      appRtcClient = new DirectRTCClient(this);
    340    }
    341    // Create connection parameters.
    342    String urlParameters = intent.getStringExtra(EXTRA_URLPARAMETERS);
    343    roomConnectionParameters =
    344        new RoomConnectionParameters(roomUri.toString(), roomId, loopback, urlParameters);
    345 
    346    // Create CPU monitor
    347    if (CpuMonitor.isSupported()) {
    348      cpuMonitor = new CpuMonitor(this);
    349      hudFragment.setCpuMonitor(cpuMonitor);
    350    }
    351 
    352    // Send intent arguments to fragments.
    353    callFragment.setArguments(intent.getExtras());
    354    hudFragment.setArguments(intent.getExtras());
    355    // Activate call and HUD fragments and start the call.
    356    FragmentTransaction ft = getFragmentManager().beginTransaction();
    357    ft.add(R.id.call_fragment_container, callFragment);
    358    ft.add(R.id.hud_fragment_container, hudFragment);
    359    ft.commit();
    360 
    361    // For command line execution run connection for <runTimeMs> and exit.
    362    if (commandLineRun && runTimeMs > 0) {
    363      new Handler().postDelayed(new Runnable() {
    364        @Override
    365        public void run() {
    366          disconnect();
    367        }
    368      }, runTimeMs);
    369    }
    370 
    371    // Create peer connection client.
    372    peerConnectionClient = new PeerConnectionClient(
    373        getApplicationContext(), eglBase, peerConnectionParameters, CallActivity.this);
    374    PeerConnectionFactory.Options options = new PeerConnectionFactory.Options();
    375    if (loopback) {
    376      options.networkIgnoreMask = 0;
    377    }
    378    peerConnectionClient.createPeerConnectionFactory(options);
    379 
    380    if (screencaptureEnabled) {
    381      startScreenCapture();
    382    } else {
    383      startCall();
    384    }
    385  }
    386 
    387  private DisplayMetrics getDisplayMetrics() {
    388    DisplayMetrics displayMetrics = new DisplayMetrics();
    389    WindowManager windowManager =
    390        (WindowManager) getApplication().getSystemService(Context.WINDOW_SERVICE);
    391    windowManager.getDefaultDisplay().getRealMetrics(displayMetrics);
    392    return displayMetrics;
    393  }
    394 
    395  private static int getSystemUiVisibility() {
    396    return View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_FULLSCREEN
    397        | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
    398  }
    399 
    400  private void startScreenCapture() {
    401    MediaProjectionManager mediaProjectionManager =
    402        (MediaProjectionManager) getApplication().getSystemService(
    403            Context.MEDIA_PROJECTION_SERVICE);
    404    startActivityForResult(
    405        mediaProjectionManager.createScreenCaptureIntent(), CAPTURE_PERMISSION_REQUEST_CODE);
    406  }
    407 
    408  @Override
    409  public void onActivityResult(int requestCode, int resultCode, Intent data) {
    410    if (requestCode != CAPTURE_PERMISSION_REQUEST_CODE)
    411      return;
    412    mediaProjectionPermissionResultCode = resultCode;
    413    mediaProjectionPermissionResultData = data;
    414    startCall();
    415  }
    416 
    417  private boolean useCamera2() {
    418    return Camera2Enumerator.isSupported(this) && getIntent().getBooleanExtra(EXTRA_CAMERA2, true);
    419  }
    420 
    421  private boolean captureToTexture() {
    422    return getIntent().getBooleanExtra(EXTRA_CAPTURETOTEXTURE_ENABLED, false);
    423  }
    424 
    425  private @Nullable VideoCapturer createCameraCapturer(CameraEnumerator enumerator) {
    426    final String[] deviceNames = enumerator.getDeviceNames();
    427 
    428    // First, try to find front facing camera
    429    Logging.d(TAG, "Looking for front facing cameras.");
    430    for (String deviceName : deviceNames) {
    431      if (enumerator.isFrontFacing(deviceName)) {
    432        Logging.d(TAG, "Creating front facing camera capturer.");
    433        VideoCapturer videoCapturer = enumerator.createCapturer(deviceName, null);
    434 
    435        if (videoCapturer != null) {
    436          return videoCapturer;
    437        }
    438      }
    439    }
    440 
    441    // Front facing camera not found, try something else
    442    Logging.d(TAG, "Looking for other cameras.");
    443    for (String deviceName : deviceNames) {
    444      if (!enumerator.isFrontFacing(deviceName)) {
    445        Logging.d(TAG, "Creating other camera capturer.");
    446        VideoCapturer videoCapturer = enumerator.createCapturer(deviceName, null);
    447 
    448        if (videoCapturer != null) {
    449          return videoCapturer;
    450        }
    451      }
    452    }
    453 
    454    return null;
    455  }
    456 
    457  private @Nullable VideoCapturer createScreenCapturer() {
    458    if (mediaProjectionPermissionResultCode != Activity.RESULT_OK) {
    459      reportError("User didn't give permission to capture the screen.");
    460      return null;
    461    }
    462    return new ScreenCapturerAndroid(
    463        mediaProjectionPermissionResultData, new MediaProjection.Callback() {
    464      @Override
    465      public void onStop() {
    466        reportError("User revoked permission to capture the screen.");
    467      }
    468    });
    469  }
    470 
    471  // Activity interfaces
    472  @Override
    473  public void onStop() {
    474    super.onStop();
    475    activityRunning = false;
    476    // Don't stop the video when using screencapture to allow user to show other apps to the remote
    477    // end.
    478    if (peerConnectionClient != null && !screencaptureEnabled) {
    479      peerConnectionClient.stopVideoSource();
    480    }
    481    if (cpuMonitor != null) {
    482      cpuMonitor.pause();
    483    }
    484  }
    485 
    486  @Override
    487  public void onStart() {
    488    super.onStart();
    489    activityRunning = true;
    490    // Video is not paused for screencapture. See onPause.
    491    if (peerConnectionClient != null && !screencaptureEnabled) {
    492      peerConnectionClient.startVideoSource();
    493    }
    494    if (cpuMonitor != null) {
    495      cpuMonitor.resume();
    496    }
    497  }
    498 
    499  @Override
    500  protected void onDestroy() {
    501    Thread.setDefaultUncaughtExceptionHandler(null);
    502    disconnect();
    503    if (logToast != null) {
    504      logToast.cancel();
    505    }
    506    activityRunning = false;
    507    super.onDestroy();
    508  }
    509 
    510  // CallFragment.OnCallEvents interface implementation.
    511  @Override
    512  public void onCallHangUp() {
    513    disconnect();
    514  }
    515 
    516  @Override
    517  public void onCameraSwitch() {
    518    if (peerConnectionClient != null) {
    519      peerConnectionClient.switchCamera();
    520    }
    521  }
    522 
    523  @Override
    524  public void onVideoScalingSwitch(ScalingType scalingType) {
    525    fullscreenRenderer.setScalingType(scalingType);
    526  }
    527 
    528  @Override
    529  public void onCaptureFormatChange(int width, int height, int framerate) {
    530    if (peerConnectionClient != null) {
    531      peerConnectionClient.changeCaptureFormat(width, height, framerate);
    532    }
    533  }
    534 
    535  @Override
    536  public boolean onToggleMic() {
    537    if (peerConnectionClient != null) {
    538      micEnabled = !micEnabled;
    539      peerConnectionClient.setAudioEnabled(micEnabled);
    540    }
    541    return micEnabled;
    542  }
    543 
    544  // Helper functions.
    545  private void toggleCallControlFragmentVisibility() {
    546    if (!connected || !callFragment.isAdded()) {
    547      return;
    548    }
    549    // Show/hide call control fragment
    550    callControlFragmentVisible = !callControlFragmentVisible;
    551    FragmentTransaction ft = getFragmentManager().beginTransaction();
    552    if (callControlFragmentVisible) {
    553      ft.show(callFragment);
    554      ft.show(hudFragment);
    555    } else {
    556      ft.hide(callFragment);
    557      ft.hide(hudFragment);
    558    }
    559    ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
    560    ft.commit();
    561  }
    562 
    563  private void startCall() {
    564    if (appRtcClient == null) {
    565      Log.e(TAG, "AppRTC client is not allocated for a call.");
    566      return;
    567    }
    568    callStartedTimeMs = System.currentTimeMillis();
    569 
    570    // Start room connection.
    571    logAndToast(getString(R.string.connecting_to, roomConnectionParameters.roomUrl));
    572    appRtcClient.connectToRoom(roomConnectionParameters);
    573 
    574    // Create and audio manager that will take care of audio routing,
    575    // audio modes, audio device enumeration etc.
    576    audioManager = AppRTCAudioManager.create(getApplicationContext());
    577    // Store existing audio settings and change audio mode to
    578    // MODE_IN_COMMUNICATION for best possible VoIP performance.
    579    Log.d(TAG, "Starting the audio manager...");
    580    audioManager.start(new AudioManagerEvents() {
    581      // This method will be called each time the number of available audio
    582      // devices has changed.
    583      @Override
    584      public void onAudioDeviceChanged(
    585          AudioDevice audioDevice, Set<AudioDevice> availableAudioDevices) {
    586        onAudioManagerDevicesChanged(audioDevice, availableAudioDevices);
    587      }
    588    });
    589  }
    590 
    591  // Should be called from UI thread
    592  private void callConnected() {
    593    final long delta = System.currentTimeMillis() - callStartedTimeMs;
    594    Log.i(TAG, "Call connected: delay=" + delta + "ms");
    595    if (peerConnectionClient == null || isError) {
    596      Log.w(TAG, "Call is connected in closed or error state");
    597      return;
    598    }
    599    // Enable statistics callback.
    600    peerConnectionClient.enableStatsEvents(true, STAT_CALLBACK_PERIOD);
    601    setSwappedFeeds(false /* isSwappedFeeds */);
    602  }
    603 
    604  // This method is called when the audio manager reports audio device change,
    605  // e.g. from wired headset to speakerphone.
    606  private void onAudioManagerDevicesChanged(
    607      final AudioDevice device, final Set<AudioDevice> availableDevices) {
    608    Log.d(TAG, "onAudioManagerDevicesChanged: " + availableDevices + ", "
    609            + "selected: " + device);
    610    // TODO(henrika): add callback handler.
    611  }
    612 
    613  // Disconnect from remote resources, dispose of local resources, and exit.
    614  private void disconnect() {
    615    activityRunning = false;
    616    remoteProxyRenderer.setTarget(null);
    617    localProxyVideoSink.setTarget(null);
    618    if (appRtcClient != null) {
    619      appRtcClient.disconnectFromRoom();
    620      appRtcClient = null;
    621    }
    622    if (pipRenderer != null) {
    623      pipRenderer.release();
    624      pipRenderer = null;
    625    }
    626    if (videoFileRenderer != null) {
    627      videoFileRenderer.release();
    628      videoFileRenderer = null;
    629    }
    630    if (fullscreenRenderer != null) {
    631      fullscreenRenderer.release();
    632      fullscreenRenderer = null;
    633    }
    634    if (peerConnectionClient != null) {
    635      peerConnectionClient.close();
    636      peerConnectionClient = null;
    637    }
    638    if (audioManager != null) {
    639      audioManager.stop();
    640      audioManager = null;
    641    }
    642    if (connected && !isError) {
    643      setResult(RESULT_OK);
    644    } else {
    645      setResult(RESULT_CANCELED);
    646    }
    647    finish();
    648  }
    649 
    650  private void disconnectWithErrorMessage(final String errorMessage) {
    651    if (commandLineRun || !activityRunning) {
    652      Log.e(TAG, "Critical error: " + errorMessage);
    653      disconnect();
    654    } else {
    655      new AlertDialog.Builder(this)
    656          .setTitle(getText(R.string.channel_error_title))
    657          .setMessage(errorMessage)
    658          .setCancelable(false)
    659          .setNeutralButton(R.string.ok,
    660              new DialogInterface.OnClickListener() {
    661                @Override
    662                public void onClick(DialogInterface dialog, int id) {
    663                  dialog.cancel();
    664                  disconnect();
    665                }
    666              })
    667          .create()
    668          .show();
    669    }
    670  }
    671 
    672  // Log `msg` and Toast about it.
    673  private void logAndToast(String msg) {
    674    Log.d(TAG, msg);
    675    if (logToast != null) {
    676      logToast.cancel();
    677    }
    678    logToast = Toast.makeText(this, msg, Toast.LENGTH_SHORT);
    679    logToast.show();
    680  }
    681 
    682  private void reportError(final String description) {
    683    runOnUiThread(new Runnable() {
    684      @Override
    685      public void run() {
    686        if (!isError) {
    687          isError = true;
    688          disconnectWithErrorMessage(description);
    689        }
    690      }
    691    });
    692  }
    693 
    694  private @Nullable VideoCapturer createVideoCapturer() {
    695    final VideoCapturer videoCapturer;
    696    String videoFileAsCamera = getIntent().getStringExtra(EXTRA_VIDEO_FILE_AS_CAMERA);
    697    if (videoFileAsCamera != null) {
    698      try {
    699        videoCapturer = new FileVideoCapturer(videoFileAsCamera);
    700      } catch (IOException e) {
    701        reportError("Failed to open video file for emulated camera");
    702        return null;
    703      }
    704    } else if (screencaptureEnabled) {
    705      return createScreenCapturer();
    706    } else if (useCamera2()) {
    707      if (!captureToTexture()) {
    708        reportError(getString(R.string.camera2_texture_only_error));
    709        return null;
    710      }
    711 
    712      Logging.d(TAG, "Creating capturer using camera2 API.");
    713      videoCapturer = createCameraCapturer(new Camera2Enumerator(this));
    714    } else {
    715      Logging.d(TAG, "Creating capturer using camera1 API.");
    716      videoCapturer = createCameraCapturer(new Camera1Enumerator(captureToTexture()));
    717    }
    718    if (videoCapturer == null) {
    719      reportError("Failed to open camera");
    720      return null;
    721    }
    722    return videoCapturer;
    723  }
    724 
    725  private void setSwappedFeeds(boolean isSwappedFeeds) {
    726    Logging.d(TAG, "setSwappedFeeds: " + isSwappedFeeds);
    727    this.isSwappedFeeds = isSwappedFeeds;
    728    localProxyVideoSink.setTarget(isSwappedFeeds ? fullscreenRenderer : pipRenderer);
    729    remoteProxyRenderer.setTarget(isSwappedFeeds ? pipRenderer : fullscreenRenderer);
    730    fullscreenRenderer.setMirror(isSwappedFeeds);
    731    pipRenderer.setMirror(!isSwappedFeeds);
    732  }
    733 
    734  // -----Implementation of AppRTCClient.AppRTCSignalingEvents ---------------
    735  // All callbacks are invoked from websocket signaling looper thread and
    736  // are routed to UI thread.
    737  private void onConnectedToRoomInternal(final SignalingParameters params) {
    738    final long delta = System.currentTimeMillis() - callStartedTimeMs;
    739 
    740    signalingParameters = params;
    741    logAndToast("Creating peer connection, delay=" + delta + "ms");
    742    VideoCapturer videoCapturer = null;
    743    if (peerConnectionParameters.videoCallEnabled) {
    744      videoCapturer = createVideoCapturer();
    745    }
    746    peerConnectionClient.createPeerConnection(
    747        localProxyVideoSink, remoteSinks, videoCapturer, signalingParameters);
    748 
    749    if (signalingParameters.initiator) {
    750      logAndToast("Creating OFFER...");
    751      // Create offer. Offer SDP will be sent to answering client in
    752      // PeerConnectionEvents.onLocalDescription event.
    753      peerConnectionClient.createOffer();
    754    } else {
    755      if (params.offerSdp != null) {
    756        peerConnectionClient.setRemoteDescription(params.offerSdp);
    757        logAndToast("Creating ANSWER...");
    758        // Create answer. Answer SDP will be sent to offering client in
    759        // PeerConnectionEvents.onLocalDescription event.
    760        peerConnectionClient.createAnswer();
    761      }
    762      if (params.iceCandidates != null) {
    763        // Add remote ICE candidates from room.
    764        for (IceCandidate iceCandidate : params.iceCandidates) {
    765          peerConnectionClient.addRemoteIceCandidate(iceCandidate);
    766        }
    767      }
    768    }
    769  }
    770 
    771  @Override
    772  public void onConnectedToRoom(final SignalingParameters params) {
    773    runOnUiThread(new Runnable() {
    774      @Override
    775      public void run() {
    776        onConnectedToRoomInternal(params);
    777      }
    778    });
    779  }
    780 
    781  @Override
    782  public void onRemoteDescription(final SessionDescription desc) {
    783    final long delta = System.currentTimeMillis() - callStartedTimeMs;
    784    runOnUiThread(new Runnable() {
    785      @Override
    786      public void run() {
    787        if (peerConnectionClient == null) {
    788          Log.e(TAG, "Received remote SDP for non-initilized peer connection.");
    789          return;
    790        }
    791        logAndToast("Received remote " + desc.type + ", delay=" + delta + "ms");
    792        peerConnectionClient.setRemoteDescription(desc);
    793        if (!signalingParameters.initiator) {
    794          logAndToast("Creating ANSWER...");
    795          // Create answer. Answer SDP will be sent to offering client in
    796          // PeerConnectionEvents.onLocalDescription event.
    797          peerConnectionClient.createAnswer();
    798        }
    799      }
    800    });
    801  }
    802 
    803  @Override
    804  public void onRemoteIceCandidate(final IceCandidate candidate) {
    805    runOnUiThread(new Runnable() {
    806      @Override
    807      public void run() {
    808        if (peerConnectionClient == null) {
    809          Log.e(TAG, "Received ICE candidate for a non-initialized peer connection.");
    810          return;
    811        }
    812        peerConnectionClient.addRemoteIceCandidate(candidate);
    813      }
    814    });
    815  }
    816 
    817  @Override
    818  public void onRemoteIceCandidatesRemoved(final IceCandidate[] candidates) {
    819    runOnUiThread(new Runnable() {
    820      @Override
    821      public void run() {
    822        if (peerConnectionClient == null) {
    823          Log.e(TAG, "Received ICE candidate removals for a non-initialized peer connection.");
    824          return;
    825        }
    826        peerConnectionClient.removeRemoteIceCandidates(candidates);
    827      }
    828    });
    829  }
    830 
    831  @Override
    832  public void onChannelClose() {
    833    runOnUiThread(new Runnable() {
    834      @Override
    835      public void run() {
    836        logAndToast("Remote end hung up; dropping PeerConnection");
    837        disconnect();
    838      }
    839    });
    840  }
    841 
    842  @Override
    843  public void onChannelError(final String description) {
    844    reportError(description);
    845  }
    846 
    847  // -----Implementation of PeerConnectionClient.PeerConnectionEvents.---------
    848  // Send local peer connection SDP and ICE candidates to remote party.
    849  // All callbacks are invoked from peer connection client looper thread and
    850  // are routed to UI thread.
    851  @Override
    852  public void onLocalDescription(final SessionDescription desc) {
    853    final long delta = System.currentTimeMillis() - callStartedTimeMs;
    854    runOnUiThread(new Runnable() {
    855      @Override
    856      public void run() {
    857        if (appRtcClient != null) {
    858          logAndToast("Sending " + desc.type + ", delay=" + delta + "ms");
    859          if (signalingParameters.initiator) {
    860            appRtcClient.sendOfferSdp(desc);
    861          } else {
    862            appRtcClient.sendAnswerSdp(desc);
    863          }
    864        }
    865        if (peerConnectionParameters.videoMaxBitrate > 0) {
    866          Log.d(TAG, "Set video maximum bitrate: " + peerConnectionParameters.videoMaxBitrate);
    867          peerConnectionClient.setVideoMaxBitrate(peerConnectionParameters.videoMaxBitrate);
    868        }
    869      }
    870    });
    871  }
    872 
    873  @Override
    874  public void onIceCandidate(final IceCandidate candidate) {
    875    runOnUiThread(new Runnable() {
    876      @Override
    877      public void run() {
    878        if (appRtcClient != null) {
    879          appRtcClient.sendLocalIceCandidate(candidate);
    880        }
    881      }
    882    });
    883  }
    884 
    885  @Override
    886  public void onIceCandidatesRemoved(final IceCandidate[] candidates) {
    887    runOnUiThread(new Runnable() {
    888      @Override
    889      public void run() {
    890        if (appRtcClient != null) {
    891          appRtcClient.sendLocalIceCandidateRemovals(candidates);
    892        }
    893      }
    894    });
    895  }
    896 
    897  @Override
    898  public void onIceConnected() {
    899    final long delta = System.currentTimeMillis() - callStartedTimeMs;
    900    runOnUiThread(new Runnable() {
    901      @Override
    902      public void run() {
    903        logAndToast("ICE connected, delay=" + delta + "ms");
    904      }
    905    });
    906  }
    907 
    908  @Override
    909  public void onIceDisconnected() {
    910    runOnUiThread(new Runnable() {
    911      @Override
    912      public void run() {
    913        logAndToast("ICE disconnected");
    914      }
    915    });
    916  }
    917 
    918  @Override
    919  public void onConnected() {
    920    final long delta = System.currentTimeMillis() - callStartedTimeMs;
    921    runOnUiThread(new Runnable() {
    922      @Override
    923      public void run() {
    924        logAndToast("DTLS connected, delay=" + delta + "ms");
    925        connected = true;
    926        callConnected();
    927      }
    928    });
    929  }
    930 
    931  @Override
    932  public void onDisconnected() {
    933    runOnUiThread(new Runnable() {
    934      @Override
    935      public void run() {
    936        logAndToast("DTLS disconnected");
    937        connected = false;
    938        disconnect();
    939      }
    940    });
    941  }
    942 
    943  @Override
    944  public void onPeerConnectionClosed() {}
    945 
    946  @Override
    947  public void onPeerConnectionStatsReady(final RTCStatsReport report) {
    948    runOnUiThread(new Runnable() {
    949      @Override
    950      public void run() {
    951        if (!isError && connected) {
    952          hudFragment.updateEncoderStatistics(report);
    953        }
    954      }
    955    });
    956  }
    957 
    958  @Override
    959  public void onPeerConnectionError(final String description) {
    960    reportError(description);
    961  }
    962 }