Add support for swapping feeds in Android AppRTCMobile.

BUG=webrtc:6937

Review-Url: https://codereview.webrtc.org/2682943006
Cr-Commit-Position: refs/heads/master@{#16582}
diff --git a/webrtc/examples/BUILD.gn b/webrtc/examples/BUILD.gn
index 3ee9ae5..59842fc 100644
--- a/webrtc/examples/BUILD.gn
+++ b/webrtc/examples/BUILD.gn
@@ -64,6 +64,8 @@
   }
 
   android_library("AppRTCMobile_javalib") {
+    android_manifest = "androidapp/AndroidManifest.xml"
+
     java_files = [
       "androidapp/src/org/appspot/apprtc/AppRTCAudioManager.java",
       "androidapp/src/org/appspot/apprtc/AppRTCBluetoothManager.java",
@@ -77,7 +79,6 @@
       "androidapp/src/org/appspot/apprtc/DirectRTCClient.java",
       "androidapp/src/org/appspot/apprtc/HudFragment.java",
       "androidapp/src/org/appspot/apprtc/PeerConnectionClient.java",
-      "androidapp/src/org/appspot/apprtc/PercentFrameLayout.java",
       "androidapp/src/org/appspot/apprtc/RoomParametersFetcher.java",
       "androidapp/src/org/appspot/apprtc/SettingsActivity.java",
       "androidapp/src/org/appspot/apprtc/SettingsFragment.java",
diff --git a/webrtc/examples/androidapp/AndroidManifest.xml b/webrtc/examples/androidapp/AndroidManifest.xml
index f777ee5..f85bf4e 100644
--- a/webrtc/examples/androidapp/AndroidManifest.xml
+++ b/webrtc/examples/androidapp/AndroidManifest.xml
@@ -19,10 +19,13 @@
     <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
     <uses-permission android:name="android.permission.CAPTURE_VIDEO_OUTPUT" />
 
+    <!-- This is a test application that should always be debuggable. -->
+    <!--suppress HardcodedDebugMode -->
     <application android:label="@string/app_name"
                  android:icon="@drawable/ic_launcher"
                  android:allowBackup="false"
-                 android:debuggable="true">
+                 android:debuggable="true"
+                 android:supportsRtl="false">
 
         <activity android:name="ConnectActivity"
                   android:label="@string/app_name"
diff --git a/webrtc/examples/androidapp/res/layout/activity_call.xml b/webrtc/examples/androidapp/res/layout/activity_call.xml
index 793ed58..fdfe9b1 100644
--- a/webrtc/examples/androidapp/res/layout/activity_call.xml
+++ b/webrtc/examples/androidapp/res/layout/activity_call.xml
@@ -1,29 +1,18 @@
 <?xml version="1.0" encoding="utf-8"?>
 
-<RelativeLayout
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent">
+<merge xmlns:android="http://schemas.android.com/apk/res/android">
 
-    <org.appspot.apprtc.PercentFrameLayout
-        android:id="@+id/remote_video_layout"
+    <org.webrtc.SurfaceViewRenderer
+        android:id="@+id/fullscreen_video_view"
         android:layout_width="match_parent"
-        android:layout_height="match_parent">
-        <org.webrtc.SurfaceViewRenderer
-            android:id="@+id/remote_video_view"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content" />
-    </org.appspot.apprtc.PercentFrameLayout>
+        android:layout_height="match_parent" />
 
-    <org.appspot.apprtc.PercentFrameLayout
-        android:id="@+id/local_video_layout"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent">
-        <org.webrtc.SurfaceViewRenderer
-            android:id="@+id/local_video_view"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content" />
-    </org.appspot.apprtc.PercentFrameLayout>
+    <org.webrtc.SurfaceViewRenderer
+        android:id="@+id/pip_video_view"
+        android:layout_height="144dp"
+        android:layout_width="wrap_content"
+        android:layout_gravity="bottom|end"
+        android:layout_margin="16dp"/>
 
     <FrameLayout
         android:id="@+id/call_fragment_container"
@@ -34,4 +23,4 @@
         android:layout_width="match_parent"
         android:layout_height="match_parent" />
 
-</RelativeLayout>
+</merge>
diff --git a/webrtc/examples/androidapp/src/org/appspot/apprtc/CallActivity.java b/webrtc/examples/androidapp/src/org/appspot/apprtc/CallActivity.java
index 309a5be..0197bcb 100644
--- a/webrtc/examples/androidapp/src/org/appspot/apprtc/CallActivity.java
+++ b/webrtc/examples/androidapp/src/org/appspot/apprtc/CallActivity.java
@@ -134,34 +134,37 @@
 
   // Peer connection statistics callback period in ms.
   private static final int STAT_CALLBACK_PERIOD = 1000;
-  // Local preview screen position before call is connected.
-  private static final int LOCAL_X_CONNECTING = 0;
-  private static final int LOCAL_Y_CONNECTING = 0;
-  private static final int LOCAL_WIDTH_CONNECTING = 100;
-  private static final int LOCAL_HEIGHT_CONNECTING = 100;
-  // Local preview screen position after call is connected.
-  private static final int LOCAL_X_CONNECTED = 72;
-  private static final int LOCAL_Y_CONNECTED = 72;
-  private static final int LOCAL_WIDTH_CONNECTED = 25;
-  private static final int LOCAL_HEIGHT_CONNECTED = 25;
-  // Remote video screen position
-  private static final int REMOTE_X = 0;
-  private static final int REMOTE_Y = 0;
-  private static final int REMOTE_WIDTH = 100;
-  private static final int REMOTE_HEIGHT = 100;
+
+  private class ProxyRenderer implements VideoRenderer.Callbacks {
+    private VideoRenderer.Callbacks target;
+
+    synchronized public void renderFrame(VideoRenderer.I420Frame frame) {
+      if (target == null) {
+        Logging.d(TAG, "Dropping frame in proxy because target is null.");
+        VideoRenderer.renderFrameDone(frame);
+        return;
+      }
+
+      target.renderFrame(frame);
+    }
+
+    synchronized public void setTarget(VideoRenderer.Callbacks target) {
+      this.target = target;
+    }
+  }
+
+  private final ProxyRenderer remoteProxyRenderer = new ProxyRenderer();
+  private final ProxyRenderer localProxyRenderer = new ProxyRenderer();
   private PeerConnectionClient peerConnectionClient = null;
   private AppRTCClient appRtcClient;
   private SignalingParameters signalingParameters;
   private AppRTCAudioManager audioManager = null;
   private EglBase rootEglBase;
-  private SurfaceViewRenderer localRender;
-  private SurfaceViewRenderer remoteRenderScreen;
+  private SurfaceViewRenderer pipRenderer;
+  private SurfaceViewRenderer fullscreenRenderer;
   private VideoFileRenderer videoFileRenderer;
   private final List<VideoRenderer.Callbacks> remoteRenderers =
       new ArrayList<VideoRenderer.Callbacks>();
-  private PercentFrameLayout localRenderLayout;
-  private PercentFrameLayout remoteRenderLayout;
-  private ScalingType scalingType;
   private Toast logToast;
   private boolean commandLineRun;
   private int runTimeMs;
@@ -176,6 +179,8 @@
   private boolean screencaptureEnabled = false;
   private static Intent mediaProjectionPermissionResultData;
   private static int mediaProjectionPermissionResultCode;
+  // True if local view is in the fullscreen renderer.
+  private boolean isSwappedFeeds;
 
   // Controls
   private CallFragment callFragment;
@@ -198,13 +203,10 @@
 
     iceConnected = false;
     signalingParameters = null;
-    scalingType = ScalingType.SCALE_ASPECT_FILL;
 
     // Create UI controls.
-    localRender = (SurfaceViewRenderer) findViewById(R.id.local_video_view);
-    remoteRenderScreen = (SurfaceViewRenderer) findViewById(R.id.remote_video_view);
-    localRenderLayout = (PercentFrameLayout) findViewById(R.id.local_video_layout);
-    remoteRenderLayout = (PercentFrameLayout) findViewById(R.id.remote_video_layout);
+    pipRenderer = (SurfaceViewRenderer) findViewById(R.id.pip_video_view);
+    fullscreenRenderer = (SurfaceViewRenderer) findViewById(R.id.fullscreen_video_view);
     callFragment = new CallFragment();
     hudFragment = new HudFragment();
 
@@ -216,15 +218,23 @@
       }
     };
 
-    localRender.setOnClickListener(listener);
-    remoteRenderScreen.setOnClickListener(listener);
-    remoteRenderers.add(remoteRenderScreen);
+    // Swap feeds on pip view click.
+    pipRenderer.setOnClickListener(new View.OnClickListener() {
+      @Override
+      public void onClick(View view) {
+        setSwappedFeeds(!isSwappedFeeds);
+      }
+    });
+
+    fullscreenRenderer.setOnClickListener(listener);
+    remoteRenderers.add(remoteProxyRenderer);
 
     final Intent intent = getIntent();
 
     // Create video renderers.
     rootEglBase = EglBase.create();
-    localRender.init(rootEglBase.getEglBaseContext(), null);
+    pipRenderer.init(rootEglBase.getEglBaseContext(), null);
+    pipRenderer.setScalingType(ScalingType.SCALE_ASPECT_FIT);
     String saveRemoteVideoToFile = intent.getStringExtra(EXTRA_SAVE_REMOTE_VIDEO_TO_FILE);
 
     // When saveRemoteVideoToFile is set we save the video from the remote to a file.
@@ -240,12 +250,14 @@
             "Failed to open video file for output: " + saveRemoteVideoToFile, e);
       }
     }
-    remoteRenderScreen.init(rootEglBase.getEglBaseContext(), null);
+    fullscreenRenderer.init(rootEglBase.getEglBaseContext(), null);
+    fullscreenRenderer.setScalingType(ScalingType.SCALE_ASPECT_FILL);
 
-    localRender.setZOrderMediaOverlay(true);
-    localRender.setEnableHardwareScaler(true /* enabled */);
-    remoteRenderScreen.setEnableHardwareScaler(true /* enabled */);
-    updateVideoView();
+    pipRenderer.setZOrderMediaOverlay(true);
+    pipRenderer.setEnableHardwareScaler(true /* enabled */);
+    fullscreenRenderer.setEnableHardwareScaler(true /* enabled */);
+    // Start with local feed in fullscreen and swap it to the pip when the call is connected.
+    setSwappedFeeds(true /* isSwappedFeeds */);
 
     // Check for mandatory permissions.
     for (String permission : MANDATORY_PERMISSIONS) {
@@ -508,8 +520,7 @@
 
   @Override
   public void onVideoScalingSwitch(ScalingType scalingType) {
-    this.scalingType = scalingType;
-    updateVideoView();
+    fullscreenRenderer.setScalingType(scalingType);
   }
 
   @Override
@@ -547,26 +558,6 @@
     ft.commit();
   }
 
-  private void updateVideoView() {
-    remoteRenderLayout.setPosition(REMOTE_X, REMOTE_Y, REMOTE_WIDTH, REMOTE_HEIGHT);
-    remoteRenderScreen.setScalingType(scalingType);
-    remoteRenderScreen.setMirror(false);
-
-    if (iceConnected) {
-      localRenderLayout.setPosition(
-          LOCAL_X_CONNECTED, LOCAL_Y_CONNECTED, LOCAL_WIDTH_CONNECTED, LOCAL_HEIGHT_CONNECTED);
-      localRender.setScalingType(ScalingType.SCALE_ASPECT_FIT);
-    } else {
-      localRenderLayout.setPosition(
-          LOCAL_X_CONNECTING, LOCAL_Y_CONNECTING, LOCAL_WIDTH_CONNECTING, LOCAL_HEIGHT_CONNECTING);
-      localRender.setScalingType(scalingType);
-    }
-    localRender.setMirror(true);
-
-    localRender.requestLayout();
-    remoteRenderScreen.requestLayout();
-  }
-
   private void startCall() {
     if (appRtcClient == null) {
       Log.e(TAG, "AppRTC client is not allocated for a call.");
@@ -603,10 +594,9 @@
       Log.w(TAG, "Call is connected in closed or error state");
       return;
     }
-    // Update video view.
-    updateVideoView();
     // Enable statistics callback.
     peerConnectionClient.enableStatsEvents(true, STAT_CALLBACK_PERIOD);
+    setSwappedFeeds(false /* isSwappedFeeds */);
   }
 
   // This method is called when the audio manager reports audio device change,
@@ -621,6 +611,8 @@
   // Disconnect from remote resources, dispose of local resources, and exit.
   private void disconnect() {
     activityRunning = false;
+    remoteProxyRenderer.setTarget(null);
+    localProxyRenderer.setTarget(null);
     if (appRtcClient != null) {
       appRtcClient.disconnectFromRoom();
       appRtcClient = null;
@@ -629,17 +621,17 @@
       peerConnectionClient.close();
       peerConnectionClient = null;
     }
-    if (localRender != null) {
-      localRender.release();
-      localRender = null;
+    if (pipRenderer != null) {
+      pipRenderer.release();
+      pipRenderer = null;
     }
     if (videoFileRenderer != null) {
       videoFileRenderer.release();
       videoFileRenderer = null;
     }
-    if (remoteRenderScreen != null) {
-      remoteRenderScreen.release();
-      remoteRenderScreen = null;
+    if (fullscreenRenderer != null) {
+      fullscreenRenderer.release();
+      fullscreenRenderer = null;
     }
     if (audioManager != null) {
       audioManager.stop();
@@ -728,6 +720,15 @@
     return videoCapturer;
   }
 
+  private void setSwappedFeeds(boolean isSwappedFeeds) {
+    Logging.d(TAG, "setSwappedFeeds: " + isSwappedFeeds);
+    this.isSwappedFeeds = isSwappedFeeds;
+    localProxyRenderer.setTarget(isSwappedFeeds ? fullscreenRenderer : pipRenderer);
+    remoteProxyRenderer.setTarget(isSwappedFeeds ? pipRenderer : fullscreenRenderer);
+    fullscreenRenderer.setMirror(isSwappedFeeds);
+    pipRenderer.setMirror(!isSwappedFeeds);
+  }
+
   // -----Implementation of AppRTCClient.AppRTCSignalingEvents ---------------
   // All callbacks are invoked from websocket signaling looper thread and
   // are routed to UI thread.
@@ -740,7 +741,7 @@
     if (peerConnectionParameters.videoCallEnabled) {
       videoCapturer = createVideoCapturer();
     }
-    peerConnectionClient.createPeerConnection(rootEglBase.getEglBaseContext(), localRender,
+    peerConnectionClient.createPeerConnection(rootEglBase.getEglBaseContext(), localProxyRenderer,
         remoteRenderers, videoCapturer, signalingParameters);
 
     if (signalingParameters.initiator) {
diff --git a/webrtc/examples/androidapp/src/org/appspot/apprtc/PercentFrameLayout.java b/webrtc/examples/androidapp/src/org/appspot/apprtc/PercentFrameLayout.java
deleted file mode 100644
index 81f22eb..0000000
--- a/webrtc/examples/androidapp/src/org/appspot/apprtc/PercentFrameLayout.java
+++ /dev/null
@@ -1,94 +0,0 @@
-/*
- *  Copyright 2015 The WebRTC Project Authors. All rights reserved.
- *
- *  Use of this source code is governed by a BSD-style license
- *  that can be found in the LICENSE file in the root of the source
- *  tree. An additional intellectual property rights grant can be found
- *  in the file PATENTS.  All contributing project authors may
- *  be found in the AUTHORS file in the root of the source tree.
- */
-
-package org.appspot.apprtc;
-
-import android.content.Context;
-import android.util.AttributeSet;
-import android.view.View;
-import android.view.ViewGroup;
-
-/**
- * Simple container that confines the children to a subrectangle specified as percentage values of
- * the container size. The children are centered horizontally and vertically inside the confined
- * space.
- */
-public class PercentFrameLayout extends ViewGroup {
-  private int xPercent = 0;
-  private int yPercent = 0;
-  private int widthPercent = 100;
-  private int heightPercent = 100;
-
-  public PercentFrameLayout(Context context) {
-    super(context);
-  }
-
-  public PercentFrameLayout(Context context, AttributeSet attrs) {
-    super(context, attrs);
-  }
-
-  public PercentFrameLayout(Context context, AttributeSet attrs, int defStyleAttr) {
-    super(context, attrs, defStyleAttr);
-  }
-
-  public void setPosition(int xPercent, int yPercent, int widthPercent, int heightPercent) {
-    this.xPercent = xPercent;
-    this.yPercent = yPercent;
-    this.widthPercent = widthPercent;
-    this.heightPercent = heightPercent;
-  }
-
-  @Override
-  public boolean shouldDelayChildPressedState() {
-    return false;
-  }
-
-  @Override
-  protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
-    final int width = getDefaultSize(Integer.MAX_VALUE, widthMeasureSpec);
-    final int height = getDefaultSize(Integer.MAX_VALUE, heightMeasureSpec);
-    setMeasuredDimension(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
-        MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY));
-
-    final int childWidthMeasureSpec =
-        MeasureSpec.makeMeasureSpec(width * widthPercent / 100, MeasureSpec.AT_MOST);
-    final int childHeightMeasureSpec =
-        MeasureSpec.makeMeasureSpec(height * heightPercent / 100, MeasureSpec.AT_MOST);
-    for (int i = 0; i < getChildCount(); ++i) {
-      final View child = getChildAt(i);
-      if (child.getVisibility() != GONE) {
-        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
-      }
-    }
-  }
-
-  @Override
-  protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
-    final int width = right - left;
-    final int height = bottom - top;
-    // Sub-rectangle specified by percentage values.
-    final int subWidth = width * widthPercent / 100;
-    final int subHeight = height * heightPercent / 100;
-    final int subLeft = left + width * xPercent / 100;
-    final int subTop = top + height * yPercent / 100;
-
-    for (int i = 0; i < getChildCount(); ++i) {
-      final View child = getChildAt(i);
-      if (child.getVisibility() != GONE) {
-        final int childWidth = child.getMeasuredWidth();
-        final int childHeight = child.getMeasuredHeight();
-        // Center child both vertically and horizontally.
-        final int childLeft = subLeft + (subWidth - childWidth) / 2;
-        final int childTop = subTop + (subHeight - childHeight) / 2;
-        child.layout(childLeft, childTop, childLeft + childWidth, childTop + childHeight);
-      }
-    }
-  }
-}