Merge "Fix the issue that automatic mute state remains ON after adding VT call"
diff --git a/java/com/android/incallui/CallButtonPresenter.java b/java/com/android/incallui/CallButtonPresenter.java
index 8bf2348..2a9600a 100644
--- a/java/com/android/incallui/CallButtonPresenter.java
+++ b/java/com/android/incallui/CallButtonPresenter.java
@@ -42,6 +42,7 @@
 import com.android.incallui.call.CallList;
 import com.android.incallui.call.DialerCall;
 import com.android.incallui.call.DialerCall.CameraDirection;
+import com.android.incallui.call.DialerCallListener;
 import com.android.incallui.call.TelecomAdapter;
 import com.android.incallui.call.state.DialerCallState;
 import com.android.incallui.incall.protocol.InCallButtonIds;
@@ -58,7 +59,8 @@
         InCallDetailsListener,
         CanAddCallListener,
         Listener,
-        InCallButtonUiDelegate {
+        InCallButtonUiDelegate,
+        DialerCallListener {
 
   private final Context context;
   private InCallButtonUi inCallButtonUi;
@@ -100,11 +102,18 @@
     InCallPresenter.getInstance().getInCallCameraManager().removeCameraSelectionListener(this);
     InCallPresenter.getInstance().removeCanAddCallListener(this);
     isInCallButtonUiReady = false;
+
+    if (call != null) {
+      call.removeListener(this);
+    }
   }
 
   @Override
   public void onStateChange(InCallState oldState, InCallState newState, CallList callList) {
     Trace.beginSection("CallButtonPresenter.onStateChange");
+    if (call != null) {
+      call.removeListener(this);
+    }
     if (newState == InCallState.OUTGOING) {
       call = callList.getOutgoingCall();
     } else if (newState == InCallState.INCALL) {
@@ -127,6 +136,10 @@
     } else {
       call = null;
     }
+
+    if (call != null) {
+      call.addListener(this);
+    }
     updateUi(newState, call);
     Trace.endSection();
   }
@@ -376,7 +389,6 @@
             call.getTimeAddedMs());
 
     if (pause) {
-      call.getVideoTech().setCamera(null);
       call.getVideoTech().stopTransmission();
     } else {
       updateCamera(
@@ -546,6 +558,41 @@
   }
 
   @Override
+  public void onDialerCallSessionModificationStateChange() {
+    if (inCallButtonUi != null && call != null) {
+      inCallButtonUi.enableButton(InCallButtonIds.BUTTON_PAUSE_VIDEO, true);
+      updateButtonsState(call);
+    }
+  }
+
+  @Override
+  public void onDialerCallDisconnect() {}
+
+  @Override
+  public void onDialerCallUpdate() {}
+
+  @Override
+  public void onDialerCallChildNumberChange() {}
+
+  @Override
+  public void onDialerCallLastForwardedNumberChange() {}
+
+  @Override
+  public void onDialerCallUpgradeToVideo() {}
+
+  @Override
+  public void onWiFiToLteHandover() {}
+
+  @Override
+  public void onHandoverToWifiFailure() {}
+
+  @Override
+  public void onInternationalCallOnWifi() {}
+
+  @Override
+  public void onEnrichedCallSessionUpdate() {}
+
+  @Override
   public Context getContext() {
     return context;
   }
diff --git a/java/com/android/incallui/VideoCallPresenter.java b/java/com/android/incallui/VideoCallPresenter.java
index 5bdcd7a..1700d53 100644
--- a/java/com/android/incallui/VideoCallPresenter.java
+++ b/java/com/android/incallui/VideoCallPresenter.java
@@ -323,6 +323,17 @@
     InCallPresenter.InCallState inCallState = InCallPresenter.getInstance().getInCallState();
     onStateChange(inCallState, inCallState, CallList.getInstance());
     isVideoCallScreenUiReady = true;
+
+    Point sourceVideoDimensions = getRemoteVideoSurfaceTexture().getSourceVideoDimensions();
+    if (sourceVideoDimensions != null && primaryCall != null) {
+      int width = primaryCall.getPeerDimensionWidth();
+      int height = primaryCall.getPeerDimensionHeight();
+      boolean updated = DialerCall.UNKNOWN_PEER_DIMENSIONS != width
+          && DialerCall.UNKNOWN_PEER_DIMENSIONS != height;
+      if (updated && (sourceVideoDimensions.x != width || sourceVideoDimensions.y != height)) {
+        onUpdatePeerDimensions(primaryCall, width, height);
+      }
+    }
   }
 
   /** Called when the user interface is no longer ready to be used. */
diff --git a/java/com/android/incallui/call/DialerCall.java b/java/com/android/incallui/call/DialerCall.java
index 1f4e49a..f9afd2d 100644
--- a/java/com/android/incallui/call/DialerCall.java
+++ b/java/com/android/incallui/call/DialerCall.java
@@ -126,6 +126,8 @@
 
   private static int idCounter = 0;
 
+  public static final int UNKNOWN_PEER_DIMENSIONS = -1;
+
   /**
    * A counter used to append to restricted/private/hidden calls so that users can identify them in
    * a conversation. This value is reset in {@link CallList#onCallRemoved(Context, Call)} when there
@@ -386,6 +388,8 @@
       };
 
   private long timeAddedMs;
+  private int peerDimensionWidth = UNKNOWN_PEER_DIMENSIONS;
+  private int peerDimensionHeight = UNKNOWN_PEER_DIMENSIONS;
 
   public DialerCall(
       Context context,
@@ -1558,6 +1562,8 @@
 
   @Override
   public void onPeerDimensionsChanged(int width, int height) {
+    peerDimensionWidth = width;
+    peerDimensionHeight = height;
     InCallVideoCallCallbackNotifier.getInstance().peerDimensionsChanged(this, width, height);
   }
 
@@ -1974,4 +1980,14 @@
   public interface CannedTextResponsesLoadedListener {
     void onCannedTextResponsesLoaded(DialerCall call);
   }
+
+  /** Gets peer dimension width. */
+  public int getPeerDimensionWidth() {
+    return peerDimensionWidth;
+  }
+
+  /** Gets peer dimension height. */
+  public int getPeerDimensionHeight() {
+    return peerDimensionHeight;
+  }
 }
diff --git a/java/com/android/incallui/contactgrid/TopRow.java b/java/com/android/incallui/contactgrid/TopRow.java
index d242c3a..213a3c6 100644
--- a/java/com/android/incallui/contactgrid/TopRow.java
+++ b/java/com/android/incallui/contactgrid/TopRow.java
@@ -92,6 +92,8 @@
     } else if (VideoUtils.hasSentVideoUpgradeRequest(state.sessionModificationState())
         || VideoUtils.hasReceivedVideoUpgradeRequest(state.sessionModificationState())) {
       label = getLabelForVideoRequest(context, state);
+    } else if (state.sessionModificationState() == SessionModificationState.REQUEST_FAILED) {
+      label = context.getString(R.string.incall_video_call_operation_failed);
     } else if (state.state() == DialerCallState.PULLING) {
       label = context.getString(R.string.incall_transferring);
     } else if (state.state() == DialerCallState.DIALING
diff --git a/java/com/android/incallui/contactgrid/res/values/strings.xml b/java/com/android/incallui/contactgrid/res/values/strings.xml
index 9ee10c3..e8592b2 100644
--- a/java/com/android/incallui/contactgrid/res/values/strings.xml
+++ b/java/com/android/incallui/contactgrid/res/values/strings.xml
@@ -65,6 +65,9 @@
        requests and we timed out. -->
   <string name="incall_video_call_request_timed_out">Call timed out</string>
 
+  <!-- Displayed above the contact name when the user's operation for video calling is failed due to an unknown reason. -->
+  <string name="incall_video_call_operation_failed">Unable to operate</string>
+
   <!-- In-call screen: status label for a call that's in the process of hanging up
        [CHAR LIMIT=25] -->
   <string name="incall_hanging_up">Hanging up</string>
diff --git a/java/com/android/incallui/video/impl/VideoCallFragment.java b/java/com/android/incallui/video/impl/VideoCallFragment.java
index e7dff89..3a355c4 100644
--- a/java/com/android/incallui/video/impl/VideoCallFragment.java
+++ b/java/com/android/incallui/video/impl/VideoCallFragment.java
@@ -540,13 +540,13 @@
       return new Point();
     }
     if (isLandscape()) {
-      int stableInsetEnd =
+      int systemWindowInsetEnd =
           getView().getLayoutDirection() == View.LAYOUT_DIRECTION_RTL
-              ? getView().getRootWindowInsets().getStableInsetLeft()
-              : -getView().getRootWindowInsets().getStableInsetRight();
-      return new Point(stableInsetEnd, 0);
+              ? getView().getRootWindowInsets().getSystemWindowInsetLeft()
+              : -getView().getRootWindowInsets().getSystemWindowInsetRight();
+      return new Point(systemWindowInsetEnd, 0);
     } else {
-      return new Point(0, -getView().getRootWindowInsets().getStableInsetBottom());
+      return new Point(0, -getView().getRootWindowInsets().getSystemWindowInsetBottom());
     }
   }
 
@@ -695,9 +695,17 @@
     videoCallScreenDelegate.getLocalVideoSurfaceTexture().attachToTextureView(previewTextureView);
     videoCallScreenDelegate.getRemoteVideoSurfaceTexture().attachToTextureView(remoteTextureView);
 
-    this.isRemotelyHeld = isRemotelyHeld;
+    boolean updateRemoteOffView = false;
     if (this.shouldShowRemote != shouldShowRemote) {
       this.shouldShowRemote = shouldShowRemote;
+      updateRemoteOffView = true;
+    }
+    if (this.isRemotelyHeld != isRemotelyHeld) {
+      this.isRemotelyHeld = isRemotelyHeld;
+      updateRemoteOffView = true;
+    }
+
+    if (updateRemoteOffView) {
       updateRemoteOffView();
     }
     if (this.shouldShowPreview != shouldShowPreview) {
diff --git a/java/com/android/incallui/videotech/ims/ImsVideoCallCallback.java b/java/com/android/incallui/videotech/ims/ImsVideoCallCallback.java
index d254d6d..3e6f441 100644
--- a/java/com/android/incallui/videotech/ims/ImsVideoCallCallback.java
+++ b/java/com/android/incallui/videotech/ims/ImsVideoCallCallback.java
@@ -103,8 +103,6 @@
 
     if (videoTech.getSessionModificationState()
         == SessionModificationState.WAITING_FOR_UPGRADE_TO_VIDEO_RESPONSE) {
-      handler.removeCallbacksAndMessages(null); // Clear everything
-
       final int newSessionModificationState = getSessionModificationStateFromTelecomStatus(status);
       if (status == VideoProvider.SESSION_MODIFY_REQUEST_SUCCESS) {
         // Telecom manages audio route for us
@@ -114,31 +112,21 @@
         videoTech.setSessionModificationState(newSessionModificationState);
       }
 
-      // Wait for 4 seconds and then clean the session modification state. This allows the video UI
-      // to stay up so that the user can read the error message.
-      //
       // If the other person accepted the upgrade request then this will keep the video UI up until
       // the call's video state change. Without this we would switch to the voice call and then
       // switch back to video UI.
-      handler.postDelayed(
-          () -> {
-            if (videoTech.getSessionModificationState() == newSessionModificationState) {
-              LogUtil.i("ImsVideoCallCallback.onSessionModifyResponseReceived", "clearing state");
-              videoTech.setSessionModificationState(SessionModificationState.NO_REQUEST);
-            } else {
-              LogUtil.i(
-                  "ImsVideoCallCallback.onSessionModifyResponseReceived",
-                  "session modification state has changed, not clearing state");
-            }
-          },
-          CLEAR_FAILED_REQUEST_TIMEOUT_MILLIS);
+      clearFailedResponseState(newSessionModificationState);
     } else if (videoTech.getSessionModificationState()
         == SessionModificationState.RECEIVED_UPGRADE_TO_VIDEO_REQUEST) {
       requestedVideoState = VideoProfile.STATE_AUDIO_ONLY;
       videoTech.setSessionModificationState(SessionModificationState.NO_REQUEST);
     } else if (videoTech.getSessionModificationState()
         == SessionModificationState.WAITING_FOR_RESPONSE) {
-      videoTech.setSessionModificationState(getSessionModificationStateFromTelecomStatus(status));
+      final int newSessionModificationState = getSessionModificationStateFromTelecomStatus(status);
+      videoTech.setSessionModificationState(newSessionModificationState);
+      if (status != VideoProvider.SESSION_MODIFY_REQUEST_SUCCESS) {
+        clearFailedResponseState(newSessionModificationState);
+      }
     } else {
       LogUtil.i(
           "ImsVideoCallCallback.onSessionModifyResponseReceived",
@@ -146,6 +134,24 @@
     }
   }
 
+  private void clearFailedResponseState(final int newSessionModificationState) {
+    handler.removeCallbacksAndMessages(null); // Clear everything
+    // Wait for 4 seconds and then clean the session modification state. This allows the video UI
+    // to stay up so that the user can read the error message.
+    handler.postDelayed(
+        () -> {
+          if (videoTech.getSessionModificationState() == newSessionModificationState) {
+            LogUtil.i("ImsVideoCallCallback.onSessionModifyResponseReceived", "clearing state");
+            videoTech.setSessionModificationState(SessionModificationState.NO_REQUEST);
+          } else {
+            LogUtil.i(
+                "ImsVideoCallCallback.onSessionModifyResponseReceived",
+                "session modification state has changed, not clearing state");
+          }
+        },
+        CLEAR_FAILED_REQUEST_TIMEOUT_MILLIS);
+  }
+
   @SessionModificationState
   private int getSessionModificationStateFromTelecomStatus(int telecomStatus) {
     switch (telecomStatus) {
diff --git a/java/com/android/incallui/videotech/ims/ImsVideoTech.java b/java/com/android/incallui/videotech/ims/ImsVideoTech.java
index 5b733d6..1d4fe76 100644
--- a/java/com/android/incallui/videotech/ims/ImsVideoTech.java
+++ b/java/com/android/incallui/videotech/ims/ImsVideoTech.java
@@ -227,6 +227,7 @@
     call.getVideoCall()
         .sendSessionModifyRequest(
             new VideoProfile(unpausedVideoState & ~VideoProfile.STATE_TX_ENABLED));
+    setSessionModificationState(SessionModificationState.WAITING_FOR_RESPONSE);
   }
 
   @Override