Merge "Refactor simState & activeSubscriptionInfo"
diff --git a/src/java/com/android/internal/telephony/PhoneConfigurationModels.java b/src/java/com/android/internal/telephony/PhoneConfigurationModels.java
index fb8cc5e..aca4955 100644
--- a/src/java/com/android/internal/telephony/PhoneConfigurationModels.java
+++ b/src/java/com/android/internal/telephony/PhoneConfigurationModels.java
@@ -40,7 +40,7 @@
         List<ModemInfo> logicalModemList = new ArrayList<>();
         logicalModemList.add(modemInfo1);
         logicalModemList.add(modemInfo2);
-        DSDS_CAPABILITY = new PhoneCapability(1, 2, 0, logicalModemList, false);
+        DSDS_CAPABILITY = new PhoneCapability(1, 2, 0, logicalModemList, true);
 
         logicalModemList = new ArrayList<>();
         logicalModemList.add(modemInfo1);
diff --git a/src/java/com/android/internal/telephony/PhoneSwitcher.java b/src/java/com/android/internal/telephony/PhoneSwitcher.java
index 2d4426e..4aff9ca 100644
--- a/src/java/com/android/internal/telephony/PhoneSwitcher.java
+++ b/src/java/com/android/internal/telephony/PhoneSwitcher.java
@@ -22,6 +22,9 @@
 import static android.telephony.SubscriptionManager.DEFAULT_SUBSCRIPTION_ID;
 import static android.telephony.SubscriptionManager.INVALID_PHONE_INDEX;
 import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+import static android.telephony.TelephonyManager.SET_OPPORTUNISTIC_SUB_INVALID_PARAMETER;
+import static android.telephony.TelephonyManager.SET_OPPORTUNISTIC_SUB_SUCCESS;
+import static android.telephony.TelephonyManager.SET_OPPORTUNISTIC_SUB_VALIDATION_FAILED;
 
 import android.content.BroadcastReceiver;
 import android.content.Context;
@@ -42,6 +45,7 @@
 import android.os.Registrant;
 import android.os.RegistrantList;
 import android.os.RemoteException;
+import android.os.ServiceManager;
 import android.telephony.PhoneCapability;
 import android.telephony.PhoneStateListener;
 import android.telephony.PreciseCallState;
@@ -113,13 +117,15 @@
 
     private int mPhoneIdInCall = SubscriptionManager.INVALID_PHONE_INDEX;
 
+    private ISetOpportunisticDataCallback mSetOpptSubCallback;
+
     private static final int EVENT_DEFAULT_SUBSCRIPTION_CHANGED   = 101;
     private static final int EVENT_SUBSCRIPTION_CHANGED           = 102;
     private static final int EVENT_REQUEST_NETWORK                = 103;
     private static final int EVENT_RELEASE_NETWORK                = 104;
     private static final int EVENT_EMERGENCY_TOGGLE               = 105;
     private static final int EVENT_RADIO_CAPABILITY_CHANGED       = 106;
-    private static final int EVENT_PREFERRED_SUBSCRIPTION_CHANGED = 107;
+    private static final int EVENT_CHANGE_PREFERRED_SUBSCRIPTION  = 107;
     private static final int EVENT_RADIO_AVAILABLE                = 108;
     private static final int EVENT_PHONE_IN_CALL_CHANGED          = 109;
     private static final int EVENT_NETWORK_VALIDATION_DONE        = 110;
@@ -349,9 +355,16 @@
                 resendRilCommands(msg);
                 break;
             }
-            case EVENT_PREFERRED_SUBSCRIPTION_CHANGED: {
-                onEvaluate(REQUESTS_UNCHANGED, "preferredDataSubscriptionIdChanged");
-                registerDefaultNetworkChangeCallback();
+            case EVENT_CHANGE_PREFERRED_SUBSCRIPTION: {
+                int subId = msg.arg1;
+                boolean needValidation = (msg.arg2 == 1);
+                ISetOpportunisticDataCallback callback =
+                        (ISetOpportunisticDataCallback) msg.obj;
+                if (SubscriptionManager.isUsableSubscriptionId(subId)) {
+                    setOpportunisticDataSubscription(subId, needValidation, callback);
+                } else {
+                    unsetOpportunisticDataSubscription(callback);
+                }
                 break;
             }
             case EVENT_RADIO_AVAILABLE: {
@@ -779,20 +792,34 @@
      * subscription. It has to be an active subscription, and PhoneSwitcher will try to validate
      * it first if needed.
      */
-    public void setOpportunisticDataSubscription(int subId) {
+    private void setOpportunisticDataSubscription(int subId, boolean needValidation,
+            ISetOpportunisticDataCallback callback) {
         if (!mSubscriptionController.isActiveSubId(subId)) {
             log("Can't switch data to inactive subId " + subId);
+            sendSetOpptCallbackHelper(callback, SET_OPPORTUNISTIC_SUB_INVALID_PARAMETER);
             return;
         }
 
-        logDataSwitchEvent(TelephonyEvent.EventState.START, DataSwitch.Reason.CBRS);
+        if (mValidator.isValidating()
+                && (!needValidation || subId != mValidator.getSubIdInValidation())) {
+            mValidator.stopValidation();
+        }
+
+        if (subId == mPreferredDataSubId) {
+            sendSetOpptCallbackHelper(callback, SET_OPPORTUNISTIC_SUB_SUCCESS);
+            return;
+        }
+
         // If validation feature is not supported, set it directly. Otherwise,
         // start validation on the subscription first.
-        if (!CellularNetworkValidator.isValidationFeatureSupported()) {
-            setPreferredDataSubscriptionId(subId);
-        } else {
+        if (CellularNetworkValidator.isValidationFeatureSupported() && needValidation) {
+            logDataSwitchEvent(TelephonyEvent.EventState.START, DataSwitch.Reason.CBRS);
+            mSetOpptSubCallback = callback;
             mValidator.validate(subId, DEFAULT_VALIDATION_EXPIRATION_TIME,
                     false, mValidationCallback);
+        } else {
+            setPreferredSubscription(subId);
+            sendSetOpptCallbackHelper(callback, SET_OPPORTUNISTIC_SUB_SUCCESS);
         }
     }
 
@@ -800,32 +827,66 @@
      * Unset opportunistic data subscription. It's an indication to switch Internet data back
      * from opportunistic subscription to primary subscription.
      */
-    public void unsetOpportunisticDataSubscription() {
-        logDataSwitchEvent(TelephonyEvent.EventState.START, DataSwitch.Reason.CBRS);
-        if (CellularNetworkValidator.isValidationFeatureSupported()
-                && mValidator.isValidating()) {
+    private void unsetOpportunisticDataSubscription(ISetOpportunisticDataCallback callback) {
+        if (mValidator.isValidating()) {
             mValidator.stopValidation();
         }
 
         // Set mPreferredDataSubId back to DEFAULT_SUBSCRIPTION_ID. This will trigger
         // data switch to mDefaultDataSubId.
-        setPreferredDataSubscriptionId(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID);
+        setPreferredSubscription(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID);
+        sendSetOpptCallbackHelper(callback, SET_OPPORTUNISTIC_SUB_SUCCESS);
+    }
+
+    private void sendSetOpptCallbackHelper(ISetOpportunisticDataCallback callback, int result) {
+        if (callback == null) return;
+        try {
+            callback.onComplete(result);
+        } catch (RemoteException exception) {
+            log("RemoteException " + exception);
+        }
+    }
+
+    /**
+     * Set opportunistic data subscription.
+     */
+    private void setPreferredSubscription(int subId) {
+        if (mPreferredDataSubId != subId) {
+            mPreferredDataSubId = subId;
+            logDataSwitchEvent(TelephonyEvent.EventState.START, DataSwitch.Reason.CBRS);
+            onEvaluate(REQUESTS_UNCHANGED, "preferredDataSubscriptionIdChanged");
+            notifyPreferredDataSubIdChanged();
+            registerDefaultNetworkChangeCallback();
+        }
     }
 
     private void onValidationDone(int subId, boolean passed) {
         log("Network validation " + (passed ? "passed" : "failed")
                 + " on subId " + subId);
-        mValidator.stopValidation();
-        if (passed) setPreferredDataSubscriptionId(subId);
+        if (passed) setPreferredSubscription(subId);
+
+        // Trigger callback if needed
+        sendSetOpptCallbackHelper(mSetOpptSubCallback, passed ? SET_OPPORTUNISTIC_SUB_SUCCESS
+                : SET_OPPORTUNISTIC_SUB_VALIDATION_FAILED);
+        mSetOpptSubCallback = null;
     }
 
     // TODO b/123598154: rename preferredDataSub to opportunisticSubId.
-    private void setPreferredDataSubscriptionId(int subId) {
-        if (mPreferredDataSubId != subId) {
-            log("setPreferredDataSubscriptionId subId changed to " + subId);
-            mPreferredDataSubId = subId;
-            Message msg = PhoneSwitcher.this.obtainMessage(EVENT_PREFERRED_SUBSCRIPTION_CHANGED);
-            msg.sendToTarget();
+    public void trySetPreferredSubscription(int subId, boolean needValidation,
+            ISetOpportunisticDataCallback callback) {
+        log("Try set preferred subscription to subId " + subId
+                + (needValidation ? " with " : " without ") + "validation");
+        PhoneSwitcher.this.obtainMessage(EVENT_CHANGE_PREFERRED_SUBSCRIPTION,
+                subId, needValidation ? 1 : 0, callback).sendToTarget();
+    }
+
+    private void notifyPreferredDataSubIdChanged() {
+        ITelephonyRegistry tr = ITelephonyRegistry.Stub.asInterface(ServiceManager.getService(
+                "telephony.registry"));
+        try {
+            tr.notifyPreferredDataSubIdChanged(mPreferredDataSubId);
+        } catch (RemoteException ex) {
+            // Should never happen because TelephonyRegistry service should always be available.
         }
     }
 
@@ -843,6 +904,10 @@
                 ? HAL_COMMAND_PREFERRED_DATA : HAL_COMMAND_ALLOW_DATA;
     }
 
+    public int getPreferredDataSubscriptionId() {
+        return mPreferredDataSubId;
+    }
+
     private void log(String l) {
         Rlog.d(LOG_TAG, l);
         mLocalLog.log(l);
diff --git a/src/java/com/android/internal/telephony/SubscriptionController.java b/src/java/com/android/internal/telephony/SubscriptionController.java
index f89ba4d..12e5845 100644
--- a/src/java/com/android/internal/telephony/SubscriptionController.java
+++ b/src/java/com/android/internal/telephony/SubscriptionController.java
@@ -2690,16 +2690,14 @@
     }
 
     @Override
-    public void setPreferredDataSubscriptionId(int subId) {
+    public void setPreferredDataSubscriptionId(int subId, boolean needValidation,
+            ISetOpportunisticDataCallback callback) {
         enforceModifyPhoneState("setPreferredDataSubscriptionId");
         final long token = Binder.clearCallingIdentity();
 
         try {
-            if (SubscriptionManager.isUsableSubscriptionId(subId)) {
-                PhoneSwitcher.getInstance().setOpportunisticDataSubscription(subId);
-            } else {
-                PhoneSwitcher.getInstance().unsetOpportunisticDataSubscription();
-            }
+            PhoneSwitcher.getInstance().trySetPreferredSubscription(
+                    subId, needValidation, callback);
         } finally {
             Binder.restoreCallingIdentity(token);
         }
@@ -2708,17 +2706,12 @@
     @Override
     public int getPreferredDataSubscriptionId() {
         enforceReadPrivilegedPhoneState("getPreferredDataSubscriptionId");
-        return mPreferredDataSubId;
-    }
+        final long token = Binder.clearCallingIdentity();
 
-    private void notifyPreferredDataSubIdChanged() {
-        ITelephonyRegistry tr = ITelephonyRegistry.Stub.asInterface(ServiceManager.getService(
-                "telephony.registry"));
         try {
-            if (DBG) logd("notifyPreferredDataSubIdChanged:");
-            tr.notifyPreferredDataSubIdChanged(mPreferredDataSubId);
-        } catch (RemoteException ex) {
-            // Should never happen because its always available.
+            return PhoneSwitcher.getInstance().getPreferredDataSubscriptionId();
+        } finally {
+            Binder.restoreCallingIdentity(token);
         }
     }
 
diff --git a/tests/telephonytests/src/com/android/internal/telephony/PhoneSwitcherTest.java b/tests/telephonytests/src/com/android/internal/telephony/PhoneSwitcherTest.java
index 7746bd5..de47da1 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/PhoneSwitcherTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/PhoneSwitcherTest.java
@@ -386,13 +386,14 @@
         assertTrue(mDataAllowed[0]);
 
         // Set sub 2 as preferred sub should make phone 1 activated and phone 0 deactivated.
-        mPhoneSwitcher.setOpportunisticDataSubscription(2);
+        mPhoneSwitcher.trySetPreferredSubscription(2, false, null);
         waitABit();
         assertFalse(mDataAllowed[0]);
         assertTrue(mDataAllowed[1]);
 
         // Unset preferred sub should make default data sub (phone 0 / sub 1) activated again.
-        mPhoneSwitcher.unsetOpportunisticDataSubscription();
+        mPhoneSwitcher.trySetPreferredSubscription(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID,
+                false, null);
         waitABit();
         assertTrue(mDataAllowed[0]);
         assertFalse(mDataAllowed[1]);
@@ -441,7 +442,7 @@
         assertTrue(mPhoneSwitcher.shouldApplyNetworkRequest(mmsRequest, 1));
 
         // Set sub 2 as preferred sub should make phone 1 preferredDataModem
-        mPhoneSwitcher.setOpportunisticDataSubscription(2);
+        mPhoneSwitcher.trySetPreferredSubscription(2, false, null);
         waitABit();
         verify(mMockRadioConfig).setPreferredDataModem(eq(1), any());
         verify(mActivePhoneSwitchHandler, times(2)).sendMessageAtTime(any(), anyLong());
@@ -454,7 +455,8 @@
         clearInvocations(mActivePhoneSwitchHandler);
 
         // Unset preferred sub should make phone0 preferredDataModem again.
-        mPhoneSwitcher.unsetOpportunisticDataSubscription();
+        mPhoneSwitcher.trySetPreferredSubscription(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID,
+                false, null);
         waitABit();
         verify(mMockRadioConfig).setPreferredDataModem(eq(0), any());
         verify(mActivePhoneSwitchHandler, times(2)).sendMessageAtTime(any(), anyLong());
diff --git a/tests/telephonytests/src/com/android/internal/telephony/ims/ImsManagerTest.java b/tests/telephonytests/src/com/android/internal/telephony/ims/ImsManagerTest.java
index dff75b0..294c5bb 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/ims/ImsManagerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/ims/ImsManagerTest.java
@@ -61,10 +61,14 @@
     private static final boolean VT_IMS_ENABLE_DEFAULT_VAL = true;
     private static final boolean WFC_IMS_EDITABLE_VAL = true;
     private static final boolean WFC_IMS_NOT_EDITABLE_VAL = false;
+    private static final boolean WFC_IMS_ROAMING_EDITABLE_VAL = true;
+    private static final boolean WFC_IMS_ROAMING_NOT_EDITABLE_VAL = false;
     private static final int WFC_IMS_MODE_DEFAULT_VAL =
             ImsConfig.WfcModeFeatureValueConstants.CELLULAR_PREFERRED;
     private static final int WFC_IMS_ROAMING_MODE_DEFAULT_VAL =
             ImsConfig.WfcModeFeatureValueConstants.WIFI_PREFERRED;
+    private static final boolean WFC_USE_HOME_MODE_FOR_ROAMING_VAL = true;
+    private static final boolean WFC_NOT_USE_HOME_MODE_FOR_ROAMING_VAL = false;
 
     PersistableBundle mBundle;
     @Mock IBinder mBinder;
@@ -107,6 +111,8 @@
                 ENHANCED_4G_MODE_EDITABLE);
         mBundle.putBoolean(CarrierConfigManager.KEY_EDITABLE_WFC_MODE_BOOL,
                 WFC_IMS_EDITABLE_VAL);
+        mBundle.putBoolean(CarrierConfigManager.KEY_EDITABLE_WFC_ROAMING_MODE_BOOL,
+                WFC_IMS_ROAMING_EDITABLE_VAL);
         mBundle.putBoolean(CarrierConfigManager.KEY_CARRIER_DEFAULT_WFC_IMS_ENABLED_BOOL,
                 WFC_IMS_ENABLE_DEFAULT_VAL);
         mBundle.putBoolean(CarrierConfigManager.KEY_CARRIER_DEFAULT_WFC_IMS_ROAMING_ENABLED_BOOL,
@@ -118,6 +124,9 @@
         mBundle.putBoolean(CarrierConfigManager.KEY_ENHANCED_4G_LTE_ON_BY_DEFAULT_BOOL,
                 ENHANCED_4G_MODE_DEFAULT_VAL);
         mBundle.putBoolean(CarrierConfigManager.KEY_CARRIER_VOLTE_PROVISIONING_REQUIRED_BOOL, true);
+        mBundle.putBoolean(
+                CarrierConfigManager.KEY_USE_WFC_HOME_NETWORK_MODE_IN_ROAMING_NETWORK_BOOL,
+                WFC_NOT_USE_HOME_MODE_FOR_ROAMING_VAL);
     }
 
     @Test @SmallTest
@@ -653,6 +662,98 @@
                 eq(WFC_IMS_MODE_DEFAULT_VAL));
     }
 
+    /**
+     * Tests the operation of getWfcMode when the configuration to use the home network mode when
+     * roaming for WFC is false. First, it checks that the user setting for WFC_IMS_ROAMING_MODE is
+     * returned when WFC roaming is set to editable. Then, it switches the WFC roaming mode to not
+     * editable and ensures that the default WFC roaming mode is returned.
+     *
+     * Preconditions:
+     *  - CarrierConfigManager.KEY_USE_WFC_HOME_NETWORK_MODE_IN_ROAMING_NETWORK_BOOL = false
+     */
+    @Test @SmallTest
+    public void getWfcMode_useWfcHomeModeConfigFalse_shouldUseWfcRoamingMode() {
+        // Set some values that are different than the defaults for WFC mode.
+        doReturn(String.valueOf(ImsConfig.WfcModeFeatureValueConstants.WIFI_ONLY))
+                .when(mSubscriptionController).getSubscriptionProperty(
+                anyInt(),
+                eq(SubscriptionManager.WFC_IMS_MODE),
+                anyString());
+        doReturn(String.valueOf(ImsConfig.WfcModeFeatureValueConstants.CELLULAR_PREFERRED))
+                .when(mSubscriptionController).getSubscriptionProperty(
+                anyInt(),
+                eq(SubscriptionManager.WFC_IMS_ROAMING_MODE),
+                anyString());
+
+        ImsManager imsManager = getImsManagerAndInitProvisionedValues();
+
+        // Check that use the WFC roaming network mode.
+        assertEquals(ImsConfig.WfcModeFeatureValueConstants.CELLULAR_PREFERRED,
+                imsManager.getWfcMode(true));
+        verify(mSubscriptionController, times(1)).getSubscriptionProperty(
+                anyInt(),
+                eq(SubscriptionManager.WFC_IMS_ROAMING_MODE),
+                anyString());
+
+        // Set WFC roaming network mode to not editable.
+        mBundle.putBoolean(CarrierConfigManager.KEY_EDITABLE_WFC_ROAMING_MODE_BOOL,
+                WFC_IMS_ROAMING_NOT_EDITABLE_VAL);
+
+        // Check that use the default WFC roaming network mode.
+        assertEquals(WFC_IMS_ROAMING_MODE_DEFAULT_VAL, imsManager.getWfcMode(true));
+        verify(mSubscriptionController, times(1)).getSubscriptionProperty(
+                anyInt(),
+                eq(SubscriptionManager.WFC_IMS_ROAMING_MODE),
+                anyString());
+    }
+
+    /**
+     * Tests the operation of getWfcMode when the configuration to use the home network mode when
+     * roaming for WFC is true independent of whether or not the WFC roaming mode is editable.
+     *
+     * Preconditions:
+     *  - CarrierConfigManager.KEY_USE_WFC_HOME_NETWORK_MODE_IN_ROAMING_NETWORK_BOOL = true
+     */
+    @Test @SmallTest
+    public void getWfcMode_useWfcHomeModeConfigTrue_shouldUseWfcHomeMode() {
+        // Set some values that are different than the defaults for WFC mode.
+        doReturn(String.valueOf(ImsConfig.WfcModeFeatureValueConstants.WIFI_ONLY))
+                .when(mSubscriptionController).getSubscriptionProperty(
+                anyInt(),
+                eq(SubscriptionManager.WFC_IMS_MODE),
+                anyString());
+        doReturn(String.valueOf(ImsConfig.WfcModeFeatureValueConstants.CELLULAR_PREFERRED))
+                .when(mSubscriptionController).getSubscriptionProperty(
+                anyInt(),
+                eq(SubscriptionManager.WFC_IMS_ROAMING_MODE),
+                anyString());
+
+        // Set to use WFC home network mode in roaming network.
+        mBundle.putBoolean(
+                CarrierConfigManager.KEY_USE_WFC_HOME_NETWORK_MODE_IN_ROAMING_NETWORK_BOOL,
+                WFC_USE_HOME_MODE_FOR_ROAMING_VAL);
+
+        ImsManager imsManager = getImsManagerAndInitProvisionedValues();
+
+        // Check that use the WFC home network mode.
+        assertEquals(ImsConfig.WfcModeFeatureValueConstants.WIFI_ONLY, imsManager.getWfcMode(true));
+        verify(mSubscriptionController, times(1)).getSubscriptionProperty(
+                anyInt(),
+                eq(SubscriptionManager.WFC_IMS_MODE),
+                anyString());
+
+        // Set WFC home network mode to not editable.
+        mBundle.putBoolean(CarrierConfigManager.KEY_EDITABLE_WFC_MODE_BOOL,
+                WFC_IMS_NOT_EDITABLE_VAL);
+
+        // Check that use the default WFC home network mode.
+        assertEquals(WFC_IMS_MODE_DEFAULT_VAL, imsManager.getWfcMode(true));
+        verify(mSubscriptionController, times(1)).getSubscriptionProperty(
+                anyInt(),
+                eq(SubscriptionManager.WFC_IMS_MODE),
+                anyString());
+    }
+
     private ImsManager getImsManagerAndInitProvisionedValues() {
         when(mImsConfigImplBaseMock.getConfigInt(anyInt()))
                 .thenAnswer(invocation ->  {