Merge changes from topic "error_policy"

* changes:
  Support initial attach for HO failures due to network issues
  Update parser for error policy config
diff --git a/assets/defaultiwlanerrorconfig.json b/assets/defaultiwlanerrorconfig.json
index 7dd5818..212f611 100644
--- a/assets/defaultiwlanerrorconfig.json
+++ b/assets/defaultiwlanerrorconfig.json
@@ -50,6 +50,10 @@
 #         3.4.3. APM_ENABLE_EVENT: APM off to on toggle.
 #         3.4.4. WIFI_AP_CHANGED_EVENT: Wifi is connected to a AP with different SSID.
 #         3.4.5. WIFI_CALLING_DISABLE_EVENT: Wifi calling button on to off toggle.
+#    3.5. "HandoverAttemptCount": Integer to specify the number of handover request attempts before
+#         using initial attach instead. It is an optional field.
+#         Note: This should only be defined in the config when handover attempt count is enabled and
+#         "ErrorType" is explicitly defined as "IKE_PROTOCOL_ERROR_TYPE".
 #
 # Note: When the value is "*" for any of "ApnName" or "ErrorType" or "ErrorDetails",
 #       it means that the config definition applies to rest of the errors for which
diff --git a/src/com/google/android/iwlan/ErrorPolicyManager.java b/src/com/google/android/iwlan/ErrorPolicyManager.java
index b2eedbd..6d64814 100644
--- a/src/com/google/android/iwlan/ErrorPolicyManager.java
+++ b/src/com/google/android/iwlan/ErrorPolicyManager.java
@@ -32,6 +32,8 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 
+import com.google.auto.value.AutoValue;
+
 import org.json.JSONArray;
 import org.json.JSONException;
 import org.json.JSONObject;
@@ -50,6 +52,7 @@
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.Optional;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.TimeUnit;
@@ -444,6 +447,19 @@
         return new IwlanError(IwlanError.NO_ERROR);
     }
 
+    /**
+     * Returns whether framework should retry tunnel setup with initial PDN bringup request when
+     * handover request fails.
+     *
+     * @param apn apn name
+     * @return boolean result of whether framework should retry tunnel setup with initial PDN
+     *     bringup request when handover request fails
+     */
+    public synchronized boolean shouldRetryWithInitialAttach(String apn) {
+        ErrorInfo errorInfo = mLastErrorForApn.get(apn);
+        return errorInfo != null && errorInfo.shouldRetryWithInitialAttach();
+    }
+
     public void logErrorPolicies() {
         Log.d(LOG_TAG, "mCarrierConfigPolicies:");
         for (Map.Entry<String, List<ErrorPolicy>> entry : mCarrierConfigPolicies.entrySet()) {
@@ -560,7 +576,8 @@
         return stringBuilder.toString();
     }
 
-    private Map<String, List<ErrorPolicy>> readErrorPolicies(JSONArray apnArray)
+    @VisibleForTesting
+    Map<String, List<ErrorPolicy>> readErrorPolicies(JSONArray apnArray)
             throws JSONException, IllegalArgumentException {
         Map<String, List<ErrorPolicy>> errorPolicies = new HashMap<>();
         for (int i = 0; i < apnArray.length(); i++) {
@@ -580,14 +597,39 @@
                     throw new IllegalArgumentException("Unknown error type in the parsing");
                 }
 
-                ErrorPolicy errorPolicy =
-                        new ErrorPolicy(
-                                errorType,
-                                parseErrorDetails(errorType, errorDetailArray),
-                                parseRetryArray((JSONArray) errorTypeObject.get("RetryArray")),
-                                errorTypeObject.optInt("NumAttemptsPerFqdn", -1),
-                                parseUnthrottlingEvents(
-                                        (JSONArray) errorTypeObject.get("UnthrottlingEvents")));
+                List<Integer> retryArray =
+                        parseRetryArray((JSONArray) errorTypeObject.get("RetryArray"));
+
+                ErrorPolicy.Builder errorPolicyBuilder =
+                        ErrorPolicy.builder()
+                                .setErrorType(errorType)
+                                .setErrorDetails(parseErrorDetails(errorType, errorDetailArray))
+                                .setRetryArray(retryArray)
+                                .setUnthrottlingEvents(
+                                        parseUnthrottlingEvents(
+                                                (JSONArray)
+                                                        errorTypeObject.get("UnthrottlingEvents")));
+
+                if (!retryArray.isEmpty() && retryArray.get(retryArray.size() - 1) == -1L) {
+                    errorPolicyBuilder.setInfiniteRetriesWithLastRetryTime(true);
+                }
+
+                if (errorTypeObject.has("NumAttemptsPerFqdn")) {
+                    errorPolicyBuilder.setNumAttemptsPerFqdn(
+                            errorTypeObject.getInt("NumAttemptsPerFqdn"));
+                }
+
+                if (errorTypeObject.has("HandoverAttemptCount")) {
+                    if (errorType != IKE_PROTOCOL_ERROR_TYPE) {
+                        throw new IllegalArgumentException(
+                                "Handover attempt count should not be applied when errorType is not"
+                                        + " explicitly defined as IKE_PROTOCOL_ERROR_TYPE");
+                    }
+                    errorPolicyBuilder.setHandoverAttemptCount(
+                            errorTypeObject.getInt("HandoverAttemptCount"));
+                }
+
+                ErrorPolicy errorPolicy = errorPolicyBuilder.build();
 
                 errorPolicies.putIfAbsent(apnName, new ArrayList<ErrorPolicy>());
                 errorPolicies.get(apnName).add(errorPolicy);
@@ -727,13 +769,13 @@
         for (Map.Entry<String, List<ErrorPolicy>> entry : mCarrierConfigPolicies.entrySet()) {
             List<ErrorPolicy> errorPolicies = entry.getValue();
             for (ErrorPolicy errorPolicy : errorPolicies) {
-                events.addAll(errorPolicy.mUnthrottlingEvents);
+                events.addAll(errorPolicy.unthrottlingEvents());
             }
         }
         for (Map.Entry<String, List<ErrorPolicy>> entry : mDefaultPolicies.entrySet()) {
             List<ErrorPolicy> errorPolicies = entry.getValue();
             for (ErrorPolicy errorPolicy : errorPolicies) {
-                events.addAll(errorPolicy.mUnthrottlingEvents);
+                events.addAll(errorPolicy.unthrottlingEvents());
             }
         }
         events.add(IwlanEventListener.CARRIER_CONFIG_CHANGED_EVENT);
@@ -828,38 +870,60 @@
         return mErrorStats;
     }
 
-    class ErrorPolicy {
-        @ErrorPolicyErrorType int mErrorType;
-        List<String> mErrorDetails;
-        List<Integer> mRetryArray;
-        int mNumAttemptsPerFqdn;
-        List<Integer> mUnthrottlingEvents;
+    @AutoValue
+    abstract static class ErrorPolicy {
+        private static final String LOG_TAG = ErrorPolicyManager.class.getSimpleName();
 
-        ErrorPolicy(
-                @ErrorPolicyErrorType int errorType,
-                List<String> errorDetails,
-                List<Integer> retryArray,
-                int numAttemptsPerFqdn,
-                List<Integer> unthrottlingEvents) {
-            mErrorType = errorType;
-            mErrorDetails = errorDetails;
-            mRetryArray = retryArray;
-            mNumAttemptsPerFqdn = numAttemptsPerFqdn;
-            mUnthrottlingEvents = unthrottlingEvents;
+        abstract @ErrorPolicyErrorType int errorType();
+
+        abstract List<String> errorDetails();
+
+        abstract List<Integer> retryArray();
+
+        abstract Boolean infiniteRetriesWithLastRetryTime();
+
+        abstract List<Integer> unthrottlingEvents();
+
+        abstract Optional<Integer> numAttemptsPerFqdn();
+
+        abstract Optional<Integer> handoverAttemptCount();
+
+        static Builder builder() {
+            return new AutoValue_ErrorPolicyManager_ErrorPolicy.Builder()
+                    .setInfiniteRetriesWithLastRetryTime(false);
+        }
+
+        @AutoValue.Builder
+        abstract static class Builder {
+            abstract Builder setErrorType(int errorType);
+
+            abstract Builder setErrorDetails(List<String> errorDetails);
+
+            abstract Builder setRetryArray(List<Integer> retryArray);
+
+            abstract Builder setInfiniteRetriesWithLastRetryTime(
+                    Boolean infiniteRetriesWithLastRetryTime);
+
+            abstract Builder setUnthrottlingEvents(List<Integer> unthrottlingEvents);
+
+            abstract Builder setNumAttemptsPerFqdn(Integer numAttemptsPerFqdn);
+
+            abstract Builder setHandoverAttemptCount(Integer handoverAttemptCount);
+
+            abstract ErrorPolicy build();
         }
 
         long getRetryTime(int index) {
             long retryTime = -1;
-            if (mRetryArray.size() > 0) {
+            if (retryArray().size() > 0) {
                 // If the index is greater than or equal to the last element's index
                 // and if the last item in the retryArray is "-1" use the retryTime
                 // of the element before the last element to repeat the element.
-                if (index >= mRetryArray.size() - 1
-                        && mRetryArray.get(mRetryArray.size() - 1) == -1L) {
-                    index = mRetryArray.size() - 2;
+                if (infiniteRetriesWithLastRetryTime()) {
+                    index = Math.min(index, retryArray().size() - 2);
                 }
-                if (index >= 0 && index < mRetryArray.size()) {
-                    retryTime = mRetryArray.get(index);
+                if (index >= 0 && index < retryArray().size()) {
+                    retryTime = retryArray().get(index);
                 }
             }
 
@@ -873,33 +937,37 @@
 
         int getCurrentFqdnIndex(int retryIndex, int numFqdns) {
             int result = -1;
-            if (mNumAttemptsPerFqdn == -1 || mRetryArray.size() <= 0) {
+            if (!numAttemptsPerFqdn().isPresent() || retryArray().size() <= 0) {
                 return result;
             }
             // Cycles between 0 and (numFqdns - 1), based on the current attempt count and size of
             // mRetryArray.
-            return (retryIndex + 1) / mNumAttemptsPerFqdn % numFqdns;
+            return (retryIndex + 1) / numAttemptsPerFqdn().get() % numFqdns;
         }
 
         @ErrorPolicyErrorType
         int getErrorType() {
-            return mErrorType;
+            return errorType();
+        }
+
+        int getHandoverAttemptCount() {
+            return handoverAttemptCount().orElse(Integer.MAX_VALUE);
         }
 
         synchronized boolean canUnthrottle(int event) {
-            return mUnthrottlingEvents.contains(event);
+            return unthrottlingEvents().contains(event);
         }
 
         boolean match(IwlanError iwlanError) {
             // Generic by default to match to generic policy.
             String iwlanErrorDetail = "*";
-            if (mErrorType == FALLBACK_ERROR_TYPE) {
+            if (errorType() == FALLBACK_ERROR_TYPE) {
                 return true;
-            } else if (mErrorType == IKE_PROTOCOL_ERROR_TYPE
+            } else if (errorType() == IKE_PROTOCOL_ERROR_TYPE
                     && iwlanError.getErrorType() == IwlanError.IKE_PROTOCOL_EXCEPTION) {
                 IkeProtocolException exception = (IkeProtocolException) iwlanError.getException();
                 iwlanErrorDetail = String.valueOf(exception.getErrorType());
-            } else if (mErrorType == GENERIC_ERROR_TYPE) {
+            } else if (errorType() == GENERIC_ERROR_TYPE) {
                 iwlanErrorDetail = getGenericErrorDetailString(iwlanError);
                 if (iwlanErrorDetail.equals("UNKNOWN")) {
                     return false;
@@ -909,8 +977,8 @@
             }
 
             boolean ret = false;
-            for (String errorDetail : mErrorDetails) {
-                if (mErrorType == IKE_PROTOCOL_ERROR_TYPE
+            for (String errorDetail : errorDetails()) {
+                if (errorType() == IKE_PROTOCOL_ERROR_TYPE
                         && iwlanError.getErrorType() == IwlanError.IKE_PROTOCOL_EXCEPTION
                         && errorDetail.contains("-")) {
                     // error detail is stored in range format.
@@ -933,15 +1001,22 @@
         }
 
         void log() {
-            Log.d(LOG_TAG, "ErrorType: " + mErrorType);
-            Log.d(LOG_TAG, "ErrorDetail: " + Arrays.toString(mErrorDetails.toArray()));
-            Log.d(LOG_TAG, "RetryArray: " + Arrays.toString(mRetryArray.toArray()));
-            Log.d(LOG_TAG, "unthrottlingEvents: " + Arrays.toString(mUnthrottlingEvents.toArray()));
+            Log.d(LOG_TAG, "ErrorType: " + errorType());
+            Log.d(LOG_TAG, "ErrorDetail: " + Arrays.toString(errorDetails().toArray()));
+            Log.d(LOG_TAG, "RetryArray: " + Arrays.toString(retryArray().toArray()));
+            Log.d(
+                    LOG_TAG,
+                    "InfiniteRetriesWithLastRetryTime: " + infiniteRetriesWithLastRetryTime());
+            Log.d(
+                    LOG_TAG,
+                    "UnthrottlingEvents: " + Arrays.toString(unthrottlingEvents().toArray()));
+            Log.d(LOG_TAG, "NumAttemptsPerFqdn: " + numAttemptsPerFqdn());
+            Log.d(LOG_TAG, "handoverAttemptCount: " + handoverAttemptCount());
         }
 
         boolean isFallback() {
-            if ((mErrorType == FALLBACK_ERROR_TYPE)
-                    || (mErrorDetails.size() == 1 && mErrorDetails.get(0).equals("*"))) {
+            if ((errorType() == FALLBACK_ERROR_TYPE)
+                    || (errorDetails().size() == 1 && errorDetails().get(0).equals("*"))) {
                 return true;
             }
             return false;
@@ -1062,6 +1137,15 @@
             return ret;
         }
 
+        boolean shouldRetryWithInitialAttach() {
+            // UE should only uses initial attach to reset network failure, not for UE internal or
+            // DNS errors. When the number of handover failures due to network issues exceeds the
+            // configured threshold, UE should request network with initial attach instead of
+            // handover request.
+            return mErrorPolicy.getErrorType() == IKE_PROTOCOL_ERROR_TYPE
+                    && mCurrentRetryIndex + 1 >= mErrorPolicy.getHandoverAttemptCount();
+        }
+
         ErrorPolicy getErrorPolicy() {
             return mErrorPolicy;
         }
diff --git a/src/com/google/android/iwlan/IwlanDataService.java b/src/com/google/android/iwlan/IwlanDataService.java
index 8987f7d..d30d99c 100644
--- a/src/com/google/android/iwlan/IwlanDataService.java
+++ b/src/com/google/android/iwlan/IwlanDataService.java
@@ -217,6 +217,7 @@
         private EpdgSelector mEpdgSelector;
         private IwlanDataTunnelStats mTunnelStats;
         private CellInfo mCellInfo = null;
+        private int mCallState = TelephonyManager.CALL_STATE_IDLE;
         private long mProcessingStartTime = 0;
 
         // apn to TunnelState
@@ -246,6 +247,7 @@
             private boolean mIsHandover;
             private Date mBringUpStateTime = null;
             private Date mUpStateTime = null;
+            private boolean mIsImsOrEmergency;
 
             public int getPduSessionId() {
                 return mPduSessionId;
@@ -327,6 +329,14 @@
                 return mUpStateTime;
             }
 
+            public boolean getIsImsOrEmergency() {
+                return mIsImsOrEmergency;
+            }
+
+            public void setIsImsOrEmergency(boolean isImsOrEmergency) {
+                mIsImsOrEmergency = isImsOrEmergency;
+            }
+
             @Override
             public String toString() {
                 StringBuilder sb = new StringBuilder();
@@ -612,6 +622,7 @@
             events.add(IwlanEventListener.WIFI_CALLING_ENABLE_EVENT);
             events.add(IwlanEventListener.WIFI_CALLING_DISABLE_EVENT);
             events.add(IwlanEventListener.CELLINFO_CHANGED_EVENT);
+            events.add(IwlanEventListener.CALL_STATE_CHANGED_EVENT);
             IwlanEventListener.getInstance(mContext, slotIndex)
                     .addEventListener(events, mIwlanDataServiceHandler);
         }
@@ -903,13 +914,15 @@
                 int tunnelStatus,
                 TunnelLinkProperties linkProperties,
                 boolean isHandover,
-                int pduSessionId) {
+                int pduSessionId,
+                boolean isImsOrEmergency) {
             TunnelState tunnelState = new TunnelState(callback);
             tunnelState.setState(tunnelStatus);
             tunnelState.setProtocolType(dataProfile.getProtocolType());
             tunnelState.setTunnelLinkProperties(linkProperties);
             tunnelState.setIsHandover(isHandover);
             tunnelState.setPduSessionId(pduSessionId);
+            tunnelState.setIsImsOrEmergency(isImsOrEmergency);
             mTunnelStateForApn.put(dataProfile.getApn(), tunnelState);
         }
 
@@ -1055,6 +1068,25 @@
             return TelephonyManager.NETWORK_TYPE_UNKNOWN;
         }
 
+        /* Determines if this subscription is in an active call */
+        private boolean isOnCall() {
+            return mCallState != TelephonyManager.CALL_STATE_IDLE;
+        }
+
+        /**
+         * IMS and Emergency are not allowed to retry with initial attach during call to keep call
+         * continuity. Other APNs like XCAP and MMS are allowed to retry with initial attach
+         * regardless of the call state.
+         */
+        private boolean shouldRetryWithInitialAttachForHandoverRequest(
+                String apn, TunnelState tunnelState) {
+            boolean isOnImsOrEmergencyCall = tunnelState.getIsImsOrEmergency() && isOnCall();
+            return tunnelState.getIsHandover()
+                    && !isOnImsOrEmergencyCall
+                    && ErrorPolicyManager.getInstance(mContext, getSlotIndex())
+                            .shouldRetryWithInitialAttach(apn);
+        }
+
         /**
          * Called when the instance of data service is destroyed (e.g. got unbind or binder died) or
          * when the data service provider is removed.
@@ -1168,20 +1200,21 @@
                                 .setId(apnName.hashCode())
                                 .setProtocolType(tunnelState.getProtocolType());
 
-                        if (tunnelState.getIsHandover()) {
-                            respBuilder.setHandoverFailureMode(
-                                    DataCallResponse
-                                            .HANDOVER_FAILURE_MODE_NO_FALLBACK_RETRY_HANDOVER);
-                            metricsAtom.setHandoverFailureMode(
-                                    DataCallResponse
-                                            .HANDOVER_FAILURE_MODE_NO_FALLBACK_RETRY_HANDOVER);
-                        } else {
+                        if (iwlanDataServiceProvider.shouldRetryWithInitialAttachForHandoverRequest(
+                                apnName, tunnelState)) {
                             respBuilder.setHandoverFailureMode(
                                     DataCallResponse
                                             .HANDOVER_FAILURE_MODE_NO_FALLBACK_RETRY_SETUP_NORMAL);
                             metricsAtom.setHandoverFailureMode(
                                     DataCallResponse
                                             .HANDOVER_FAILURE_MODE_NO_FALLBACK_RETRY_SETUP_NORMAL);
+                        } else if (tunnelState.getIsHandover()) {
+                            respBuilder.setHandoverFailureMode(
+                                    DataCallResponse
+                                            .HANDOVER_FAILURE_MODE_NO_FALLBACK_RETRY_HANDOVER);
+                            metricsAtom.setHandoverFailureMode(
+                                    DataCallResponse
+                                            .HANDOVER_FAILURE_MODE_NO_FALLBACK_RETRY_HANDOVER);
                         }
 
                         if (tunnelState.getState()
@@ -1331,6 +1364,13 @@
                     }
                     break;
 
+                case IwlanEventListener.CALL_STATE_CHANGED_EVENT:
+                    iwlanDataServiceProvider =
+                            (IwlanDataServiceProvider) getDataServiceProvider(msg.arg1);
+
+                    iwlanDataServiceProvider.mCallState = msg.arg2;
+                    break;
+
                 case EVENT_SETUP_DATA_CALL:
                     SetupDataCallData setupDataCallData = (SetupDataCallData) msg.obj;
                     int accessNetworkType = setupDataCallData.mAccessNetworkType;
@@ -1447,10 +1487,8 @@
                     }
 
                     int apnTypeBitmask = dataProfile.getSupportedApnTypesBitmask();
-                    boolean isIMS = (apnTypeBitmask & ApnSetting.TYPE_IMS) == ApnSetting.TYPE_IMS;
-                    boolean isEmergency =
-                            (apnTypeBitmask & ApnSetting.TYPE_EMERGENCY)
-                                    == ApnSetting.TYPE_EMERGENCY;
+                    boolean isIMS = hasApnTypes(apnTypeBitmask, ApnSetting.TYPE_IMS);
+                    boolean isEmergency = hasApnTypes(apnTypeBitmask, ApnSetting.TYPE_EMERGENCY);
                     tunnelReqBuilder.setRequestPcscf(isIMS || isEmergency);
                     tunnelReqBuilder.setIsEmergency(isEmergency);
 
@@ -1460,7 +1498,8 @@
                             IwlanDataServiceProvider.TunnelState.TUNNEL_IN_BRINGUP,
                             null,
                             (reason == DataService.REQUEST_REASON_HANDOVER),
-                            pduSessionId);
+                            pduSessionId,
+                            isIMS || isEmergency);
 
                     boolean result =
                             iwlanDataServiceProvider
@@ -1990,6 +2029,10 @@
         mNetworkMonitorCallback = null;
     }
 
+    boolean hasApnTypes(int apnTypeBitmask, int expectedApn) {
+        return (apnTypeBitmask & expectedApn) != 0;
+    }
+
     @VisibleForTesting
     void setAppContext(Context appContext) {
         mContext = appContext;
@@ -2044,6 +2087,8 @@
                 return "EVENT_TUNNEL_OPENED_METRICS";
             case EVENT_TUNNEL_CLOSED_METRICS:
                 return "EVENT_TUNNEL_CLOSED_METRICS";
+            case IwlanEventListener.CALL_STATE_CHANGED_EVENT:
+                return "CALL_STATE_CHANGED_EVENT";
             default:
                 return "Unknown(" + event + ")";
         }
diff --git a/src/com/google/android/iwlan/IwlanEventListener.java b/src/com/google/android/iwlan/IwlanEventListener.java
index 31f0262..2dd0af3 100644
--- a/src/com/google/android/iwlan/IwlanEventListener.java
+++ b/src/com/google/android/iwlan/IwlanEventListener.java
@@ -85,6 +85,9 @@
     /** On Cellinfo changed */
     public static final int CELLINFO_CHANGED_EVENT = 11;
 
+    /** On Call state changed */
+    public static final int CALL_STATE_CHANGED_EVENT = 12;
+
     /* Events used and handled by IwlanDataService internally */
     public static final int DATA_SERVICE_INTERNAL_EVENT_BASE = 100;
 
@@ -106,7 +109,8 @@
         CROSS_SIM_CALLING_ENABLE_EVENT,
         CROSS_SIM_CALLING_DISABLE_EVENT,
         CARRIER_CONFIG_UNKNOWN_CARRIER_EVENT,
-        CELLINFO_CHANGED_EVENT
+        CELLINFO_CHANGED_EVENT,
+        CALL_STATE_CHANGED_EVENT
     })
     @interface IwlanEventType {};
 
@@ -147,7 +151,7 @@
     }
 
     private class RadioInfoTelephonyCallback extends TelephonyCallback
-            implements TelephonyCallback.CellInfoListener {
+            implements TelephonyCallback.CellInfoListener, TelephonyCallback.CallStateListener {
         @Override
         public void onCellInfoChanged(List<CellInfo> arrayCi) {
             Log.d(LOG_TAG, "Cellinfo changed");
@@ -160,6 +164,20 @@
                 }
             }
         }
+
+        @Override
+        public void onCallStateChanged(int state) {
+            Log.d(
+                    LOG_TAG,
+                    "Call state changed to " + callStateToString(state) + " for slot " + mSlotId);
+
+            for (Map.Entry<Integer, IwlanEventListener> entry : mInstances.entrySet()) {
+                IwlanEventListener instance = entry.getValue();
+                if (instance != null) {
+                    instance.updateHandlers(CALL_STATE_CHANGED_EVENT, state);
+                }
+            }
+        }
     }
 
     /**
@@ -515,4 +533,26 @@
             }
         }
     }
+
+    private synchronized void updateHandlers(int event, int state) {
+        if (eventHandlers.contains(event)) {
+            Log.d(SUB_TAG, "Updating handlers for the event: " + event);
+            for (Handler handler : eventHandlers.get(event)) {
+                handler.obtainMessage(event, mSlotId, state).sendToTarget();
+            }
+        }
+    }
+
+    private String callStateToString(int state) {
+        switch (state) {
+            case TelephonyManager.CALL_STATE_IDLE:
+                return "CALL_STATE_IDLE";
+            case TelephonyManager.CALL_STATE_RINGING:
+                return "CALL_STATE_RINGING";
+            case TelephonyManager.CALL_STATE_OFFHOOK:
+                return "CALL_STATE_OFFHOOK";
+            default:
+                return "Unknown Call State (" + state + ")";
+        }
+    }
 }
diff --git a/test/com/google/android/iwlan/ErrorPolicyManagerTest.java b/test/com/google/android/iwlan/ErrorPolicyManagerTest.java
index a4539e5..0895f09 100644
--- a/test/com/google/android/iwlan/ErrorPolicyManagerTest.java
+++ b/test/com/google/android/iwlan/ErrorPolicyManagerTest.java
@@ -20,6 +20,7 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertThrows;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.Mockito.*;
 import static org.mockito.Mockito.mock;
@@ -40,6 +41,9 @@
 
 import androidx.test.InstrumentationRegistry;
 
+import com.google.auto.value.AutoValue;
+
+import org.json.JSONArray;
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
@@ -49,9 +53,80 @@
 import org.mockito.quality.Strictness;
 
 import java.io.InputStream;
+import java.util.List;
 import java.util.Map;
+import java.util.Optional;
 
 public class ErrorPolicyManagerTest {
+    @AutoValue
+    abstract static class ErrorPolicyString {
+        abstract String errorType();
+
+        abstract List<String> errorDetails();
+
+        abstract List<String> retryArray();
+
+        abstract List<String> unthrottlingEvents();
+
+        abstract Optional<String> numAttemptsPerFqdn();
+
+        abstract Optional<String> handoverAttemptCount();
+
+        static Builder builder() {
+            return new AutoValue_ErrorPolicyManagerTest_ErrorPolicyString.Builder();
+        }
+
+        @AutoValue.Builder
+        abstract static class Builder {
+            abstract Builder setErrorType(String errorType);
+
+            abstract Builder setErrorDetails(List<String> errorDetails);
+
+            abstract Builder setRetryArray(List<String> retryArray);
+
+            abstract Builder setUnthrottlingEvents(List<String> unthrottlingEvents);
+
+            abstract Builder setNumAttemptsPerFqdn(String numAttemptsPerFqdn);
+
+            abstract Builder setHandoverAttemptCount(String handoverAttemptCount);
+
+            abstract ErrorPolicyString build();
+        }
+
+        String getErrorPolicyInString() {
+            StringBuilder errorPolicy =
+                    new StringBuilder(
+                            "\"ErrorType\": \""
+                                    + errorType()
+                                    + "\","
+                                    + "\"ErrorDetails\": [\""
+                                    + String.join("\", \"", errorDetails())
+                                    + "\"],"
+                                    + "\"RetryArray\": [\""
+                                    + String.join("\", \"", retryArray())
+                                    + "\"],"
+                                    + "\"UnthrottlingEvents\": [\""
+                                    + String.join("\", \"", unthrottlingEvents())
+                                    + "\"]");
+
+            numAttemptsPerFqdn()
+                    .ifPresent(
+                            numAttemptsPerFqdn ->
+                                    errorPolicy
+                                            .append(",\"NumAttemptsPerFqdn\": \"")
+                                            .append(numAttemptsPerFqdn)
+                                            .append("\""));
+            handoverAttemptCount()
+                    .ifPresent(
+                            handoverAttemptCount ->
+                                    errorPolicy
+                                            .append(",\"HandoverAttemptCount\": \"")
+                                            .append(handoverAttemptCount)
+                                            .append("\""));
+            return errorPolicy.toString();
+        }
+    }
+
     private static final String TAG = "ErrorPolicyManagerTest";
 
     // @Rule public final MockitoRule mockito = MockitoJUnit.rule();
@@ -131,17 +206,22 @@
                         + apn
                         + "\","
                         + "\"ErrorTypes\": [{"
-                        + getErrorTypeInJSON(
-                                "IKE_PROTOCOL_ERROR_TYPE",
-                                new String[] {"24", "34", "9000-9050"},
-                                new String[] {"4", "8", "16"},
-                                new String[] {"APM_ENABLE_EVENT", "WIFI_AP_CHANGED_EVENT"})
+                        + ErrorPolicyString.builder()
+                                .setErrorType("IKE_PROTOCOL_ERROR_TYPE")
+                                .setErrorDetails(List.of("24", "34", "9000-9050"))
+                                .setRetryArray(List.of("4", "8", "16"))
+                                .setUnthrottlingEvents(
+                                        List.of("APM_ENABLE_EVENT", "WIFI_AP_CHANGED_EVENT"))
+                                .build()
+                                .getErrorPolicyInString()
                         + "}, {"
-                        + getErrorTypeInJSON(
-                                "GENERIC_ERROR_TYPE",
-                                new String[] {"SERVER_SELECTION_FAILED"},
-                                new String[] {"0"},
-                                new String[] {"APM_ENABLE_EVENT"})
+                        + ErrorPolicyString.builder()
+                                .setErrorType("GENERIC_ERROR_TYPE")
+                                .setErrorDetails(List.of("SERVER_SELECTION_FAILED"))
+                                .setRetryArray(List.of("0"))
+                                .setUnthrottlingEvents(List.of("APM_ENABLE_EVENT"))
+                                .build()
+                                .getErrorPolicyInString()
                         + "}]"
                         + "}]";
 
@@ -207,11 +287,14 @@
                         + apn
                         + "\","
                         + "\"ErrorTypes\": [{"
-                        + getErrorTypeInJSON(
-                                "IKE_PROTOCOL_ERROR_TYPE",
-                                new String[] {"WRONG_ERROR_DETAIL"},
-                                new String[] {"4", "8", "16"},
-                                new String[] {"APM_ENABLE_EVENT", "WIFI_AP_CHANGED_EVENT"})
+                        + ErrorPolicyString.builder()
+                                .setErrorType("IKE_PROTOCOL_ERROR_TYPE")
+                                .setErrorDetails(List.of("WRONG_ERROR_DETAIL"))
+                                .setRetryArray(List.of("4", "8", "16"))
+                                .setUnthrottlingEvents(
+                                        List.of("APM_ENABLE_EVENT", "WIFI_AP_CHANGED_EVENT"))
+                                .build()
+                                .getErrorPolicyInString()
                         + "}]"
                         + "}]";
 
@@ -264,17 +347,22 @@
                         + apn
                         + "\","
                         + "\"ErrorTypes\": [{"
-                        + getErrorTypeInJSON(
-                                "IKE_PROTOCOL_ERROR_TYPE",
-                                new String[] {"24", "34"},
-                                new String[] {"4", "8", "16"},
-                                new String[] {"APM_ENABLE_EVENT", "WIFI_AP_CHANGED_EVENT"})
+                        + ErrorPolicyString.builder()
+                                .setErrorType("IKE_PROTOCOL_ERROR_TYPE")
+                                .setErrorDetails(List.of("24", "34"))
+                                .setRetryArray(List.of("4", "8", "16"))
+                                .setUnthrottlingEvents(
+                                        List.of("APM_ENABLE_EVENT", "WIFI_AP_CHANGED_EVENT"))
+                                .build()
+                                .getErrorPolicyInString()
                         + "}, {"
-                        + getErrorTypeInJSON(
-                                "IKE_PROTOCOL_ERROR_TYPE",
-                                new String[] {"*"},
-                                new String[] {"0"},
-                                new String[] {"APM_ENABLE_EVENT"})
+                        + ErrorPolicyString.builder()
+                                .setErrorType("IKE_PROTOCOL_ERROR_TYPE")
+                                .setErrorDetails(List.of("*"))
+                                .setRetryArray(List.of("0"))
+                                .setUnthrottlingEvents(List.of("APM_ENABLE_EVENT"))
+                                .build()
+                                .getErrorPolicyInString()
                         + "}]"
                         + "}]";
         PersistableBundle bundle = new PersistableBundle();
@@ -317,17 +405,22 @@
                         + apn
                         + "\","
                         + "\"ErrorTypes\": [{"
-                        + getErrorTypeInJSON(
-                                "IKE_PROTOCOL_ERROR_TYPE",
-                                new String[] {"24", "34"},
-                                new String[] {"4", "8", "16"},
-                                new String[] {"APM_ENABLE_EVENT", "WIFI_AP_CHANGED_EVENT"})
+                        + ErrorPolicyString.builder()
+                                .setErrorType("IKE_PROTOCOL_ERROR_TYPE")
+                                .setErrorDetails(List.of("24", "34"))
+                                .setRetryArray(List.of("4", "8", "16"))
+                                .setUnthrottlingEvents(
+                                        List.of("APM_ENABLE_EVENT", "WIFI_AP_CHANGED_EVENT"))
+                                .build()
+                                .getErrorPolicyInString()
                         + "}, {"
-                        + getErrorTypeInJSON(
-                                "GENERIC_ERROR_TYPE",
-                                new String[] {"SERVER_SELECTION_FAILED"},
-                                new String[] {"0"},
-                                new String[] {"APM_ENABLE_EVENT"})
+                        + ErrorPolicyString.builder()
+                                .setErrorType("GENERIC_ERROR_TYPE")
+                                .setErrorDetails(List.of("SERVER_SELECTION_FAILED"))
+                                .setRetryArray(List.of("0"))
+                                .setUnthrottlingEvents(List.of("APM_ENABLE_EVENT"))
+                                .build()
+                                .getErrorPolicyInString()
                         + "}]"
                         + "}]";
         PersistableBundle bundle = new PersistableBundle();
@@ -368,17 +461,22 @@
                         + apn
                         + "\","
                         + "\"ErrorTypes\": [{"
-                        + getErrorTypeInJSON(
-                                "IKE_PROTOCOL_ERROR_TYPE",
-                                new String[] {"24", "34"},
-                                new String[] {"4", "8", "16"},
-                                new String[] {"APM_ENABLE_EVENT", "WIFI_AP_CHANGED_EVENT"})
+                        + ErrorPolicyString.builder()
+                                .setErrorType("IKE_PROTOCOL_ERROR_TYPE")
+                                .setErrorDetails(List.of("24", "34"))
+                                .setRetryArray(List.of("4", "8", "16"))
+                                .setUnthrottlingEvents(
+                                        List.of("APM_ENABLE_EVENT", "WIFI_AP_CHANGED_EVENT"))
+                                .build()
+                                .getErrorPolicyInString()
                         + "}, {"
-                        + getErrorTypeInJSON(
-                                "GENERIC_ERROR_TYPE",
-                                new String[] {"SERVER_SELECTION_FAILED"},
-                                new String[] {"0"},
-                                new String[] {"APM_ENABLE_EVENT"})
+                        + ErrorPolicyString.builder()
+                                .setErrorType("GENERIC_ERROR_TYPE")
+                                .setErrorDetails(List.of("SERVER_SELECTION_FAILED"))
+                                .setRetryArray(List.of("0"))
+                                .setUnthrottlingEvents(List.of("APM_ENABLE_EVENT"))
+                                .build()
+                                .getErrorPolicyInString()
                         + "}]"
                         + "}]";
         PersistableBundle bundle = new PersistableBundle();
@@ -414,17 +512,22 @@
                         + apn
                         + "\","
                         + "\"ErrorTypes\": [{"
-                        + getErrorTypeInJSON(
-                                "IKE_PROTOCOL_ERROR_TYPE",
-                                new String[] {"24", "34"},
-                                new String[] {"6", "12", "24"},
-                                new String[] {"APM_ENABLE_EVENT", "WIFI_DISABLE_EVENT"})
+                        + ErrorPolicyString.builder()
+                                .setErrorType("IKE_PROTOCOL_ERROR_TYPE")
+                                .setErrorDetails(List.of("24", "34"))
+                                .setRetryArray(List.of("6", "12", "24"))
+                                .setUnthrottlingEvents(
+                                        List.of("APM_ENABLE_EVENT", "WIFI_DISABLE_EVENT"))
+                                .build()
+                                .getErrorPolicyInString()
                         + "}, {"
-                        + getErrorTypeInJSON(
-                                "GENERIC_ERROR_TYPE",
-                                new String[] {"SERVER_SELECTION_FAILED"},
-                                new String[] {"0"},
-                                new String[] {"APM_DISABLE_EVENT"})
+                        + ErrorPolicyString.builder()
+                                .setErrorType("GENERIC_ERROR_TYPE")
+                                .setErrorDetails(List.of("SERVER_SELECTION_FAILED"))
+                                .setRetryArray(List.of("0"))
+                                .setUnthrottlingEvents(List.of("APM_ENABLE_EVENT"))
+                                .build()
+                                .getErrorPolicyInString()
                         + "}]"
                         + "}]";
         PersistableBundle bundle = new PersistableBundle();
@@ -465,17 +568,22 @@
                         + apn
                         + "\","
                         + "\"ErrorTypes\": [{"
-                        + getErrorTypeInJSON(
-                                "IKE_PROTOCOL_ERROR_TYPE",
-                                new String[] {"24", "34"},
-                                new String[] {"6", "12", "24"},
-                                new String[] {"WIFI_CALLING_DISABLE_EVENT", "WIFI_DISABLE_EVENT"})
+                        + ErrorPolicyString.builder()
+                                .setErrorType("IKE_PROTOCOL_ERROR_TYPE")
+                                .setErrorDetails(List.of("24", "34"))
+                                .setRetryArray(List.of("6", "12", "24"))
+                                .setUnthrottlingEvents(
+                                        List.of("WIFI_CALLING_DISABLE_EVENT", "WIFI_DISABLE_EVENT"))
+                                .build()
+                                .getErrorPolicyInString()
                         + "}, {"
-                        + getErrorTypeInJSON(
-                                "GENERIC_ERROR_TYPE",
-                                new String[] {"SERVER_SELECTION_FAILED"},
-                                new String[] {"0"},
-                                new String[] {"APM_DISABLE_EVENT"})
+                        + ErrorPolicyString.builder()
+                                .setErrorType("GENERIC_ERROR_TYPE")
+                                .setErrorDetails(List.of("SERVER_SELECTION_FAILED"))
+                                .setRetryArray(List.of("0"))
+                                .setUnthrottlingEvents(List.of("APM_ENABLE_EVENT"))
+                                .build()
+                                .getErrorPolicyInString()
                         + "}]"
                         + "}]";
         PersistableBundle bundle = new PersistableBundle();
@@ -516,17 +624,22 @@
                         + apn
                         + "\","
                         + "\"ErrorTypes\": [{"
-                        + getErrorTypeInJSON(
-                                "IKE_PROTOCOL_ERROR_TYPE",
-                                new String[] {"24", "34"},
-                                new String[] {"4", "8", "16"},
-                                new String[] {"APM_ENABLE_EVENT", "WIFI_AP_CHANGED_EVENT"})
+                        + ErrorPolicyString.builder()
+                                .setErrorType("IKE_PROTOCOL_ERROR_TYPE")
+                                .setErrorDetails(List.of("24", "34"))
+                                .setRetryArray(List.of("4", "8", "16"))
+                                .setUnthrottlingEvents(
+                                        List.of("APM_ENABLE_EVENT", "WIFI_AP_CHANGED_EVENT"))
+                                .build()
+                                .getErrorPolicyInString()
                         + "}, {"
-                        + getErrorTypeInJSON(
-                                "GENERIC_ERROR_TYPE",
-                                new String[] {"SERVER_SELECTION_FAILED"},
-                                new String[] {"0"},
-                                new String[] {"APM_DISABLE_EVENT"})
+                        + ErrorPolicyString.builder()
+                                .setErrorType("GENERIC_ERROR_TYPE")
+                                .setErrorDetails(List.of("SERVER_SELECTION_FAILED"))
+                                .setRetryArray(List.of("0"))
+                                .setUnthrottlingEvents(List.of("APM_ENABLE_EVENT"))
+                                .build()
+                                .getErrorPolicyInString()
                         + "}]"
                         + "}]";
         PersistableBundle bundle = new PersistableBundle();
@@ -568,17 +681,22 @@
                         + apn1
                         + "\","
                         + "\"ErrorTypes\": [{"
-                        + getErrorTypeInJSON(
-                                "IKE_PROTOCOL_ERROR_TYPE",
-                                new String[] {"24", "34"},
-                                new String[] {"4", "8", "16"},
-                                new String[] {"APM_ENABLE_EVENT", "WIFI_AP_CHANGED_EVENT"})
+                        + ErrorPolicyString.builder()
+                                .setErrorType("IKE_PROTOCOL_ERROR_TYPE")
+                                .setErrorDetails(List.of("24", "34"))
+                                .setRetryArray(List.of("4", "8", "16"))
+                                .setUnthrottlingEvents(
+                                        List.of("APM_ENABLE_EVENT", "WIFI_AP_CHANGED_EVENT"))
+                                .build()
+                                .getErrorPolicyInString()
                         + "}, {"
-                        + getErrorTypeInJSON(
-                                "GENERIC_ERROR_TYPE",
-                                new String[] {"SERVER_SELECTION_FAILED"},
-                                new String[] {"0"},
-                                new String[] {"APM_ENABLE_EVENT"})
+                        + ErrorPolicyString.builder()
+                                .setErrorType("GENERIC_ERROR_TYPE")
+                                .setErrorDetails(List.of("SERVER_SELECTION_FAILED"))
+                                .setRetryArray(List.of("0"))
+                                .setUnthrottlingEvents(List.of("APM_ENABLE_EVENT"))
+                                .build()
+                                .getErrorPolicyInString()
                         + "}]"
                         + "}]";
         PersistableBundle bundle = new PersistableBundle();
@@ -624,17 +742,22 @@
                         + apn
                         + "\","
                         + "\"ErrorTypes\": [{"
-                        + getErrorTypeInJSON(
-                                "IKE_PROTOCOL_ERROR_TYPE",
-                                new String[] {"24", "34"},
-                                new String[] {"10", "15", "20"},
-                                new String[] {"APM_ENABLE_EVENT", "WIFI_AP_CHANGED_EVENT"})
+                        + ErrorPolicyString.builder()
+                                .setErrorType("IKE_PROTOCOL_ERROR_TYPE")
+                                .setErrorDetails(List.of("24", "34"))
+                                .setRetryArray(List.of("10", "15", "20"))
+                                .setUnthrottlingEvents(
+                                        List.of("APM_ENABLE_EVENT", "WIFI_AP_CHANGED_EVENT"))
+                                .build()
+                                .getErrorPolicyInString()
                         + "}, {"
-                        + getErrorTypeInJSON(
-                                "GENERIC_ERROR_TYPE",
-                                new String[] {"SERVER_SELECTION_FAILED"},
-                                new String[] {"0"},
-                                new String[] {"APM_ENABLE_EVENT"})
+                        + ErrorPolicyString.builder()
+                                .setErrorType("GENERIC_ERROR_TYPE")
+                                .setErrorDetails(List.of("SERVER_SELECTION_FAILED"))
+                                .setRetryArray(List.of("0"))
+                                .setUnthrottlingEvents(List.of("APM_ENABLE_EVENT"))
+                                .build()
+                                .getErrorPolicyInString()
                         + "}]"
                         + "}]";
 
@@ -687,19 +810,21 @@
                         + apn
                         + "\","
                         + "\"ErrorTypes\": [{"
-                        + getErrorTypeInJSON(
-                                "IKE_PROTOCOL_ERROR_TYPE", /* ErrorType */
-                                new String[] {"15500"}, /* ErrorDetails ("CONGESTION") */
-                                "6", /* NumAttemptsPerFqdn */
-                                new String[] {
-                                    "0", "0", "300", "600", "1200", "0", "0", "0", "300", "600",
-                                    "1200", "-1"
-                                }, /* RetryArray */
-                                new String[] {
-                                    "APM_ENABLE_EVENT",
-                                    "WIFI_DISABLE_EVENT",
-                                    "WIFI_CALLING_DISABLE_EVENT"
-                                }) /* UnthrottlingEvents */
+                        + ErrorPolicyString.builder()
+                                .setErrorType("IKE_PROTOCOL_ERROR_TYPE")
+                                .setErrorDetails(List.of("15500")) /* CONGESTION */
+                                .setRetryArray(
+                                        List.of(
+                                                "0", "0", "300", "600", "1200", "0", "0", "0",
+                                                "300", "600", "1200", "-1"))
+                                .setUnthrottlingEvents(
+                                        List.of(
+                                                "APM_ENABLE_EVENT",
+                                                "WIFI_DISABLE_EVENT",
+                                                "WIFI_CALLING_DISABLE_EVENT"))
+                                .setNumAttemptsPerFqdn("6")
+                                .build()
+                                .getErrorPolicyInString()
                         + "}]"
                         + "}]";
 
@@ -758,6 +883,124 @@
     }
 
     @Test
+    public void testShouldRetryWithInitialAttach() throws Exception {
+        String apn = "ims";
+        String config =
+                "[{"
+                        + "\"ApnName\": \""
+                        + apn
+                        + "\","
+                        + "\"ErrorTypes\": [{"
+                        + ErrorPolicyString.builder()
+                                .setErrorType("IKE_PROTOCOL_ERROR_TYPE")
+                                .setErrorDetails(List.of("24", "34"))
+                                .setRetryArray(List.of("4", "8", "16"))
+                                .setUnthrottlingEvents(
+                                        List.of("APM_ENABLE_EVENT", "WIFI_AP_CHANGED_EVENT"))
+                                .setHandoverAttemptCount("2")
+                                .build()
+                                .getErrorPolicyInString()
+                        + "}]"
+                        + "}]";
+
+        PersistableBundle bundle = new PersistableBundle();
+        bundle.putString(ErrorPolicyManager.KEY_ERROR_POLICY_CONFIG_STRING, config);
+        setupMockForCarrierConfig(bundle);
+        mErrorPolicyManager
+                .mHandler
+                .obtainMessage(IwlanEventListener.CARRIER_CONFIG_CHANGED_EVENT)
+                .sendToTarget();
+        mTestLooper.dispatchAll();
+
+        // IKE_PROTOCOL_ERROR_TYPE(24) and retryArray = 4,8,16
+        IwlanError iwlanError = buildIwlanIkeAuthFailedError();
+        long time = mErrorPolicyManager.reportIwlanError(apn, iwlanError);
+        assertEquals(4, time);
+        assertFalse(mErrorPolicyManager.shouldRetryWithInitialAttach(apn));
+
+        time = mErrorPolicyManager.reportIwlanError(apn, iwlanError);
+        assertEquals(8, time);
+        // Reached handover attempt count and error is IKE protocol error
+        assertTrue(mErrorPolicyManager.shouldRetryWithInitialAttach(apn));
+    }
+
+    @Test
+    public void testShouldRetryWithInitialAttachForInternalError() throws Exception {
+        String apn = "ims";
+        String config =
+                "[{"
+                        + "\"ApnName\": \""
+                        + apn
+                        + "\","
+                        + "\"ErrorTypes\": [{"
+                        + ErrorPolicyString.builder()
+                                .setErrorType("IKE_PROTOCOL_ERROR_TYPE")
+                                .setErrorDetails(List.of("24", "34"))
+                                .setRetryArray(List.of("4", "8", "16"))
+                                .setUnthrottlingEvents(
+                                        List.of("APM_ENABLE_EVENT", "WIFI_AP_CHANGED_EVENT"))
+                                .setHandoverAttemptCount("2")
+                                .build()
+                                .getErrorPolicyInString()
+                        + "}, {"
+                        + ErrorPolicyString.builder()
+                                .setErrorType("GENERIC_ERROR_TYPE")
+                                .setErrorDetails(List.of("SERVER_SELECTION_FAILED"))
+                                .setRetryArray(List.of("0", "0"))
+                                .setUnthrottlingEvents(List.of("APM_ENABLE_EVENT"))
+                                .build()
+                                .getErrorPolicyInString()
+                        + "}]"
+                        + "}]";
+
+        PersistableBundle bundle = new PersistableBundle();
+        bundle.putString(ErrorPolicyManager.KEY_ERROR_POLICY_CONFIG_STRING, config);
+        setupMockForCarrierConfig(bundle);
+        mErrorPolicyManager
+                .mHandler
+                .obtainMessage(IwlanEventListener.CARRIER_CONFIG_CHANGED_EVENT)
+                .sendToTarget();
+        mTestLooper.dispatchAll();
+
+        // GENERIC_PROTOCOL_ERROR_TYPE - SERVER_SELECTION_FAILED and retryArray = 0, 0
+        IwlanError iwlanError = new IwlanError(IwlanError.EPDG_SELECTOR_SERVER_SELECTION_FAILED);
+        long time = mErrorPolicyManager.reportIwlanError(apn, iwlanError);
+        assertEquals(0, time);
+        assertFalse(mErrorPolicyManager.shouldRetryWithInitialAttach(apn));
+
+        time = mErrorPolicyManager.reportIwlanError(apn, iwlanError);
+        assertEquals(0, time);
+        // Should not retry with initial attach as the errors are not IKE_PROTOCOL_ERROR_TYPE
+        assertFalse(mErrorPolicyManager.shouldRetryWithInitialAttach(apn));
+    }
+
+    @Test
+    public void testHandoverAttemptCountInvalidErrorType() throws Exception {
+        String apn = "ims";
+        String config =
+                "[{"
+                        + "\"ApnName\": \""
+                        + apn
+                        + "\","
+                        + "\"ErrorTypes\": [{"
+                        + ErrorPolicyString.builder()
+                                .setErrorType("GENERIC_ERROR_TYPE")
+                                .setErrorDetails(List.of("*"))
+                                .setRetryArray(List.of("4", "8", "16"))
+                                .setUnthrottlingEvents(
+                                        List.of("APM_ENABLE_EVENT", "WIFI_AP_CHANGED_EVENT"))
+                                .setHandoverAttemptCount("2")
+                                .build()
+                                .getErrorPolicyInString()
+                        + "}]"
+                        + "}]";
+
+        assertThrows(
+                IllegalArgumentException.class,
+                () -> mErrorPolicyManager.readErrorPolicies(new JSONArray(config)));
+    }
+
+    @Test
     public void testErrorStats() throws Exception {
         String apn1 = "ims";
         String apn2 = "mms";
@@ -798,48 +1041,6 @@
         assertEquals(resultServerApn2, serverSelectionCountApn2);
     }
 
-    private String getErrorTypeInJSON(
-            String ErrorType,
-            String[] errorDetails,
-            String[] retryArray,
-            String[] unthrottlingEvents) {
-        return "\"ErrorType\": \""
-                + ErrorType
-                + "\","
-                + "\"ErrorDetails\": [\""
-                + String.join("\", \"", errorDetails)
-                + "\"],"
-                + "\"RetryArray\": [\""
-                + String.join("\", \"", retryArray)
-                + "\"],"
-                + "\"UnthrottlingEvents\": [\""
-                + String.join("\", \"", unthrottlingEvents)
-                + "\"]";
-    }
-
-    private String getErrorTypeInJSON(
-            String ErrorType,
-            String[] errorDetails,
-            String numAttemptsPerFqdn,
-            String[] retryArray,
-            String[] unthrottlingEvents) {
-        return "\"ErrorType\": \""
-                + ErrorType
-                + "\","
-                + "\"ErrorDetails\": [\""
-                + String.join("\", \"", errorDetails)
-                + "\"],"
-                + "\"NumAttemptsPerFqdn\": \""
-                + numAttemptsPerFqdn
-                + "\","
-                + "\"RetryArray\": [\""
-                + String.join("\", \"", retryArray)
-                + "\"],"
-                + "\"UnthrottlingEvents\": [\""
-                + String.join("\", \"", unthrottlingEvents)
-                + "\"]";
-    }
-
     private void advanceClockByTimeMs(long time) {
         mMockedClockTime += time;
         mTestLooper.dispatchAll();
diff --git a/test/com/google/android/iwlan/IwlanDataServiceTest.java b/test/com/google/android/iwlan/IwlanDataServiceTest.java
index ed93c90..2446655 100644
--- a/test/com/google/android/iwlan/IwlanDataServiceTest.java
+++ b/test/com/google/android/iwlan/IwlanDataServiceTest.java
@@ -348,7 +348,7 @@
 
     @Test
     public void testRequestDataCallListPass() throws Exception {
-        DataProfile dp = buildDataProfile();
+        DataProfile dp = buildImsDataProfile();
         List<LinkAddress> mInternalAddressList;
         List<InetAddress> mDNSAddressList;
         List<InetAddress> mGatewayAddressList;
@@ -363,8 +363,9 @@
                 new DataServiceCallback(callback),
                 TunnelState.TUNNEL_UP,
                 mLinkProperties,
-                false,
-                1);
+                false, /* isHandover */
+                1, /* pduSessionId */
+                true /* isImsOrEmergency */);
         mIwlanDataServiceProvider.requestDataCallList(new DataServiceCallback(callback));
         mTestLooper.dispatchAll();
         latch.await(1, TimeUnit.SECONDS);
@@ -441,7 +442,7 @@
 
     @Test
     public void testIwlanSetupDataCallWithIllegalState() {
-        DataProfile dp = buildDataProfile();
+        DataProfile dp = buildImsDataProfile();
 
         /* Wifi is not connected */
         mIwlanDataService.setNetworkConnected(
@@ -481,7 +482,7 @@
 
     @Test
     public void testIwlanSetupDataCallWithBringUpTunnel() {
-        DataProfile dp = buildDataProfile();
+        DataProfile dp = buildImsDataProfile();
 
         /* Wifi is connected */
         mIwlanDataService.setNetworkConnected(true, mMockNetwork, IwlanDataService.Transport.WIFI);
@@ -521,7 +522,7 @@
 
     @Test
     public void testSliceInfoInclusionInDataCallResponse() throws Exception {
-        DataProfile dp = buildDataProfile();
+        DataProfile dp = buildImsDataProfile();
 
         /* Wifi is connected */
         mIwlanDataService.setNetworkConnected(true, mMockNetwork, IwlanDataService.Transport.WIFI);
@@ -569,14 +570,20 @@
 
     @Test
     public void testIwlanDeactivateDataCallWithCloseTunnel() {
-        DataProfile dp = buildDataProfile();
+        DataProfile dp = buildImsDataProfile();
 
         doReturn(mMockEpdgTunnelManager).when(mSpyIwlanDataServiceProvider).getTunnelManager();
 
         verifyNetworkConnected(TRANSPORT_WIFI);
 
         mSpyIwlanDataServiceProvider.setTunnelState(
-                dp, mMockDataServiceCallback, TunnelState.TUNNEL_IN_BRINGUP, null, false, 1);
+                dp,
+                mMockDataServiceCallback,
+                TunnelState.TUNNEL_IN_BRINGUP,
+                null, /* linkProperties */
+                false, /* isHandover */
+                1, /* pduSessionId */
+                true /* isImsOrEmergency */);
 
         mSpyIwlanDataServiceProvider.deactivateDataCall(
                 TEST_APN_NAME.hashCode() /* cid: hashcode() of "ims" */,
@@ -597,14 +604,20 @@
 
     @Test
     public void testIwlanDeactivateDataCallAfterSuccessHandover() {
-        DataProfile dp = buildDataProfile();
+        DataProfile dp = buildImsDataProfile();
 
         doReturn(mMockEpdgTunnelManager).when(mSpyIwlanDataServiceProvider).getTunnelManager();
 
         verifyNetworkConnected(TRANSPORT_WIFI);
 
         mSpyIwlanDataServiceProvider.setTunnelState(
-                dp, mMockDataServiceCallback, TunnelState.TUNNEL_IN_BRINGUP, null, false, 1);
+                dp,
+                mMockDataServiceCallback,
+                TunnelState.TUNNEL_IN_BRINGUP,
+                null, /* linkProperties */
+                false, /* isHandover */
+                1, /* pduSessionId */
+                true /* isImsOrEmergency */);
 
         mSpyIwlanDataServiceProvider.deactivateDataCall(
                 TEST_APN_NAME.hashCode() /* cid: hashcode() of "ims" */,
@@ -624,8 +637,8 @@
     }
 
     @Test
-    public void testHandoverFailureModeNormal() {
-        DataProfile dp = buildDataProfile();
+    public void testHandoverFailureModeDefault() {
+        DataProfile dp = buildImsDataProfile();
         int setupDataReason = DataService.REQUEST_REASON_NORMAL;
 
         when(ErrorPolicyManager.getInstance(eq(mMockContext), eq(DEFAULT_SLOT_INDEX)))
@@ -638,9 +651,10 @@
                 dp,
                 mMockDataServiceCallback,
                 TunnelState.TUNNEL_IN_BRINGUP,
-                null,
+                null, /* linkProperties */
                 (setupDataReason == DataService.REQUEST_REASON_HANDOVER),
-                1);
+                1 /* pduSessionId */,
+                true /* isImsOrEmergency */);
 
         mSpyIwlanDataServiceProvider.setMetricsAtom(
                 TEST_APN_NAME,
@@ -667,14 +681,14 @@
         DataCallResponse dataCallResponse = dataCallResponseCaptor.getValue();
         assertEquals(
                 dataCallResponse.getHandoverFailureMode(),
-                DataCallResponse.HANDOVER_FAILURE_MODE_NO_FALLBACK_RETRY_SETUP_NORMAL);
+                DataCallResponse.HANDOVER_FAILURE_MODE_LEGACY);
         assertEquals(dataCallResponse.getCause(), DataFailCause.USER_AUTHENTICATION);
         assertEquals(dataCallResponse.getRetryDurationMillis(), 5L);
     }
 
     @Test
     public void testHandoverFailureModeHandover() {
-        DataProfile dp = buildDataProfile();
+        DataProfile dp = buildImsDataProfile();
         int setupDataReason = DataService.REQUEST_REASON_HANDOVER;
 
         when(ErrorPolicyManager.getInstance(eq(mMockContext), eq(DEFAULT_SLOT_INDEX)))
@@ -682,14 +696,17 @@
         when(mMockErrorPolicyManager.getCurrentRetryTimeMs(eq(TEST_APN_NAME))).thenReturn(-1L);
         when(mMockErrorPolicyManager.getDataFailCause(eq(TEST_APN_NAME)))
                 .thenReturn(DataFailCause.ERROR_UNSPECIFIED);
+        when(mMockErrorPolicyManager.shouldRetryWithInitialAttach(eq(TEST_APN_NAME)))
+                .thenReturn(false);
 
         mSpyIwlanDataServiceProvider.setTunnelState(
                 dp,
                 mMockDataServiceCallback,
                 TunnelState.TUNNEL_IN_BRINGUP,
-                null,
+                null, /* linkProperties */
                 (setupDataReason == DataService.REQUEST_REASON_HANDOVER),
-                1);
+                1 /* pduSessionId */,
+                true /* isImsOrEmergency */);
 
         mSpyIwlanDataServiceProvider.setMetricsAtom(
                 TEST_APN_NAME,
@@ -722,6 +739,238 @@
     }
 
     @Test
+    public void testSupportInitialAttachSuccessOnIms() {
+        DataProfile dp = buildImsDataProfile();
+        int setupDataReason = DataService.REQUEST_REASON_HANDOVER;
+
+        when(ErrorPolicyManager.getInstance(eq(mMockContext), eq(DEFAULT_SLOT_INDEX)))
+                .thenReturn(mMockErrorPolicyManager);
+        when(mMockErrorPolicyManager.getCurrentRetryTimeMs(eq(TEST_APN_NAME))).thenReturn(-1L);
+        when(mMockErrorPolicyManager.getDataFailCause(eq(TEST_APN_NAME)))
+                .thenReturn(DataFailCause.ERROR_UNSPECIFIED);
+        when(mMockErrorPolicyManager.shouldRetryWithInitialAttach(eq(TEST_APN_NAME)))
+                .thenReturn(true);
+
+        // APN = IMS, in idle call state
+        mIwlanDataService
+                .mIwlanDataServiceHandler
+                .obtainMessage(
+                        IwlanEventListener.CALL_STATE_CHANGED_EVENT,
+                        DEFAULT_SLOT_INDEX,
+                        TelephonyManager.CALL_STATE_IDLE)
+                .sendToTarget();
+
+        mSpyIwlanDataServiceProvider.setTunnelState(
+                dp,
+                mMockDataServiceCallback,
+                TunnelState.TUNNEL_IN_BRINGUP,
+                null, /* linkProperties */
+                (setupDataReason == DataService.REQUEST_REASON_HANDOVER),
+                1 /* pduSessionId */,
+                true /* isImsOrEmergency */);
+
+        mSpyIwlanDataServiceProvider.setMetricsAtom(
+                TEST_APN_NAME,
+                64, // type IMS
+                true,
+                13, // LTE
+                false,
+                true,
+                1 // Transport Wi-Fi
+                );
+
+        mSpyIwlanDataServiceProvider
+                .getIwlanTunnelCallback()
+                .onClosed(TEST_APN_NAME, new IwlanError(IwlanError.NO_ERROR));
+        mTestLooper.dispatchAll();
+
+        ArgumentCaptor<DataCallResponse> dataCallResponseCaptor =
+                ArgumentCaptor.forClass(DataCallResponse.class);
+        verify(mMockDataServiceCallback, times(1))
+                .onSetupDataCallComplete(
+                        eq(DataServiceCallback.RESULT_SUCCESS), dataCallResponseCaptor.capture());
+        DataCallResponse dataCallResponse = dataCallResponseCaptor.getValue();
+        // Not on video or voice call
+        assertEquals(
+                DataCallResponse.HANDOVER_FAILURE_MODE_NO_FALLBACK_RETRY_SETUP_NORMAL,
+                dataCallResponse.getHandoverFailureMode());
+    }
+
+    @Test
+    public void testSupportInitialAttachSuccessOnEmergency() {
+        DataProfile dp = buildDataProfile(ApnSetting.TYPE_EMERGENCY);
+        int setupDataReason = DataService.REQUEST_REASON_HANDOVER;
+
+        when(ErrorPolicyManager.getInstance(eq(mMockContext), eq(DEFAULT_SLOT_INDEX)))
+                .thenReturn(mMockErrorPolicyManager);
+        when(mMockErrorPolicyManager.getCurrentRetryTimeMs(eq(TEST_APN_NAME))).thenReturn(-1L);
+        when(mMockErrorPolicyManager.getDataFailCause(eq(TEST_APN_NAME)))
+                .thenReturn(DataFailCause.ERROR_UNSPECIFIED);
+        when(mMockErrorPolicyManager.shouldRetryWithInitialAttach(eq(TEST_APN_NAME)))
+                .thenReturn(true);
+
+        // APN = Emergency, in idle call state
+        mIwlanDataService
+                .mIwlanDataServiceHandler
+                .obtainMessage(
+                        IwlanEventListener.CALL_STATE_CHANGED_EVENT,
+                        DEFAULT_SLOT_INDEX,
+                        TelephonyManager.CALL_STATE_IDLE)
+                .sendToTarget();
+
+        mSpyIwlanDataServiceProvider.setTunnelState(
+                dp,
+                mMockDataServiceCallback,
+                TunnelState.TUNNEL_IN_BRINGUP,
+                null, /* linkProperties */
+                (setupDataReason == DataService.REQUEST_REASON_HANDOVER),
+                1 /* pduSessionId */,
+                true /* isImsOrEmergency */);
+
+        mSpyIwlanDataServiceProvider.setMetricsAtom(
+                TEST_APN_NAME,
+                512, // type Emergency
+                true,
+                13, // LTE
+                false,
+                true,
+                1 // Transport Wi-Fi
+                );
+
+        mSpyIwlanDataServiceProvider
+                .getIwlanTunnelCallback()
+                .onClosed(TEST_APN_NAME, new IwlanError(IwlanError.NO_ERROR));
+        mTestLooper.dispatchAll();
+
+        ArgumentCaptor<DataCallResponse> dataCallResponseCaptor =
+                ArgumentCaptor.forClass(DataCallResponse.class);
+        verify(mMockDataServiceCallback, times(1))
+                .onSetupDataCallComplete(
+                        eq(DataServiceCallback.RESULT_SUCCESS), dataCallResponseCaptor.capture());
+        DataCallResponse dataCallResponse = dataCallResponseCaptor.getValue();
+        // Not on video or voice call
+        assertEquals(
+                DataCallResponse.HANDOVER_FAILURE_MODE_NO_FALLBACK_RETRY_SETUP_NORMAL,
+                dataCallResponse.getHandoverFailureMode());
+    }
+
+    @Test
+    public void testSupportInitialAttachOnImsCall() {
+        DataProfile dp = buildImsDataProfile();
+        int setupDataReason = DataService.REQUEST_REASON_HANDOVER;
+
+        when(ErrorPolicyManager.getInstance(eq(mMockContext), eq(DEFAULT_SLOT_INDEX)))
+                .thenReturn(mMockErrorPolicyManager);
+        when(mMockErrorPolicyManager.getCurrentRetryTimeMs(eq(TEST_APN_NAME))).thenReturn(-1L);
+        when(mMockErrorPolicyManager.getDataFailCause(eq(TEST_APN_NAME)))
+                .thenReturn(DataFailCause.ERROR_UNSPECIFIED);
+        when(mMockErrorPolicyManager.shouldRetryWithInitialAttach(eq(TEST_APN_NAME)))
+                .thenReturn(true);
+
+        // APN = IMS, in call
+        mIwlanDataService
+                .mIwlanDataServiceHandler
+                .obtainMessage(
+                        IwlanEventListener.CALL_STATE_CHANGED_EVENT,
+                        DEFAULT_SLOT_INDEX,
+                        TelephonyManager.CALL_STATE_OFFHOOK)
+                .sendToTarget();
+
+        mSpyIwlanDataServiceProvider.setTunnelState(
+                dp,
+                mMockDataServiceCallback,
+                TunnelState.TUNNEL_IN_BRINGUP,
+                null /* linkProperties */,
+                (setupDataReason == DataService.REQUEST_REASON_HANDOVER),
+                1 /* pduSessionId */,
+                true /* isImsOrEmergency */);
+
+        mSpyIwlanDataServiceProvider.setMetricsAtom(
+                TEST_APN_NAME,
+                64, // type IMS
+                true,
+                13, // LTE
+                false,
+                true,
+                1 // Transport Wi-Fi
+                );
+
+        mSpyIwlanDataServiceProvider
+                .getIwlanTunnelCallback()
+                .onClosed(TEST_APN_NAME, new IwlanError(IwlanError.NO_ERROR));
+        mTestLooper.dispatchAll();
+
+        ArgumentCaptor<DataCallResponse> dataCallResponseCaptor =
+                ArgumentCaptor.forClass(DataCallResponse.class);
+        verify(mMockDataServiceCallback, times(1))
+                .onSetupDataCallComplete(
+                        eq(DataServiceCallback.RESULT_SUCCESS), dataCallResponseCaptor.capture());
+        DataCallResponse dataCallResponse = dataCallResponseCaptor.getValue();
+        // In call state
+        assertEquals(
+                DataCallResponse.HANDOVER_FAILURE_MODE_NO_FALLBACK_RETRY_HANDOVER,
+                dataCallResponse.getHandoverFailureMode());
+    }
+
+    @Test
+    public void testSupportInitialAttachOnEmergencyCall() {
+        DataProfile dp = buildDataProfile(ApnSetting.TYPE_EMERGENCY);
+        int setupDataReason = DataService.REQUEST_REASON_HANDOVER;
+
+        when(ErrorPolicyManager.getInstance(eq(mMockContext), eq(DEFAULT_SLOT_INDEX)))
+                .thenReturn(mMockErrorPolicyManager);
+        when(mMockErrorPolicyManager.getCurrentRetryTimeMs(eq(TEST_APN_NAME))).thenReturn(-1L);
+        when(mMockErrorPolicyManager.getDataFailCause(eq(TEST_APN_NAME)))
+                .thenReturn(DataFailCause.ERROR_UNSPECIFIED);
+        when(mMockErrorPolicyManager.shouldRetryWithInitialAttach(eq(TEST_APN_NAME)))
+                .thenReturn(true);
+
+        // APN = Emergency, in call
+        mIwlanDataService
+                .mIwlanDataServiceHandler
+                .obtainMessage(
+                        IwlanEventListener.CALL_STATE_CHANGED_EVENT,
+                        DEFAULT_SLOT_INDEX,
+                        TelephonyManager.CALL_STATE_OFFHOOK)
+                .sendToTarget();
+
+        mSpyIwlanDataServiceProvider.setTunnelState(
+                dp,
+                mMockDataServiceCallback,
+                TunnelState.TUNNEL_IN_BRINGUP,
+                null /* linkProperties */,
+                (setupDataReason == DataService.REQUEST_REASON_HANDOVER),
+                1 /* pduSessionId */,
+                true /* isImsOrEmergency */);
+
+        mSpyIwlanDataServiceProvider.setMetricsAtom(
+                TEST_APN_NAME,
+                512, // type Emergency
+                true,
+                13, // LTE
+                false,
+                true,
+                1 // Transport Wi-Fi
+                );
+
+        mSpyIwlanDataServiceProvider
+                .getIwlanTunnelCallback()
+                .onClosed(TEST_APN_NAME, new IwlanError(IwlanError.NO_ERROR));
+        mTestLooper.dispatchAll();
+
+        ArgumentCaptor<DataCallResponse> dataCallResponseCaptor =
+                ArgumentCaptor.forClass(DataCallResponse.class);
+        verify(mMockDataServiceCallback, times(1))
+                .onSetupDataCallComplete(
+                        eq(DataServiceCallback.RESULT_SUCCESS), dataCallResponseCaptor.capture());
+        DataCallResponse dataCallResponse = dataCallResponseCaptor.getValue();
+        // In call state
+        assertEquals(
+                DataCallResponse.HANDOVER_FAILURE_MODE_NO_FALLBACK_RETRY_HANDOVER,
+                dataCallResponse.getHandoverFailureMode());
+    }
+
+    @Test
     public void testDnsPrefetching() throws Exception {
         IwlanNetworkMonitorCallback mNetworkMonitorCallback =
                 mIwlanDataService.getNetworkMonitorCallback();
@@ -785,7 +1034,11 @@
         mTestLooper.dispatchAll();
     }
 
-    private DataProfile buildDataProfile() {
+    private DataProfile buildImsDataProfile() {
+        return buildDataProfile(ApnSetting.TYPE_IMS);
+    }
+
+    private DataProfile buildDataProfile(int supportedApnTypesBitmask) {
         DataProfile dp =
                 new DataProfile.Builder()
                         .setProfileId(1)
@@ -799,7 +1052,7 @@
                         // .setMaxConnections(3)
                         // .setWaitTime(10)
                         .enable(true)
-                        .setSupportedApnTypesBitmask(ApnSetting.TYPE_IMS)
+                        .setSupportedApnTypesBitmask(supportedApnTypesBitmask)
                         .setRoamingProtocolType(ApnSetting.PROTOCOL_IPV4V6) // IPv4v6
                         .setBearerBitmask((int) TelephonyManager.NETWORK_TYPE_BITMASK_IWLAN)
                         .setPersistent(true)
@@ -822,7 +1075,7 @@
 
     @Test
     public void testIwlanSetupDataCallFailsWithCellularAndCstDisabled() throws Exception {
-        DataProfile dp = buildDataProfile();
+        DataProfile dp = buildImsDataProfile();
         /* CST is disabled, and data is on the same sub as the data service provider */
         when(mMockImsMmTelManager.isCrossSimCallingEnabled()).thenReturn(false);
 
@@ -853,7 +1106,7 @@
 
     @Test
     public void testIwlanSetupDataCallFailsWithCellularOnSameSubAndCstEnabled() throws Exception {
-        DataProfile dp = buildDataProfile();
+        DataProfile dp = buildImsDataProfile();
 
         /* CST is enabled, but data is on the same sub as the DataServiceProvider */
         when(mMockImsMmTelManager.isCrossSimCallingEnabled()).thenReturn(true);
@@ -886,7 +1139,7 @@
     @Test
     public void testIwlanSetupDataCallSucceedsWithCellularOnDifferentSubAndCstEnabled()
             throws Exception {
-        DataProfile dp = buildDataProfile();
+        DataProfile dp = buildImsDataProfile();
 
         /* CST is enabled, but data is on the same sub as the DataServiceProvider */
         when(mMockImsMmTelManager.isCrossSimCallingEnabled()).thenReturn(true);
@@ -931,7 +1184,7 @@
 
     @Test
     public void testIwlanTunnelStatsFailureCounts() {
-        DataProfile dp = buildDataProfile();
+        DataProfile dp = buildImsDataProfile();
 
         mIwlanDataService.setNetworkConnected(true, mMockNetwork, IwlanDataService.Transport.WIFI);
         doReturn(mMockEpdgTunnelManager).when(mSpyIwlanDataServiceProvider).getTunnelManager();
@@ -952,7 +1205,7 @@
 
     @Test
     public void testIwlanTunnelStatsUnsolDownCounts() {
-        DataProfile dp = buildDataProfile();
+        DataProfile dp = buildImsDataProfile();
 
         when(ErrorPolicyManager.getInstance(eq(mMockContext), eq(DEFAULT_SLOT_INDEX)))
                 .thenReturn(mMockErrorPolicyManager);
@@ -976,7 +1229,7 @@
 
     @Test
     public void testIwlanTunnelStats() {
-        DataProfile dp = buildDataProfile();
+        DataProfile dp = buildImsDataProfile();
 
         mIwlanDataService.setNetworkConnected(true, mMockNetwork, IwlanDataService.Transport.WIFI);
         doReturn(mMockEpdgTunnelManager).when(mSpyIwlanDataServiceProvider).getTunnelManager();
@@ -1024,10 +1277,16 @@
 
     @Test
     public void testIwlanDataServiceHandlerOnUnbind() {
-        DataProfile dp = buildDataProfile();
+        DataProfile dp = buildImsDataProfile();
         doReturn(mMockEpdgTunnelManager).when(mSpyIwlanDataServiceProvider).getTunnelManager();
         mSpyIwlanDataServiceProvider.setTunnelState(
-                dp, mMockDataServiceCallback, TunnelState.TUNNEL_UP, null, false, 1);
+                dp,
+                mMockDataServiceCallback,
+                TunnelState.TUNNEL_UP,
+                null /* linkProperties */,
+                false /* isHandover */,
+                1 /* pduSessionId */,
+                true /* isImsOrEmergency */);
 
         mSpyIwlanDataServiceProvider.setMetricsAtom(
                 TEST_APN_NAME,
diff --git a/test/com/google/android/iwlan/IwlanEventListenerTest.java b/test/com/google/android/iwlan/IwlanEventListenerTest.java
index f154f20..509c0da 100644
--- a/test/com/google/android/iwlan/IwlanEventListenerTest.java
+++ b/test/com/google/android/iwlan/IwlanEventListenerTest.java
@@ -93,8 +93,7 @@
         when(mMockContext.getSystemService(eq(SubscriptionManager.class)))
                 .thenReturn(mMockSubscriptionManager);
 
-        when(mMockSubscriptionManager.getActiveSubscriptionInfoForSimSlotIndex(
-                        anyInt()))
+        when(mMockSubscriptionManager.getActiveSubscriptionInfoForSimSlotIndex(anyInt()))
                 .thenReturn(mMockSubscriptionInfo);
 
         when(mMockContext.getContentResolver()).thenReturn(mMockContentResolver);
@@ -282,4 +281,25 @@
 
         verify(mMockMessage, times(1)).sendToTarget();
     }
+
+    @Test
+    public void testCallStateChanged() throws Exception {
+        when(mMockHandler.obtainMessage(
+                        eq(IwlanEventListener.CALL_STATE_CHANGED_EVENT),
+                        eq(DEFAULT_SLOT_INDEX),
+                        eq(TelephonyManager.CALL_STATE_OFFHOOK)))
+                .thenReturn(mMockMessage);
+
+        events = new ArrayList<Integer>();
+        events.add(IwlanEventListener.CALL_STATE_CHANGED_EVENT);
+        mIwlanEventListener.addEventListener(events, mMockHandler);
+
+        mIwlanEventListener.registerTelephonyCallback();
+
+        TelephonyCallback.CallStateListener mTelephonyCallback =
+                mIwlanEventListener.getTelephonyCallback();
+        mTelephonyCallback.onCallStateChanged(TelephonyManager.CALL_STATE_OFFHOOK);
+
+        verify(mMockMessage, times(1)).sendToTarget();
+    }
 }