Remove Session Id from ImsService APIs that do not need it.
am: b5f30c7854

Change-Id: I064d41d55246dbc5ed4fa131852320070c40dd9d
diff --git a/src/java/com/android/ims/ImsConnectionStateListener.java b/src/java/com/android/ims/ImsConnectionStateListener.java
index 1158375..f281df1 100644
--- a/src/java/com/android/ims/ImsConnectionStateListener.java
+++ b/src/java/com/android/ims/ImsConnectionStateListener.java
@@ -34,6 +34,13 @@
     }
 
     /**
+     * Called when the device is connected to the IMS network with {@param imsRadioTech}.
+     */
+    public void onImsConnected(int imsRadioTech) {
+        // no-op
+    }
+
+    /**
      * Called when the device is trying to connect to the IMS network.
      */
     public void onImsProgressing() {
@@ -83,4 +90,11 @@
     public void registrationAssociatedUriChanged(Uri[] uris) {
         // no-op
     }
+
+    /**
+     * Called when IMS registration attempt on {@param imsRadioTech} failed
+     */
+    public void onRegistrationChangeFailed(int imsRadioTech, ImsReasonInfo imsReasonInfo) {
+        // no-op
+    }
 }
diff --git a/src/java/com/android/ims/ImsManager.java b/src/java/com/android/ims/ImsManager.java
index 98ff583..96debae 100644
--- a/src/java/com/android/ims/ImsManager.java
+++ b/src/java/com/android/ims/ImsManager.java
@@ -471,9 +471,12 @@
                     imsManager.turnOffIms();
                 }
 
-                // Force IMS to register over LTE when turning off WFC
+                TelephonyManager tm = (TelephonyManager) context
+                        .getSystemService(Context.TELEPHONY_SERVICE);
                 setWfcModeInternal(context, enabled
-                        ? getWfcMode(context)
+                        // Choose wfc mode per current roaming preference
+                        ? getWfcMode(context, tm.isNetworkRoaming())
+                        // Force IMS to register over LTE when turning off WFC
                         : ImsConfig.WfcModeFeatureValueConstants.CELLULAR_PREFERRED);
             } catch (ImsException e) {
                 loge("setWfcSetting(): ", e);
@@ -821,8 +824,11 @@
         boolean enabled = isVtEnabledByUser(mContext);
         boolean isNonTty = isNonTtyOrTtyOnVolteEnabled(mContext);
         boolean isDataEnabled = isDataEnabled();
+        boolean ignoreDataEnabledChanged = getBooleanCarrierConfig(mContext,
+                CarrierConfigManager.KEY_IGNORE_DATA_ENABLED_CHANGED_FOR_VIDEO_CALLS);
 
-        boolean isFeatureOn = available && enabled && isNonTty && isDataEnabled;
+        boolean isFeatureOn = available && enabled && isNonTty
+                && (ignoreDataEnabledChanged || isDataEnabled);
 
         log("updateVideoCallFeatureValue: available = " + available
                 + ", enabled = " + enabled
@@ -1561,8 +1567,10 @@
                         TelephonyManager.NETWORK_TYPE_LTE, turnOn ? 1 : 0, mImsConfigListener);
 
                 if (isVtEnabledByPlatform(mContext)) {
+                    boolean ignoreDataEnabledChanged = getBooleanCarrierConfig(mContext,
+                            CarrierConfigManager.KEY_IGNORE_DATA_ENABLED_CHANGED_FOR_VIDEO_CALLS);
                     boolean enableViLte = turnOn && isVtEnabledByUser(mContext) &&
-                            isDataEnabled();
+                            (ignoreDataEnabledChanged || isDataEnabled());
                     config.setFeatureValue(ImsConfig.FeatureConstants.FEATURE_TYPE_VIDEO_OVER_LTE,
                             TelephonyManager.NETWORK_TYPE_LTE,
                             enableViLte ? 1 : 0,
@@ -1684,6 +1692,7 @@
 
             if (mListener != null) {
                 mListener.onImsConnected();
+                mListener.onImsConnected(imsRadioTech);
             }
         }
 
@@ -1773,6 +1782,16 @@
                 mListener.registrationAssociatedUriChanged(uris);
             }
         }
+
+        @Override
+        public void registrationChangeFailed(int targetAccessTech, ImsReasonInfo imsReasonInfo) {
+            if (DBG) log("registrationChangeFailed :: targetAccessTech=" + targetAccessTech +
+                    ", imsReasonInfo=" + imsReasonInfo);
+
+            if (mListener != null) {
+                mListener.onRegistrationChangeFailed(targetAccessTech, imsReasonInfo);
+            }
+        }
     }
 
     /**
@@ -1913,6 +1932,8 @@
         pw.println("  mConfigUpdated = " + mConfigUpdated);
         pw.println("  mImsServiceProxy = " + mImsServiceProxy);
         pw.println("  mDataEnabled = " + isDataEnabled());
+        pw.println("  ignoreDataEnabledChanged = " + getBooleanCarrierConfig(mContext,
+                CarrierConfigManager.KEY_IGNORE_DATA_ENABLED_CHANGED_FOR_VIDEO_CALLS));
 
         pw.println("  isGbaValid = " + isGbaValid(mContext));
         pw.println("  isImsTurnOffAllowed = " + isImsTurnOffAllowed());
diff --git a/src/java/com/android/ims/internal/ImsVideoCallProviderWrapper.java b/src/java/com/android/ims/internal/ImsVideoCallProviderWrapper.java
index 0b239ac..fcb9e09 100644
--- a/src/java/com/android/ims/internal/ImsVideoCallProviderWrapper.java
+++ b/src/java/com/android/ims/internal/ImsVideoCallProviderWrapper.java
@@ -25,6 +25,7 @@
 import android.os.RegistrantList;
 import android.os.RemoteException;
 import android.telecom.Connection;
+import android.telecom.Log;
 import android.telecom.VideoProfile;
 import android.view.Surface;
 
@@ -65,6 +66,7 @@
     private RegistrantList mDataUsageUpdateRegistrants = new RegistrantList();
     private final Set<ImsVideoProviderWrapperCallback> mCallbacks = Collections.newSetFromMap(
             new ConcurrentHashMap<ImsVideoProviderWrapperCallback, Boolean>(8, 0.9f, 1));
+    private VideoPauseTracker mVideoPauseTracker = new VideoPauseTracker();
 
     private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {
         @Override
@@ -254,9 +256,29 @@
         }
     }
 
-    /** @inheritDoc */
+    /**
+     * Handles session modify requests received from the {@link android.telecom.InCallService}.
+     *
+     * @inheritDoc
+     **/
     public void onSendSessionModifyRequest(VideoProfile fromProfile, VideoProfile toProfile) {
+        if (fromProfile == null || toProfile == null) {
+            Log.w(this, "onSendSessionModifyRequest: null profile in request.");
+            return;
+        }
+
         try {
+            toProfile = maybeFilterPauseResume(fromProfile, toProfile,
+                    VideoPauseTracker.SOURCE_INCALL);
+
+            int fromVideoState = fromProfile.getVideoState();
+            int toVideoState = toProfile.getVideoState();
+            Log.i(this, "onSendSessionModifyRequest: fromVideoState=%s, toVideoState=%s; ",
+                    VideoProfile.videoStateToString(fromProfile.getVideoState()),
+                    VideoProfile.videoStateToString(toProfile.getVideoState()));
+            if (fromVideoState == toVideoState) {
+                return;
+            }
             mVideoCallProvider.sendSessionModifyRequest(fromProfile, toProfile);
         } catch (RemoteException e) {
         }
@@ -293,4 +315,154 @@
         } catch (RemoteException e) {
         }
     }
+
+    /**
+     * Determines if a session modify request represents a request to pause the video.
+     *
+     * @param from The from video state.
+     * @param to The to video state.
+     * @return {@code true} if a pause was requested.
+     */
+    private static boolean isPauseRequest(int from, int to) {
+        boolean fromPaused = VideoProfile.isPaused(from);
+        boolean toPaused = VideoProfile.isPaused(to);
+
+        return !fromPaused && toPaused;
+    }
+
+    /**
+     * Determines if a session modify request represents a request to resume the video.
+     *
+     * @param from The from video state.
+     * @param to The to video state.
+     * @return {@code true} if a resume was requested.
+     */
+    private static boolean isResumeRequest(int from, int to) {
+        boolean fromPaused = VideoProfile.isPaused(from);
+        boolean toPaused = VideoProfile.isPaused(to);
+
+        return fromPaused && !toPaused;
+    }
+
+    /**
+     * Filters incoming pause and resume requests based on whether there are other active pause or
+     * resume requests at the current time.
+     *
+     * Requests to pause the video stream using the {@link VideoProfile#STATE_PAUSED} bit can come
+     * from both the {@link android.telecom.InCallService}, as well as via the
+     * {@link #pauseVideo(int, int)} and {@link #resumeVideo(int, int)} methods.  As a result,
+     * multiple sources can potentially pause or resume the video stream.  This method ensures that
+     * providing any one request source has paused the video that the video will remain paused.
+     *
+     * @param fromProfile The request's from {@link VideoProfile}.
+     * @param toProfile The request's to {@link VideoProfile}.
+     * @param source The source of the request, as identified by a {@code VideoPauseTracker#SOURCE*}
+     *               constant.
+     * @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,
+            int source) {
+        int fromVideoState = fromProfile.getVideoState();
+        int toVideoState = toProfile.getVideoState();
+
+        // TODO: Remove the following workaround in favor of a new API.
+        // The current sendSessionModifyRequest API has a flaw.  If the video is already
+        // paused, it is not possible for the IncallService to inform the VideoProvider that
+        // it wishes to pause due to multi-tasking.
+        // In a future release we should add a new explicity pauseVideo and resumeVideo API
+        // instead of a difference between two video states.
+        // For now, we'll assume if the request is from pause to pause, we'll still try to
+        // pause.
+        boolean isPauseSpecialCase = (source == VideoPauseTracker.SOURCE_INCALL &&
+                VideoProfile.isPaused(fromVideoState) &&
+                VideoProfile.isPaused(toVideoState));
+
+        boolean isPauseRequest = isPauseRequest(fromVideoState, toVideoState) || isPauseSpecialCase;
+        boolean isResumeRequest = isResumeRequest(fromVideoState, toVideoState);
+        if (isPauseRequest) {
+            Log.i(this, "maybeFilterPauseResume: isPauseRequest");
+            // 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
+                // do the resulting request will be from PAUSED --> UNPAUSED, which would resume the
+                // video.
+
+                // Video was already paused, so remove the pause in the "to" profile.
+                toVideoState = toVideoState & ~VideoProfile.STATE_PAUSED;
+                toProfile = new VideoProfile(toVideoState, toProfile.getQuality());
+            }
+        } else if (isResumeRequest) {
+            Log.i(this, "maybeFilterPauseResume: isResumeRequest");
+            // 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
+                // should remain paused.
+                toVideoState = toVideoState | VideoProfile.STATE_PAUSED;
+                toProfile = new VideoProfile(toVideoState, toProfile.getQuality());
+            }
+        }
+
+        return toProfile;
+    }
+
+    /**
+     * Issues a request to pause the video using {@link VideoProfile#STATE_PAUSED} from a source
+     * other than the InCall UI.
+     *
+     * @param fromVideoState The current video state (prior to issuing the pause).
+     * @param source The source of the pause request.
+     */
+    public void pauseVideo(int fromVideoState, int source) {
+        if (mVideoPauseTracker.shouldPauseVideoFor(source)) {
+            // We should pause the video (its not already paused).
+            VideoProfile fromProfile = new VideoProfile(fromVideoState);
+            VideoProfile toProfile = new VideoProfile(fromVideoState | VideoProfile.STATE_PAUSED);
+
+            try {
+                Log.i(this, "pauseVideo: fromVideoState=%s, toVideoState=%s",
+                        VideoProfile.videoStateToString(fromProfile.getVideoState()),
+                        VideoProfile.videoStateToString(toProfile.getVideoState()));
+                mVideoCallProvider.sendSessionModifyRequest(fromProfile, toProfile);
+            } catch (RemoteException e) {
+            }
+        } else {
+            Log.i(this, "pauseVideo: video already paused");
+        }
+    }
+
+    /**
+     * Issues a request to resume the video using {@link VideoProfile#STATE_PAUSED} from a source
+     * other than the InCall UI.
+     *
+     * @param fromVideoState The current video state (prior to issuing the resume).
+     * @param source The source of the resume request.
+     */
+    public void resumeVideo(int fromVideoState, int source) {
+        if (mVideoPauseTracker.shouldResumeVideoFor(source)) {
+            // We are the last source to resume, so resume now.
+            VideoProfile fromProfile = new VideoProfile(fromVideoState);
+            VideoProfile toProfile = new VideoProfile(fromVideoState & ~VideoProfile.STATE_PAUSED);
+
+            try {
+                Log.i(this, "resumeVideo: fromVideoState=%s, toVideoState=%s",
+                        VideoProfile.videoStateToString(fromProfile.getVideoState()),
+                        VideoProfile.videoStateToString(toProfile.getVideoState()));
+                mVideoCallProvider.sendSessionModifyRequest(fromProfile, toProfile);
+            } catch (RemoteException e) {
+            }
+        } else {
+            Log.i(this, "resumeVideo: remaining paused (paused from other sources)");
+        }
+    }
+
+    /**
+     * Determines if a specified source has issued a pause request.
+     *
+     * @param source The source.
+     * @return {@code true} if the source issued a pause request, {@code false} otherwise.
+     */
+    public boolean wasVideoPausedFromSource(int source) {
+        return mVideoPauseTracker.wasVideoPausedFromSource(source);
+    }
 }
diff --git a/src/java/com/android/ims/internal/VideoPauseTracker.java b/src/java/com/android/ims/internal/VideoPauseTracker.java
new file mode 100644
index 0000000..d37f7fa
--- /dev/null
+++ b/src/java/com/android/ims/internal/VideoPauseTracker.java
@@ -0,0 +1,181 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.ims.internal;
+
+import android.telecom.Log;
+import android.telecom.VideoProfile;
+import android.util.ArraySet;
+
+import java.util.Collection;
+import java.util.Set;
+import java.util.StringJoiner;
+import java.util.stream.Collectors;
+
+/**
+ * Used by an {@link ImsVideoCallProviderWrapper} to track requests to pause video from various
+ * sources.
+ *
+ * Requests to pause the video stream using the {@link VideoProfile#STATE_PAUSED} bit can come
+ * from both the {@link android.telecom.InCallService}, as well as via the
+ * {@link ImsVideoCallProviderWrapper#pauseVideo(int, int)} and
+ * {@link ImsVideoCallProviderWrapper#resumeVideo(int, int)} methods.  As a result, multiple sources
+ * can potentially pause or resume the video stream.
+ *
+ * This class is responsible for tracking any active requests to pause the video.
+ */
+public class VideoPauseTracker {
+    /** The pause or resume request originated from an InCallService. */
+    public static final int SOURCE_INCALL = 1;
+
+    /**
+     * The pause or resume request originated from a change to the data enabled state from the
+     * {@code ImsPhoneCallTracker#onDataEnabledChanged(boolean, int)} callback.  This happens when
+     * the user reaches their data limit or enables and disables data.
+     */
+    public static final int SOURCE_DATA_ENABLED = 2;
+
+    private static final String SOURCE_INCALL_STR = "INCALL";
+    private static final String SOURCE_DATA_ENABLED_STR = "DATA_ENABLED";
+
+    /**
+     * Tracks the current sources of pause requests.
+     */
+    private Set<Integer> mPauseRequests = new ArraySet<Integer>(2);
+
+    /**
+     * Lock for the {@link #mPauseRequests} {@link ArraySet}.
+     */
+    private Object mPauseRequestsLock = new Object();
+
+    /**
+     * Tracks a request to pause the video for a source (see {@link #SOURCE_DATA_ENABLED},
+     * {@link #SOURCE_INCALL}) and determines whether a pause request should be issued to the
+     * video provider.
+     *
+     * We want to issue a pause request to the provider when we receive the first request
+     * to pause via any source and we're not already paused.
+     *
+     * @param source The source of the pause request.
+     * @return {@code true} if a pause should be issued to the
+     *      {@link com.android.ims.internal.ImsVideoCallProvider}, {@code false} otherwise.
+     */
+    public boolean shouldPauseVideoFor(int source) {
+        synchronized (mPauseRequestsLock) {
+            boolean wasPaused = isPaused();
+            mPauseRequests.add(source);
+
+            if (!wasPaused) {
+                Log.i(this, "shouldPauseVideoFor: source=%s, pendingRequests=%s - should pause",
+                        sourceToString(source), sourcesToString(mPauseRequests));
+                // There were previously no pause requests, but there is one now, so pause.
+                return true;
+            } else {
+                Log.i(this, "shouldPauseVideoFor: source=%s, pendingRequests=%s - already paused",
+                        sourceToString(source), sourcesToString(mPauseRequests));
+                // There were already pause requests, so no need to re-pause.
+                return false;
+            }
+        }
+    }
+
+    /**
+     * Tracks a request to resume the video for a source (see {@link #SOURCE_DATA_ENABLED},
+     * {@link #SOURCE_INCALL}) and determines whether a resume request should be issued to the
+     * video provider.
+     *
+     * We want to issue a resume request to the provider when we have issued a corresponding
+     * resume for each previously issued pause.
+     *
+     * @param source The source of the resume request.
+     * @return {@code true} if a resume should be issued to the
+     *      {@link com.android.ims.internal.ImsVideoCallProvider}, {@code false} otherwise.
+     */
+    public boolean shouldResumeVideoFor(int source) {
+        synchronized (mPauseRequestsLock) {
+            boolean wasPaused = isPaused();
+            mPauseRequests.remove(source);
+            boolean isPaused = isPaused();
+
+            if (wasPaused && !isPaused) {
+                Log.i(this, "shouldResumeVideoFor: source=%s, pendingRequests=%s - should resume",
+                        sourceToString(source), sourcesToString(mPauseRequests));
+                // This was the last pause request, so resume video.
+                return true;
+            } else if (wasPaused && isPaused) {
+                Log.i(this, "shouldResumeVideoFor: source=%s, pendingRequests=%s - stay paused",
+                        sourceToString(source), sourcesToString(mPauseRequests));
+                // There are still pending pause requests, so don't resume.
+                return false;
+            } else {
+                Log.i(this, "shouldResumeVideoFor: source=%s, pendingRequests=%s - not paused",
+                        sourceToString(source), sourcesToString(mPauseRequests));
+                // Video wasn't paused, so don't resume.
+                return false;
+            }
+        }
+    }
+
+    /**
+     * @return {@code true} if the video should be paused, {@code false} otherwise.
+     */
+    public boolean isPaused() {
+        synchronized (mPauseRequestsLock) {
+            return !mPauseRequests.isEmpty();
+        }
+    }
+
+    /**
+     * @param source the source of the pause.
+     * @return {@code true} if the specified source initiated a pause request and the video is
+     *      currently paused, {@code false} otherwise.
+     */
+    public boolean wasVideoPausedFromSource(int source) {
+        synchronized (mPauseRequestsLock) {
+            return mPauseRequests.contains(source);
+        }
+    }
+
+    /**
+     * Returns a string equivalent of a {@code SOURCE_*} constant.
+     *
+     * @param source A {@code SOURCE_*} constant.
+     * @return String equivalent of the source.
+     */
+    private String sourceToString(int source) {
+        switch (source) {
+            case SOURCE_DATA_ENABLED:
+                return SOURCE_DATA_ENABLED_STR;
+            case SOURCE_INCALL:
+                return SOURCE_INCALL_STR;
+        }
+        return "unknown";
+    }
+
+    /**
+     * Returns a comma separated list of sources.
+     *
+     * @param sources The sources.
+     * @return Comma separated list of sources.
+     */
+    private String sourcesToString(Collection<Integer> sources) {
+        synchronized (mPauseRequestsLock) {
+            return sources.stream()
+                    .map(source -> sourceToString(source))
+                    .collect(Collectors.joining(", "));
+        }
+    }
+}