Reconnect when ImsService binder instance is null am: ff0979256d
am: ab749c4181

Change-Id: I34795af5a91fdb114611ad70d175f6b680546e03
diff --git a/src/java/com/android/ims/ImsCall.java b/src/java/com/android/ims/ImsCall.java
index 221b4c0..ad289fd 100644
--- a/src/java/com/android/ims/ImsCall.java
+++ b/src/java/com/android/ims/ImsCall.java
@@ -1881,13 +1881,16 @@
             setIsMerged(playDisconnectTone);
             mSessionEndDuringMerge = true;
             String reasonInfo;
+            int reasonCode = ImsReasonInfo.CODE_UNSPECIFIED;
             if (playDisconnectTone) {
+                reasonCode = ImsReasonInfo.CODE_USER_TERMINATED_BY_REMOTE;
                 reasonInfo = "Call ended by network";
             } else {
+                reasonCode = ImsReasonInfo.CODE_LOCAL_ENDED_BY_CONFERENCE_MERGE;
                 reasonInfo = "Call ended during conference merge process.";
             }
             mSessionEndDuringMergeReasonInfo = new ImsReasonInfo(
-                    ImsReasonInfo.CODE_UNSPECIFIED, 0, reasonInfo);
+                    reasonCode, 0, reasonInfo);
         }
     }
 
@@ -3343,6 +3346,7 @@
         sb.append(" mute:");
         sb.append(isMuted() ? "Y" : "N");
         if (mCallProfile != null) {
+            sb.append(" mCallProfile:" + mCallProfile);
             sb.append(" tech:");
             sb.append(mCallProfile.getCallExtra(ImsCallProfile.EXTRA_CALL_RAT_TYPE));
         }
@@ -3367,8 +3371,12 @@
         sb.append(isConferenceHost() ? "Y" : "N");
         sb.append(" buried term:");
         sb.append(mSessionEndDuringMerge ? "Y" : "N");
+        sb.append(" isVideo: ");
+        sb.append(isVideoCall() ? "Y" : "N");
         sb.append(" wasVideo: ");
         sb.append(mWasVideoCall ? "Y" : "N");
+        sb.append(" isWifi: ");
+        sb.append(isWifiCall() ? "Y" : "N");
         sb.append(" session:");
         sb.append(mSession);
         sb.append(" transientSession:");
diff --git a/src/java/com/android/ims/ImsConnectionStateListener.java b/src/java/com/android/ims/ImsConnectionStateListener.java
index f281df1..4425854 100644
--- a/src/java/com/android/ims/ImsConnectionStateListener.java
+++ b/src/java/com/android/ims/ImsConnectionStateListener.java
@@ -27,13 +27,6 @@
  */
 public class ImsConnectionStateListener {
     /**
-     * Called when the device is connected to the IMS network.
-     */
-    public void onImsConnected() {
-        // no-op
-    }
-
-    /**
      * Called when the device is connected to the IMS network with {@param imsRadioTech}.
      */
     public void onImsConnected(int imsRadioTech) {
@@ -41,9 +34,9 @@
     }
 
     /**
-     * Called when the device is trying to connect to the IMS network.
+     * Called when the device is trying to connect to the IMS network with {@param imsRadioTech}.
      */
-    public void onImsProgressing() {
+    public void onImsProgressing(int imsRadioTech) {
         // no-op
     }
 
diff --git a/src/java/com/android/ims/ImsManager.java b/src/java/com/android/ims/ImsManager.java
index 52d356e..7cb192c 100644
--- a/src/java/com/android/ims/ImsManager.java
+++ b/src/java/com/android/ims/ImsManager.java
@@ -32,6 +32,7 @@
 import android.telecom.TelecomManager;
 import android.telephony.CarrierConfigManager;
 import android.telephony.Rlog;
+import android.telephony.ServiceState;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
 import android.telephony.ims.ImsServiceProxy;
@@ -2234,7 +2235,7 @@
             }
 
             if (mListener != null) {
-                mListener.onImsConnected();
+                mListener.onImsConnected(ServiceState.RIL_RADIO_TECHNOLOGY_UNKNOWN);
             }
         }
 
@@ -2245,7 +2246,7 @@
             }
 
             if (mListener != null) {
-                mListener.onImsProgressing();
+                mListener.onImsProgressing(ServiceState.RIL_RADIO_TECHNOLOGY_UNKNOWN);
             }
         }
 
@@ -2258,7 +2259,6 @@
             }
 
             if (mListener != null) {
-                mListener.onImsConnected();
                 mListener.onImsConnected(imsRadioTech);
             }
         }
@@ -2272,7 +2272,7 @@
             }
 
             if (mListener != null) {
-                mListener.onImsProgressing();
+                mListener.onImsProgressing(imsRadioTech);
             }
         }
 
@@ -2317,7 +2317,7 @@
                     serviceClass + ", event=" + event);
 
             if (mListener != null) {
-                mListener.onImsConnected();
+                mListener.onImsConnected(ServiceState.RIL_RADIO_TECHNOLOGY_UNKNOWN);
             }
         }
 
diff --git a/src/java/com/android/ims/internal/ImsVideoCallProviderWrapper.java b/src/java/com/android/ims/internal/ImsVideoCallProviderWrapper.java
index fcb9e09..d6da824 100644
--- a/src/java/com/android/ims/internal/ImsVideoCallProviderWrapper.java
+++ b/src/java/com/android/ims/internal/ImsVideoCallProviderWrapper.java
@@ -29,6 +29,7 @@
 import android.telecom.VideoProfile;
 import android.view.Surface;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.os.SomeArgs;
 
 import java.util.Collections;
@@ -67,6 +68,7 @@
     private final Set<ImsVideoProviderWrapperCallback> mCallbacks = Collections.newSetFromMap(
             new ConcurrentHashMap<ImsVideoProviderWrapperCallback, Boolean>(8, 0.9f, 1));
     private VideoPauseTracker mVideoPauseTracker = new VideoPauseTracker();
+    private boolean mUseVideoPauseWorkaround = false;
 
     private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {
         @Override
@@ -207,13 +209,26 @@
      *
      * @param VideoProvider
      */
-    public ImsVideoCallProviderWrapper(IImsVideoCallProvider VideoProvider)
+    public ImsVideoCallProviderWrapper(IImsVideoCallProvider videoProvider)
             throws RemoteException {
-        mVideoCallProvider = VideoProvider;
-        mVideoCallProvider.asBinder().linkToDeath(mDeathRecipient, 0);
 
-        mBinder = new ImsVideoCallCallback();
-        mVideoCallProvider.setCallback(mBinder);
+        mVideoCallProvider = videoProvider;
+        if (videoProvider != null) {
+            mVideoCallProvider.asBinder().linkToDeath(mDeathRecipient, 0);
+
+            mBinder = new ImsVideoCallCallback();
+            mVideoCallProvider.setCallback(mBinder);
+        } else {
+            mBinder = null;
+        }
+    }
+
+    @VisibleForTesting
+    public ImsVideoCallProviderWrapper(IImsVideoCallProvider videoProvider,
+            VideoPauseTracker videoPauseTracker)
+            throws RemoteException {
+        this(videoProvider);
+        mVideoPauseTracker = videoPauseTracker;
     }
 
     /** @inheritDoc */
@@ -323,7 +338,8 @@
      * @param to The to video state.
      * @return {@code true} if a pause was requested.
      */
-    private static boolean isPauseRequest(int from, int to) {
+    @VisibleForTesting
+    public static boolean isPauseRequest(int from, int to) {
         boolean fromPaused = VideoProfile.isPaused(from);
         boolean toPaused = VideoProfile.isPaused(to);
 
@@ -337,7 +353,8 @@
      * @param to The to video state.
      * @return {@code true} if a resume was requested.
      */
-    private static boolean isResumeRequest(int from, int to) {
+    @VisibleForTesting
+    public static boolean isResumeRequest(int from, int to) {
         boolean fromPaused = VideoProfile.isPaused(from);
         boolean toPaused = VideoProfile.isPaused(to);
 
@@ -345,6 +362,30 @@
     }
 
     /**
+     * Determines if this request includes turning the camera off (ie turning off transmission).
+     * @param from the from video state.
+     * @param to the to video state.
+     * @return true if the state change disables the user's camera.
+     */
+    @VisibleForTesting
+    public static boolean isTurnOffCameraRequest(int from, int to) {
+        return VideoProfile.isTransmissionEnabled(from)
+                && !VideoProfile.isTransmissionEnabled(to);
+    }
+
+    /**
+     * Determines if this request includes turning the camera on (ie turning on transmission).
+     * @param from the from video state.
+     * @param to the to video state.
+     * @return true if the state change enables the user's camera.
+     */
+    @VisibleForTesting
+    public static boolean isTurnOnCameraRequest(int from, int to) {
+        return !VideoProfile.isTransmissionEnabled(from)
+                && VideoProfile.isTransmissionEnabled(to);
+    }
+
+    /**
      * Filters incoming pause and resume requests based on whether there are other active pause or
      * resume requests at the current time.
      *
@@ -361,7 +402,8 @@
      * @return The new toProfile, with the pause bit set or unset based on whether we should
      *      actually pause or resume the video at the current time.
      */
-    private VideoProfile maybeFilterPauseResume(VideoProfile fromProfile, VideoProfile toProfile,
+    @VisibleForTesting
+    public VideoProfile maybeFilterPauseResume(VideoProfile fromProfile, VideoProfile toProfile,
             int source) {
         int fromVideoState = fromProfile.getVideoState();
         int toVideoState = toProfile.getVideoState();
@@ -381,7 +423,9 @@
         boolean isPauseRequest = isPauseRequest(fromVideoState, toVideoState) || isPauseSpecialCase;
         boolean isResumeRequest = isResumeRequest(fromVideoState, toVideoState);
         if (isPauseRequest) {
-            Log.i(this, "maybeFilterPauseResume: isPauseRequest");
+            Log.i(this, "maybeFilterPauseResume: isPauseRequest (from=%s, to=%s)",
+                    VideoProfile.videoStateToString(fromVideoState),
+                    VideoProfile.videoStateToString(toVideoState));
             // Check if we have already paused the video in the past.
             if (!mVideoPauseTracker.shouldPauseVideoFor(source) && !isPauseSpecialCase) {
                 // Note: We don't want to remove the "pause" in the "special case" scenario. If we
@@ -393,7 +437,29 @@
                 toProfile = new VideoProfile(toVideoState, toProfile.getQuality());
             }
         } else if (isResumeRequest) {
-            Log.i(this, "maybeFilterPauseResume: isResumeRequest");
+            boolean isTurnOffCameraRequest = isTurnOffCameraRequest(fromVideoState, toVideoState);
+            boolean isTurnOnCameraRequest = isTurnOnCameraRequest(fromVideoState, toVideoState);
+            // TODO: Fix vendor code so that this isn't required.
+            // Some vendors do not properly handle turning the camera on/off when the video is
+            // in paused state.
+            // If the request is to turn on/off the camera, it might be in the unfortunate format:
+            // FROM: Audio Tx Rx Pause TO: Audio Rx
+            // FROM: Audio Rx Pause TO: Audio Rx Tx
+            // If this is the case, we should not treat this request as a resume request as well.
+            // Ideally the IMS stack should treat a turn off camera request as:
+            // FROM: Audio Tx Rx Pause TO: Audio Rx Pause
+            // FROM: Audio Rx Pause TO: Audio Rx Tx Pause
+            // Unfortunately, it does not. ¯\_(ツ)_/¯
+            if (mUseVideoPauseWorkaround && (isTurnOffCameraRequest || isTurnOnCameraRequest)) {
+                Log.i(this, "maybeFilterPauseResume: isResumeRequest, but camera turning on/off so "
+                        + "skipping (from=%s, to=%s)",
+                        VideoProfile.videoStateToString(fromVideoState),
+                        VideoProfile.videoStateToString(toVideoState));
+                return toProfile;
+            }
+            Log.i(this, "maybeFilterPauseResume: isResumeRequest (from=%s, to=%s)",
+                    VideoProfile.videoStateToString(fromVideoState),
+                    VideoProfile.videoStateToString(toVideoState));
             // Check if we should remain paused (other pause requests pending).
             if (!mVideoPauseTracker.shouldResumeVideoFor(source)) {
                 // There are other pause requests from other sources which are still active, so we
@@ -465,4 +531,23 @@
     public boolean wasVideoPausedFromSource(int source) {
         return mVideoPauseTracker.wasVideoPausedFromSource(source);
     }
+
+    public void setUseVideoPauseWorkaround(boolean useVideoPauseWorkaround) {
+        mUseVideoPauseWorkaround = useVideoPauseWorkaround;
+    }
+
+    /**
+     * Called by {@code ImsPhoneConnection} when there is a change to the video state of the call.
+     * Informs the video pause tracker that the video is no longer paused.  This ensures that
+     * subsequent pause requests are not filtered out.
+     *
+     * @param newVideoState The new video state.
+     */
+    public void onVideoStateChanged(int newVideoState) {
+        if (mVideoPauseTracker.isPaused() && !VideoProfile.isPaused(newVideoState)) {
+            Log.i(this, "onVideoStateChanged: newVideoState=%s, clearing pending pause requests.",
+                    VideoProfile.videoStateToString(newVideoState));
+            mVideoPauseTracker.clearPauseRequests();
+        }
+    }
 }
diff --git a/src/java/com/android/ims/internal/VideoPauseTracker.java b/src/java/com/android/ims/internal/VideoPauseTracker.java
index d37f7fa..a23c590 100644
--- a/src/java/com/android/ims/internal/VideoPauseTracker.java
+++ b/src/java/com/android/ims/internal/VideoPauseTracker.java
@@ -150,6 +150,15 @@
     }
 
     /**
+     * Clears pending pause requests for the tracker.
+     */
+    public void clearPauseRequests() {
+        synchronized (mPauseRequestsLock) {
+            mPauseRequests.clear();
+        }
+    }
+
+    /**
      * Returns a string equivalent of a {@code SOURCE_*} constant.
      *
      * @param source A {@code SOURCE_*} constant.