Support initial attach for HO failures due to network issues

- This feature is only used when handover request fails due to network error, i.e. IKE_PROTOCOL_ERROR_TYPE.

- In idle state, or not on voice / video call, Iwlan set handover failure mode as NO_FALLBACK_RETRY_SETUP_NORMAL if attempt count reach the configured threshold. Otherwise, to keep call continuity, Iwlan will always set handover failure mode as NO_FALLBACK_RETRY_HANDOVER.

Bug: 226938560
Test: atest IwlanTests
Change-Id: I1099e708af74aaea13185a03bdb53757082d304c
diff --git a/src/com/google/android/iwlan/ErrorPolicyManager.java b/src/com/google/android/iwlan/ErrorPolicyManager.java
index cdf0c6e..6d64814 100644
--- a/src/com/google/android/iwlan/ErrorPolicyManager.java
+++ b/src/com/google/android/iwlan/ErrorPolicyManager.java
@@ -447,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()) {
@@ -1124,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 1bdebe5..b9dd851 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);
         }
 
@@ -1043,6 +1056,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.
@@ -1156,20 +1188,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()
@@ -1319,6 +1352,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;
@@ -1435,10 +1475,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);
 
@@ -1448,7 +1486,8 @@
                             IwlanDataServiceProvider.TunnelState.TUNNEL_IN_BRINGUP,
                             null,
                             (reason == DataService.REQUEST_REASON_HANDOVER),
-                            pduSessionId);
+                            pduSessionId,
+                            isIMS || isEmergency);
 
                     boolean result =
                             iwlanDataServiceProvider
@@ -1978,6 +2017,10 @@
         mNetworkMonitorCallback = null;
     }
 
+    boolean hasApnTypes(int apnTypeBitmask, int expectedApn) {
+        return (apnTypeBitmask & expectedApn) != 0;
+    }
+
     @VisibleForTesting
     void setAppContext(Context appContext) {
         mContext = appContext;
@@ -2032,6 +2075,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 6116475..0895f09 100644
--- a/test/com/google/android/iwlan/ErrorPolicyManagerTest.java
+++ b/test/com/google/android/iwlan/ErrorPolicyManagerTest.java
@@ -883,6 +883,98 @@
     }
 
     @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 =
diff --git a/test/com/google/android/iwlan/IwlanDataServiceTest.java b/test/com/google/android/iwlan/IwlanDataServiceTest.java
index bd74d80..de68306 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();
@@ -783,7 +1032,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)
@@ -797,7 +1050,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)
@@ -820,7 +1073,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);
 
@@ -851,7 +1104,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);
@@ -884,7 +1137,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);
@@ -929,7 +1182,7 @@
 
     @Test
     public void testIwlanTunnelStatsFailureCounts() {
-        DataProfile dp = buildDataProfile();
+        DataProfile dp = buildImsDataProfile();
 
         mIwlanDataService.setNetworkConnected(true, mMockNetwork, IwlanDataService.Transport.WIFI);
         doReturn(mMockEpdgTunnelManager).when(mSpyIwlanDataServiceProvider).getTunnelManager();
@@ -950,7 +1203,7 @@
 
     @Test
     public void testIwlanTunnelStatsUnsolDownCounts() {
-        DataProfile dp = buildDataProfile();
+        DataProfile dp = buildImsDataProfile();
 
         when(ErrorPolicyManager.getInstance(eq(mMockContext), eq(DEFAULT_SLOT_INDEX)))
                 .thenReturn(mMockErrorPolicyManager);
@@ -974,7 +1227,7 @@
 
     @Test
     public void testIwlanTunnelStats() {
-        DataProfile dp = buildDataProfile();
+        DataProfile dp = buildImsDataProfile();
 
         mIwlanDataService.setNetworkConnected(true, mMockNetwork, IwlanDataService.Transport.WIFI);
         doReturn(mMockEpdgTunnelManager).when(mSpyIwlanDataServiceProvider).getTunnelManager();
@@ -1022,10 +1275,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();
+    }
 }