[SIM Dialog Migration] Migrate SIM enable dialog from LPA to Settings

Migrates SIM enabling and DSDS dialog from LPA to Settings.
Design: https://docs.google.com/document/d/1wb5_hoBkZVbkXGNWHbx4Jf61swjfxsJzkytiTzJosYo/edit?usp=sharing
Bug: 160819390
Test: Manually tested eSIM profile enabling.

Change-Id: I9ce690a1594adfc90b62840819237acd54418469
diff --git a/res/values/strings.xml b/res/values/strings.xml
index acc7f51..2861d52 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -11975,6 +11975,30 @@
     <string name="see_less">See less</string>
 
     <!-- Strings for toggling subscriptions dialog activity -->
+    <!-- Title of confirmation dialog asking the user if they want to enable subscription. [CHAR_LIMIT=NONE] -->
+    <string name="sim_action_enable_sub_dialog_title">Turn on <xliff:g id="carrier_name" example="Google Fi">%1$s</xliff:g>?</string>
+    <!-- Title of confirmation dialog asking the user if they want to enable subscription without the subscription name. [CHAR_LIMIT=NONE] -->
+    <string name="sim_action_enable_sub_dialog_title_without_carrier_name">Turn on SIM?</string>
+    <!-- Title of confirmation dialog asking the user if they want to switch subscription. [CHAR_LIMIT=NONE] -->
+    <string name="sim_action_switch_sub_dialog_title">Switch to <xliff:g id="carrier_name" example="Google Fi">%1$s</xliff:g>?</string>
+    <!-- Title of confirmation dialog asking the user if they want to switch to the SIM card. [CHAR_LIMIT=NONE] -->
+    <string name="sim_action_switch_psim_dialog_title">Switch to using SIM card?</string>
+    <!-- Body text of confirmation dialog for switching subscription that involves switching SIM slots. Indicates that only one SIM can be active at a time. Also that switching will not cancel the user's mobile service plan. [CHAR_LIMIT=NONE] -->
+    <string name="sim_action_switch_sub_dialog_text">Only one SIM can be active at a time.\n\nSwitching to <xliff:g id="to_carrier_name" example="Google Fi">%1$s</xliff:g> won\u2019t cancel your <xliff:g id="from_carrier_name" example="Sprint">%2$s</xliff:g> service.</string>
+    <!-- Body text of confirmation dialog for switching subscription between two eSIM profiles. Indicates that only one downloaded SIM can be active at a time. Also that switching will not cancel the user's mobile service plan. [CHAR_LIMIT=NONE] -->
+    <string name="sim_action_switch_sub_dialog_text_downloaded">Only one downloaded SIM can be active at a time.\n\nSwitching to <xliff:g id="to_carrier_name" example="Google Fi">%1$s</xliff:g> won\u2019t cancel your <xliff:g id="from_carrier_name" example="Sprint">%2$s</xliff:g> service.</string>
+    <!-- Body text of confirmation dialog for switching subscription between two eSIM profiles. Indicates that only one SIM can be active at a time. Also that switching will not cancel the user's mobile service plan. [CHAR_LIMIT=NONE] -->
+    <string name="sim_action_switch_sub_dialog_text_single_sim">Only one SIM can be active at a time.\n\nSwitching won\u2019t cancel your <xliff:g id="to_carrier_name" example="Google Fi">%1$s</xliff:g> service.</string>
+    <!-- Text of confirm button in the confirmation dialog asking the user if they want to switch subscription. [CHAR_LIMIT=NONE] -->
+    <string name="sim_action_switch_sub_dialog_confirm">Switch to <xliff:g id="carrier_name" example="Google Fi">%1$s</xliff:g></string>
+    <!-- Status message indicating the device is in the process of disconnecting from one mobile network and immediately connecting to another. [CHAR_LIMIT=NONE] -->
+    <string name="sim_action_enabling_sim_without_carrier_name">Connecting to network&#8230;</string>
+    <!-- Text of progress dialog indicating the subscription switch is in progress. [CHAR_LIMIT=NONE] -->
+    <string name="sim_action_switch_sub_dialog_progress">Switching to <xliff:g id="carrier_name" example="Google Fi">%1$s</xliff:g></string>
+    <!-- Title of error message indicating that the device could not disconnect from one mobile network and immediately connect to another. [CHAR_LIMIT=NONE] -->
+    <string name="sim_action_enable_sim_fail_title">Can\u2019t switch carrier</string>
+    <!-- Body text of error message indicating the device could not disconnect from one mobile network and immediately connect to another, due to an unspecified issue. [CHAR_LIMIT=NONE] -->
+    <string name="sim_action_enable_sim_fail_text">The carrier can\u2019t be switched due to an error.</string>
     <!-- Title of confirmation dialog asking the user if they want to disable subscription. [CHAR_LIMIT=NONE] -->
     <string name="privileged_action_disable_sub_dialog_title">Turn off <xliff:g id="carrier_name" example="Google Fi">%1$s</xliff:g>?</string>
     <!-- Title of confirmation dialog asking the user if they want to disable subscription. [CHAR_LIMIT=NONE] -->
@@ -11982,9 +12006,31 @@
     <!-- Disabling SIMs progress dialog message [CHAR LIMIT=NONE] -->
     <string name="privileged_action_disable_sub_dialog_progress">Turning off SIM<xliff:g id="ellipsis" example="...">&#8230;</xliff:g></string>
     <!-- Title of error messaging indicating the device could not disable the mobile network. [CHAR LIMIT=NONE] -->
-    <string name="privileged_action_disable_fail_title">Can\'t disable carrier</string>
+    <string name="privileged_action_disable_fail_title">Can\u2019t disable carrier</string>
     <!-- Body text of error message indicating the device could not disable the mobile network, due to an unknown issue. [CHAR LIMIT=NONE] -->
     <string name="privileged_action_disable_fail_text">Something went wrong and your carrier could not be disabled.</string>
+    <!-- Title on a dialog asking the users whether they want to enable DSDS mode. [CHAR LIMIT=NONE] -->
+    <string name="sim_action_enable_dsds_title">Use 2 SIMs?</string>
+    <!-- Message in a dialog indicating the user can enable DSDS mode. [CHAR LIMIT=NONE] -->
+    <string name="sim_action_enable_dsds_text">This device can have 2 SIMs active at once. To continue using 1 SIM at a time, tap \"No thanks\".</string>
+    <!-- Ask the user whether to restart device. [CHAR LIMIT=NONE] -->
+    <string name="sim_action_restart_title">Restart device?</string>
+    <!-- Tell the user that in order to enable DSDS mode, the phone needs to restart. [CHAR LIMIT=NONE] -->
+    <string name="sim_action_restart_text">To get started, restart your device. Then you can add another SIM.</string>
+    <!-- Button on a dialog to confirm SIM operations. [CHAR LIMIT=30] -->
+    <string name="sim_action_continue">Continue</string>
+    <!-- User confirms reboot the phone. [CHAR LIMIT=30] -->
+    <string name="sim_action_reboot">Restart</string>
+    <!-- Button on a dialog to reject SIM operations. [CHAR LIMIT=30] -->
+    <string name="sim_action_no_thanks">No thanks</string>
+    <!-- Button which will disconnect the user from one mobile network and immediately connect to another. [CHAR LIMIT=30] -->
+    <string name="sim_switch_button">Switch</string>
+    <!-- Title of DSDS activation failure dialog [CHAR LIMIT=40] -->
+    <string name="dsds_activation_failure_title">Can\u2019t activate SIM</string>
+    <!-- Body text of DSDS activation failure dialog. Users could reinsert the SIM card or reboot to recover. [CHAR LIMIT=NONE] -->
+    <string name="dsds_activation_failure_body_msg1">Remove the SIM and insert it again. If the problem continues, restart your device.</string>
+    <!-- Body text of DSDS activation failure dialog. Users could toggle the selected SIM again or reboot to recover. [CHAR LIMIT=NONE] -->
+    <string name="dsds_activation_failure_body_msg2">Try turning on the SIM again. If the problem continues, restart your device.</string>
 
     <!-- Strings for deleting eUICC subscriptions dialog activity -->
     <!-- Title on confirmation dialog asking the user if they want to erase the downloaded SIM from the device. [CHAR_LIMIT=NONE] -->
diff --git a/src/com/android/settings/AsyncTaskSidecar.java b/src/com/android/settings/AsyncTaskSidecar.java
new file mode 100644
index 0000000..31c8298
--- /dev/null
+++ b/src/com/android/settings/AsyncTaskSidecar.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings;
+
+import android.os.AsyncTask;
+
+import androidx.annotation.Nullable;
+
+import com.android.settingslib.utils.ThreadUtils;
+
+import java.util.concurrent.Future;
+
+/** A {@link SidecarFragment} which uses an {@link AsyncTask} to perform background work. */
+public abstract class AsyncTaskSidecar<Param, Result> extends SidecarFragment {
+
+    private Future<Result> mAsyncTask;
+
+    @Override
+    public void onDestroy() {
+        if (mAsyncTask != null) {
+            mAsyncTask.cancel(true /* mayInterruptIfRunning */);
+        }
+
+        super.onDestroy();
+    }
+
+    /**
+     * Executes the background task.
+     *
+     * @param param parameters passed in from {@link #run}
+     */
+    protected abstract Result doInBackground(@Nullable Param param);
+
+    /** Handles the background task's result. */
+    protected void onPostExecute(Result result) {}
+
+    /** Runs the sidecar and sets the state to RUNNING. */
+    public void run(@Nullable final Param param) {
+        setState(State.RUNNING, Substate.UNUSED);
+
+        if (mAsyncTask != null) {
+            mAsyncTask.cancel(true /* mayInterruptIfRunning */);
+        }
+
+        mAsyncTask =
+                ThreadUtils.postOnBackgroundThread(
+                        () -> {
+                            Result result = doInBackground(param);
+                            ThreadUtils.postOnMainThread(() -> onPostExecute(result));
+                        });
+    }
+}
diff --git a/src/com/android/settings/network/CarrierConfigChangedReceiver.java b/src/com/android/settings/network/CarrierConfigChangedReceiver.java
new file mode 100644
index 0000000..8a6d47d
--- /dev/null
+++ b/src/com/android/settings/network/CarrierConfigChangedReceiver.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.network;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.telephony.CarrierConfigManager;
+import android.util.Log;
+
+import java.util.concurrent.CountDownLatch;
+
+/** A receiver listens to the carrier config changes. */
+public class CarrierConfigChangedReceiver extends BroadcastReceiver {
+    private static final String TAG = "CarrierConfigChangedReceiver";
+    private static final String ACTION_CARRIER_CONFIG_CHANGED =
+            CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED;
+
+    private final CountDownLatch mLatch;
+
+    public CarrierConfigChangedReceiver(CountDownLatch latch) {
+        mLatch = latch;
+    }
+
+    public void registerOn(Context context) {
+        context.registerReceiver(this, new IntentFilter(ACTION_CARRIER_CONFIG_CHANGED));
+    }
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        if (isInitialStickyBroadcast()) {
+            return;
+        }
+
+        if (ACTION_CARRIER_CONFIG_CHANGED.equals(intent.getAction())) {
+            checkSubscriptionIndex(intent);
+        }
+    }
+
+    private void checkSubscriptionIndex(Intent intent) {
+        if (intent.hasExtra(CarrierConfigManager.EXTRA_SUBSCRIPTION_INDEX)) {
+            int subId = intent.getIntExtra(CarrierConfigManager.EXTRA_SUBSCRIPTION_INDEX, -1);
+            Log.i(TAG, "subId from config changed: " + subId);
+            mLatch.countDown();
+        }
+    }
+}
diff --git a/src/com/android/settings/network/EnableMultiSimSidecar.java b/src/com/android/settings/network/EnableMultiSimSidecar.java
new file mode 100644
index 0000000..c47e61a
--- /dev/null
+++ b/src/com/android/settings/network/EnableMultiSimSidecar.java
@@ -0,0 +1,190 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.network;
+
+import android.app.FragmentManager;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.telephony.CarrierConfigManager;
+import android.telephony.TelephonyManager;
+import android.telephony.UiccSlotInfo;
+import android.util.ArraySet;
+import android.util.Log;
+
+import com.android.settings.AsyncTaskSidecar;
+import com.android.settings.SidecarFragment;
+
+import java.util.Collections;
+import java.util.Set;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * {@code EnableMultiSimSidecar} enables multi SIM on the device. It should only be called for
+ * Android R+. After {@code run} is called, it sets the configuration on modem side to enable
+ * multiple SIMs. Once the configuration is set successfully, it will listen to UICC card changes
+ * until {@code TelMan.EXTRA_ACTIVE_SIM_SUPPORTED_COUNT} matches {@code mNumOfActiveSim} or timeout.
+ */
+public class EnableMultiSimSidecar extends AsyncTaskSidecar<Void, Boolean> {
+
+    // Tags
+    private static final String TAG = "EnableMultiSimSidecar";
+
+    // TODO(b/171846124): Pass timeout value from LPA to Settings
+    private static final long ENABLE_MULTI_SIM_TIMEOUT_MILLS = 40 * 1000L;
+
+    public static EnableMultiSimSidecar get(FragmentManager fm) {
+        return SidecarFragment.get(fm, TAG, EnableMultiSimSidecar.class, null /* args */);
+    }
+
+    final CountDownLatch mSimCardStateChangedLatch = new CountDownLatch(1);
+    private TelephonyManager mTelephonyManager;
+    private int mNumOfActiveSim = 0;
+
+    private final BroadcastReceiver mCarrierConfigChangeReceiver =
+            new BroadcastReceiver() {
+                @Override
+                public void onReceive(Context context, Intent intent) {
+                    int readySimsCount = getReadySimsCount();
+                    int activeSlotsCount = getActiveSlotsCount();
+                    // If the number of ready SIM count and active slots equal to the number of SIMs
+                    // need to be activated, the device is successfully switched to multiple active
+                    // SIM mode.
+                    if (readySimsCount == mNumOfActiveSim && activeSlotsCount == mNumOfActiveSim) {
+                        Log.i(
+                                TAG,
+                                String.format("%d slots are active and ready.", mNumOfActiveSim));
+                        mSimCardStateChangedLatch.countDown();
+                        return;
+                    }
+                    Log.i(
+                            TAG,
+                            String.format(
+                                    "%d slots are active and %d SIMs are ready. Keep waiting until"
+                                        + " timeout.",
+                                    activeSlotsCount, readySimsCount));
+                }
+            };
+
+    @Override
+    protected Boolean doInBackground(Void aVoid) {
+        return updateMultiSimConfig();
+    }
+
+    @Override
+    protected void onPostExecute(Boolean isDsdsEnabled) {
+        if (isDsdsEnabled) {
+            setState(State.SUCCESS, Substate.UNUSED);
+        } else {
+            setState(State.ERROR, Substate.UNUSED);
+        }
+    }
+
+    public void run(int numberOfSimToActivate) {
+        mTelephonyManager = getContext().getSystemService(TelephonyManager.class);
+        mNumOfActiveSim = numberOfSimToActivate;
+
+        if (mNumOfActiveSim > mTelephonyManager.getSupportedModemCount()) {
+            Log.e(TAG, "Requested number of active SIM is greater than supported modem count.");
+            setState(State.ERROR, Substate.UNUSED);
+            return;
+        }
+        if (mTelephonyManager.doesSwitchMultiSimConfigTriggerReboot()) {
+            Log.e(TAG, "The device does not support reboot free DSDS.");
+            setState(State.ERROR, Substate.UNUSED);
+            return;
+        }
+        super.run(null /* param */);
+    }
+
+    // This method registers a ACTION_SIM_CARD_STATE_CHANGED broadcast receiver and wait for slot
+    // changes. If multi SIMs have been successfully enabled, it returns true. Otherwise, returns
+    // false.
+    private boolean updateMultiSimConfig() {
+        try {
+            getContext()
+                    .registerReceiver(
+                            mCarrierConfigChangeReceiver,
+                            new IntentFilter(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED));
+            mTelephonyManager.switchMultiSimConfig(mNumOfActiveSim);
+            if (mSimCardStateChangedLatch.await(
+                    ENABLE_MULTI_SIM_TIMEOUT_MILLS, TimeUnit.MILLISECONDS)) {
+                Log.i(TAG, "Multi SIM were successfully enabled.");
+                return true;
+            } else {
+                Log.e(TAG, "Timeout for waiting SIM status.");
+                return false;
+            }
+        } catch (InterruptedException e) {
+            Log.e(TAG, "Failed to enable multiple SIM due to InterruptedException", e);
+            return false;
+        } finally {
+            getContext().unregisterReceiver(mCarrierConfigChangeReceiver);
+        }
+    }
+
+    // Returns how many SIMs have SIM ready state, not ready state, or removable slot with absent
+    // SIM state.
+    private int getReadySimsCount() {
+        int readyCardsCount = 0;
+        int activeSlotCount = mTelephonyManager.getActiveModemCount();
+        Set<Integer> activeRemovableLogicalSlots = getActiveRemovableLogicalSlotIds();
+        for (int logicalSlotId = 0; logicalSlotId < activeSlotCount; logicalSlotId++) {
+            int simState = mTelephonyManager.getSimState(logicalSlotId);
+            if (simState == TelephonyManager.SIM_STATE_READY
+                    || simState == TelephonyManager.SIM_STATE_NOT_READY
+                    || simState == TelephonyManager.SIM_STATE_LOADED
+                    || (simState == TelephonyManager.SIM_STATE_ABSENT
+                            && activeRemovableLogicalSlots.contains(logicalSlotId))) {
+                readyCardsCount++;
+            }
+        }
+        return readyCardsCount;
+    }
+
+    // Get active slots count from {@code TelephonyManager#getUiccSlotsInfo}.
+    private int getActiveSlotsCount() {
+        UiccSlotInfo[] slotsInfo = mTelephonyManager.getUiccSlotsInfo();
+        if (slotsInfo == null) {
+            return 0;
+        }
+        int activeSlots = 0;
+        for (UiccSlotInfo slotInfo : slotsInfo) {
+            if (slotInfo != null && slotInfo.getIsActive()) {
+                activeSlots++;
+            }
+        }
+        return activeSlots;
+    }
+
+    /** Returns a list of active removable logical slot ids. */
+    public Set<Integer> getActiveRemovableLogicalSlotIds() {
+        UiccSlotInfo[] infos = mTelephonyManager.getUiccSlotsInfo();
+        if (infos == null) {
+            return Collections.emptySet();
+        }
+        Set<Integer> activeRemovableLogicalSlotIds = new ArraySet<>();
+        for (UiccSlotInfo info : infos) {
+            if (info != null && info.getIsActive() && info.isRemovable()) {
+                activeRemovableLogicalSlotIds.add(info.getLogicalSlotIdx());
+            }
+        }
+        return activeRemovableLogicalSlotIds;
+    }
+}
diff --git a/src/com/android/settings/network/SwitchSlotSidecar.java b/src/com/android/settings/network/SwitchSlotSidecar.java
new file mode 100644
index 0000000..cffb23f
--- /dev/null
+++ b/src/com/android/settings/network/SwitchSlotSidecar.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.network;
+
+import android.annotation.IntDef;
+import android.app.FragmentManager;
+import android.os.Bundle;
+import android.util.Log;
+
+import com.android.settings.AsyncTaskSidecar;
+import com.android.settings.SidecarFragment;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+import javax.annotation.Nullable;
+
+/** {@link SidecarFragment} to switch SIM slot. */
+public class SwitchSlotSidecar
+        extends AsyncTaskSidecar<SwitchSlotSidecar.Param, SwitchSlotSidecar.Result> {
+    private static final String TAG = "SwitchSlotSidecar";
+
+    /** Commands */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({
+        Command.SWITCH_TO_REMOVABLE_SIM,
+    })
+    private @interface Command {
+        int SWITCH_TO_REMOVABLE_SIM = 0;
+    }
+
+    static class Param {
+        @Command int command;
+        int slotId;
+    }
+
+    static class Result {
+        Exception exception;
+    }
+
+    /** Returns a SwitchSlotSidecar sidecar instance. */
+    public static SwitchSlotSidecar get(FragmentManager fm) {
+        return SidecarFragment.get(fm, TAG, SwitchSlotSidecar.class, null /* args */);
+    }
+
+    @Nullable private Exception mException;
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+    }
+
+    /** Starts switching to the removable slot. */
+    public void runSwitchToRemovableSlot(int id) {
+        Param param = new Param();
+        param.command = Command.SWITCH_TO_REMOVABLE_SIM;
+        param.slotId = id;
+        super.run(param);
+    }
+
+    /**
+     * Returns the exception thrown during the execution of a command. Will be null in any state
+     * other than {@link State#SUCCESS}, and may be null in that state if there was not an error.
+     */
+    @Nullable
+    public Exception getException() {
+        return mException;
+    }
+
+    @Override
+    protected Result doInBackground(@Nullable Param param) {
+        Result result = new Result();
+        if (param == null) {
+            result.exception = new UiccSlotsException("Null param");
+            return result;
+        }
+        try {
+            switch (param.command) {
+                case Command.SWITCH_TO_REMOVABLE_SIM:
+                    UiccSlotUtil.switchToRemovableSlot(param.slotId, getContext());
+                    break;
+                default:
+                    Log.e(TAG, "Wrong command.");
+                    break;
+            }
+        } catch (UiccSlotsException e) {
+            result.exception = e;
+        }
+        return result;
+    }
+
+    @Override
+    protected void onPostExecute(Result result) {
+        if (result.exception == null) {
+            setState(State.SUCCESS, Substate.UNUSED);
+        } else {
+            mException = result.exception;
+            setState(State.ERROR, Substate.UNUSED);
+        }
+    }
+}
diff --git a/src/com/android/settings/network/SwitchToRemovableSlotSidecar.java b/src/com/android/settings/network/SwitchToRemovableSlotSidecar.java
new file mode 100644
index 0000000..132a2fd
--- /dev/null
+++ b/src/com/android/settings/network/SwitchToRemovableSlotSidecar.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.network;
+
+import android.app.FragmentManager;
+import android.os.Bundle;
+import android.telephony.SubscriptionInfo;
+import android.telephony.SubscriptionManager;
+import android.util.Log;
+
+import com.android.settings.SidecarFragment;
+import com.android.settings.network.telephony.EuiccOperationSidecar;
+
+/**
+ * This sidecar is responsible for switching to the removable slot. It disables the active eSIM
+ * profile before switching if there is one.
+ */
+public class SwitchToRemovableSlotSidecar extends EuiccOperationSidecar
+        implements SidecarFragment.Listener {
+
+    private static final String TAG = "DisableSubscriptionAndSwitchSlotSidecar";
+    private static final String ACTION_DISABLE_SUBSCRIPTION_AND_SWITCH_SLOT =
+            "disable_subscription_and_switch_slot_sidecar";
+
+    // Stateless members.
+    private SwitchToEuiccSubscriptionSidecar mSwitchToSubscriptionSidecar;
+    private SwitchSlotSidecar mSwitchSlotSidecar;
+    private int mPhysicalSlotId;
+
+    /** Returns a SwitchToRemovableSlotSidecar sidecar instance. */
+    public static SwitchToRemovableSlotSidecar get(FragmentManager fm) {
+        return SidecarFragment.get(fm, TAG, SwitchToRemovableSlotSidecar.class, null /* args */);
+    }
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        mSwitchToSubscriptionSidecar =
+                SwitchToEuiccSubscriptionSidecar.get(getChildFragmentManager());
+        mSwitchSlotSidecar = SwitchSlotSidecar.get(getChildFragmentManager());
+    }
+
+    @Override
+    public void onResume() {
+        super.onResume();
+        mSwitchToSubscriptionSidecar.addListener(this);
+        mSwitchSlotSidecar.addListener(this);
+    }
+
+    @Override
+    public void onPause() {
+        mSwitchToSubscriptionSidecar.removeListener(this);
+        mSwitchSlotSidecar.removeListener(this);
+        super.onPause();
+    }
+
+    @Override
+    protected String getReceiverAction() {
+        return ACTION_DISABLE_SUBSCRIPTION_AND_SWITCH_SLOT;
+    }
+
+    @Override
+    public void onStateChange(SidecarFragment fragment) {
+        if (fragment == mSwitchToSubscriptionSidecar) {
+            onSwitchToSubscriptionSidecarStateChange();
+        } else if (fragment == mSwitchSlotSidecar) {
+            onSwitchSlotSidecarStateChange();
+        } else {
+            Log.wtf(TAG, "Received state change from a sidecar not expected.");
+        }
+    }
+
+    /**
+     * Starts switching to the removable slot. It disables the active eSIM profile before switching
+     * if there is one.
+     *
+     * @param physicalSlotId removable physical SIM slot ID.
+     */
+    public void run(int physicalSlotId) {
+        mPhysicalSlotId = physicalSlotId;
+        SubscriptionManager subscriptionManager =
+                getContext().getSystemService(SubscriptionManager.class);
+        if (SubscriptionUtil.getActiveSubscriptions(subscriptionManager).stream()
+                .anyMatch(SubscriptionInfo::isEmbedded)) {
+            Log.i(TAG, "There is an active eSIM profile. Disable the profile first.");
+            // Use INVALID_SUBSCRIPTION_ID to disable the only active profile.
+            mSwitchToSubscriptionSidecar.run(SubscriptionManager.INVALID_SUBSCRIPTION_ID);
+        } else {
+            Log.i(TAG, "There is no active eSIM profiles. Start to switch to removable slot.");
+            mSwitchSlotSidecar.runSwitchToRemovableSlot(mPhysicalSlotId);
+        }
+    }
+
+    private void onSwitchToSubscriptionSidecarStateChange() {
+        switch (mSwitchToSubscriptionSidecar.getState()) {
+            case State.SUCCESS:
+                mSwitchToSubscriptionSidecar.reset();
+                Log.i(
+                        TAG,
+                        "Successfully disabled eSIM profile. Start to switch to Removable slot.");
+                mSwitchSlotSidecar.runSwitchToRemovableSlot(mPhysicalSlotId);
+                break;
+            case State.ERROR:
+                mSwitchToSubscriptionSidecar.reset();
+                Log.i(TAG, "Failed to disable the active eSIM profile.");
+                setState(State.ERROR, Substate.UNUSED);
+                break;
+        }
+    }
+
+    private void onSwitchSlotSidecarStateChange() {
+        switch (mSwitchSlotSidecar.getState()) {
+            case State.SUCCESS:
+                mSwitchSlotSidecar.reset();
+                Log.i(TAG, "Successfully switched to removable slot.");
+                setState(State.SUCCESS, Substate.UNUSED);
+                break;
+            case State.ERROR:
+                mSwitchSlotSidecar.reset();
+                Log.i(TAG, "Failed to switch to removable slot.");
+                setState(State.ERROR, Substate.UNUSED);
+                break;
+        }
+    }
+}
diff --git a/src/com/android/settings/network/UiccSlotUtil.java b/src/com/android/settings/network/UiccSlotUtil.java
new file mode 100644
index 0000000..792f02a
--- /dev/null
+++ b/src/com/android/settings/network/UiccSlotUtil.java
@@ -0,0 +1,153 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.network;
+
+import android.annotation.IntDef;
+import android.content.Context;
+import android.telephony.TelephonyManager;
+import android.telephony.UiccSlotInfo;
+import android.util.Log;
+
+import com.android.settingslib.utils.ThreadUtils;
+
+import com.google.common.collect.ImmutableList;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+public class UiccSlotUtil {
+
+    private static final String TAG = "UiccSlotUtil";
+
+    // TODO(b/171846124): Pass timeout value from LPA to Settings
+    private static final long WAIT_AFTER_SWITCH_TIMEOUT_MILLIS = 25000;
+
+    public static final int INVALID_PHYSICAL_SLOT_ID = -1;
+
+    /**
+     * Mode for switching to eSIM slot which decides whether there is cleanup process, e.g.
+     * disabling test profile, after eSIM slot is activated and whether we will wait it finished.
+     */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({
+        SwitchingEsimMode.NO_CLEANUP,
+        SwitchingEsimMode.ASYNC_CLEANUP,
+        SwitchingEsimMode.SYNC_CLEANUP
+    })
+    public @interface SwitchingEsimMode {
+        /** No cleanup process after switching to eSIM slot */
+        int NO_CLEANUP = 0;
+        /** Has cleanup process, but we will not wait it finished. */
+        int ASYNC_CLEANUP = 1;
+        /** Has cleanup process and we will wait until it's finished */
+        int SYNC_CLEANUP = 2;
+    }
+
+    /**
+     * Returns an immutable list of all UICC slots. If TelephonyManager#getUiccSlotsInfo returns, it
+     * returns an empty list instead.
+     */
+    public static ImmutableList<UiccSlotInfo> getSlotInfos(TelephonyManager telMgr) {
+        UiccSlotInfo[] slotInfos = telMgr.getUiccSlotsInfo();
+        if (slotInfos == null) {
+            return ImmutableList.of();
+        }
+        return ImmutableList.copyOf(slotInfos);
+    }
+
+    /**
+     * Switches to the removable slot. It waits for SIM_STATE_LOADED after switch. If slotId is
+     * INVALID_PHYSICAL_SLOT_ID, the method will use the first detected inactive removable slot.
+     *
+     * @param slotId the physical removable slot id.
+     * @param context the application context.
+     * @throws UiccSlotsException if there is an error.
+     */
+    public static synchronized void switchToRemovableSlot(int slotId, Context context)
+            throws UiccSlotsException {
+        if (ThreadUtils.isMainThread()) {
+            throw new IllegalThreadStateException(
+                    "Do not call switchToRemovableSlot on the main thread.");
+        }
+        TelephonyManager telMgr = context.getSystemService(TelephonyManager.class);
+        if (telMgr.isMultiSimEnabled()) {
+            // If this device supports multiple active slots, don't mess with TelephonyManager.
+            Log.i(TAG, "Multiple active slots supported. Not calling switchSlots.");
+            return;
+        }
+        UiccSlotInfo[] slots = telMgr.getUiccSlotsInfo();
+        if (slotId == INVALID_PHYSICAL_SLOT_ID) {
+            for (int i = 0; i < slots.length; i++) {
+                if (slots[i].isRemovable()
+                        && !slots[i].getIsActive()
+                        && slots[i].getCardStateInfo() != UiccSlotInfo.CARD_STATE_INFO_ERROR
+                        && slots[i].getCardStateInfo() != UiccSlotInfo.CARD_STATE_INFO_RESTRICTED) {
+                    performSwitchToRemovableSlot(i, context);
+                    return;
+                }
+            }
+        } else {
+            if (slotId >= slots.length || !slots[slotId].isRemovable()) {
+                throw new UiccSlotsException("The given slotId is not a removable slot: " + slotId);
+            }
+            if (!slots[slotId].getIsActive()) {
+                performSwitchToRemovableSlot(slotId, context);
+            }
+        }
+    }
+
+    private static void performSwitchToRemovableSlot(int slotId, Context context)
+            throws UiccSlotsException {
+        CarrierConfigChangedReceiver receiver = null;
+        try {
+            CountDownLatch latch = new CountDownLatch(1);
+            receiver = new CarrierConfigChangedReceiver(latch);
+            receiver.registerOn(context);
+            switchSlots(context, slotId);
+            latch.await(WAIT_AFTER_SWITCH_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
+        } catch (InterruptedException e) {
+            Thread.currentThread().interrupt();
+            Log.e(TAG, "Failed switching to physical slot.", e);
+        } finally {
+            if (receiver != null) {
+                context.unregisterReceiver(receiver);
+            }
+        }
+    }
+
+    /**
+     * Changes the logical slot to physical slot mapping. OEM should override this to provide
+     * device-specific implementation if the device supports switching slots.
+     *
+     * @param context the application context.
+     * @param physicalSlots List of physical slot ids in the order of logical slots.
+     */
+    private static void switchSlots(Context context, int... physicalSlots)
+            throws UiccSlotsException {
+        TelephonyManager telMgr = context.getSystemService(TelephonyManager.class);
+        if (telMgr.isMultiSimEnabled()) {
+            // If this device supports multiple active slots, don't mess with TelephonyManager.
+            Log.i(TAG, "Multiple active slots supported. Not calling switchSlots.");
+            return;
+        }
+        if (!telMgr.switchSlots(physicalSlots)) {
+            throw new UiccSlotsException("Failed to switch slots");
+        }
+    }
+}
diff --git a/src/com/android/settings/network/UiccSlotsException.java b/src/com/android/settings/network/UiccSlotsException.java
new file mode 100644
index 0000000..2a2edc5
--- /dev/null
+++ b/src/com/android/settings/network/UiccSlotsException.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.network;
+
+/** The exception that is thrown when an error happens in a call to {@link UiccSlotUtil}. */
+public class UiccSlotsException extends Exception {
+
+    public UiccSlotsException() {}
+
+    public UiccSlotsException(String message) {
+        super(message);
+    }
+
+    public UiccSlotsException(String message, Throwable cause) {
+        super(message, cause);
+    }
+
+    public UiccSlotsException(Throwable cause) {
+        super(cause);
+    }
+}
diff --git a/src/com/android/settings/network/telephony/ToggleSubscriptionDialogActivity.java b/src/com/android/settings/network/telephony/ToggleSubscriptionDialogActivity.java
index 905ead0..919415b 100644
--- a/src/com/android/settings/network/telephony/ToggleSubscriptionDialogActivity.java
+++ b/src/com/android/settings/network/telephony/ToggleSubscriptionDialogActivity.java
@@ -22,15 +22,22 @@
 import android.os.UserManager;
 import android.telephony.SubscriptionInfo;
 import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
+import android.telephony.UiccSlotInfo;
 import android.text.TextUtils;
 import android.util.Log;
 
-import androidx.appcompat.app.AlertDialog;
-
 import com.android.settings.R;
 import com.android.settings.SidecarFragment;
+import com.android.settings.network.EnableMultiSimSidecar;
 import com.android.settings.network.SubscriptionUtil;
 import com.android.settings.network.SwitchToEuiccSubscriptionSidecar;
+import com.android.settings.network.SwitchToRemovableSlotSidecar;
+import com.android.settings.network.UiccSlotUtil;
+
+import com.google.common.collect.ImmutableList;
+
+import java.util.List;
 
 /** This dialog activity handles both eSIM and pSIM subscriptions enabling and disabling. */
 public class ToggleSubscriptionDialogActivity extends SubscriptionActionDialogActivity
@@ -41,6 +48,11 @@
     private static final String ARG_enable = "enable";
     // Dialog tags
     private static final int DIALOG_TAG_DISABLE_SIM_CONFIRMATION = 1;
+    private static final int DIALOG_TAG_ENABLE_SIM_CONFIRMATION = 2;
+    private static final int DIALOG_TAG_ENABLE_DSDS_CONFIRMATION = 3;
+    private static final int DIALOG_TAG_ENABLE_DSDS_REBOOT_CONFIRMATION = 4;
+    // Number of SIMs for DSDS
+    private static final int NUM_OF_SIMS_FOR_DSDS = 2;
 
     /**
      * Returns an intent of ToggleSubscriptionDialogActivity.
@@ -58,8 +70,11 @@
 
     private SubscriptionInfo mSubInfo;
     private SwitchToEuiccSubscriptionSidecar mSwitchToEuiccSubscriptionSidecar;
-    private AlertDialog mToggleSimConfirmDialog;
+    private SwitchToRemovableSlotSidecar mSwitchToRemovableSlotSidecar;
+    private EnableMultiSimSidecar mEnableMultiSimSidecar;
     private boolean mEnable;
+    private boolean mIsEsimOperation;
+    private TelephonyManager mTelMgr;
 
     @Override
     protected void onCreate(Bundle savedInstanceState) {
@@ -67,6 +82,7 @@
 
         Intent intent = getIntent();
         int subId = intent.getIntExtra(ARG_SUB_ID, SubscriptionManager.INVALID_SUBSCRIPTION_ID);
+        mTelMgr = getSystemService(TelephonyManager.class);
 
         UserManager userManager = getSystemService(UserManager.class);
         if (!userManager.isAdminUser()) {
@@ -82,13 +98,16 @@
         }
 
         mSubInfo = SubscriptionUtil.getSubById(mSubscriptionManager, subId);
+        mIsEsimOperation = mSubInfo != null && mSubInfo.isEmbedded();
         mSwitchToEuiccSubscriptionSidecar =
                 SwitchToEuiccSubscriptionSidecar.get(getFragmentManager());
+        mSwitchToRemovableSlotSidecar = SwitchToRemovableSlotSidecar.get(getFragmentManager());
+        mEnableMultiSimSidecar = EnableMultiSimSidecar.get(getFragmentManager());
         mEnable = intent.getBooleanExtra(ARG_enable, true);
 
         if (savedInstanceState == null) {
             if (mEnable) {
-                handleEnablingSubAction();
+                showEnableSubDialog();
             } else {
                 showDisableSimConfirmDialog();
             }
@@ -99,10 +118,14 @@
     protected void onResume() {
         super.onResume();
         mSwitchToEuiccSubscriptionSidecar.addListener(this);
+        mSwitchToRemovableSlotSidecar.addListener(this);
+        mEnableMultiSimSidecar.addListener(this);
     }
 
     @Override
     protected void onPause() {
+        mEnableMultiSimSidecar.removeListener(this);
+        mSwitchToRemovableSlotSidecar.removeListener(this);
         mSwitchToEuiccSubscriptionSidecar.removeListener(this);
         super.onPause();
     }
@@ -111,19 +134,25 @@
     public void onStateChange(SidecarFragment fragment) {
         if (fragment == mSwitchToEuiccSubscriptionSidecar) {
             handleSwitchToEuiccSubscriptionSidecarStateChange();
+        } else if (fragment == mSwitchToRemovableSlotSidecar) {
+            handleSwitchToRemovableSlotSidecarStateChange();
+        } else if (fragment == mEnableMultiSimSidecar) {
+            handleEnableMultiSimSidecarStateChange();
         }
     }
 
     @Override
     public void onConfirm(int tag, boolean confirmed) {
-        if (!confirmed) {
+        if (!confirmed
+                && tag != DIALOG_TAG_ENABLE_DSDS_CONFIRMATION
+                && tag != DIALOG_TAG_ENABLE_DSDS_REBOOT_CONFIRMATION) {
             finish();
             return;
         }
 
         switch (tag) {
             case DIALOG_TAG_DISABLE_SIM_CONFIRMATION:
-                if (mSubInfo.isEmbedded()) {
+                if (mIsEsimOperation) {
                     Log.i(TAG, "Disabling the eSIM profile.");
                     showProgressDialog(
                             getString(R.string.privileged_action_disable_sub_dialog_progress));
@@ -132,6 +161,51 @@
                     return;
                 }
                 Log.i(TAG, "Disabling the pSIM profile.");
+                handleTogglePsimAction();
+                break;
+            case DIALOG_TAG_ENABLE_DSDS_CONFIRMATION:
+                if (!confirmed) {
+                    Log.i(TAG, "User cancel the dialog to enable DSDS.");
+                    showEnableSimConfirmDialog();
+                    return;
+                }
+                if (mTelMgr.doesSwitchMultiSimConfigTriggerReboot()) {
+                    Log.i(TAG, "Device does not support reboot free DSDS.");
+                    showRebootConfirmDialog();
+                    return;
+                }
+                Log.i(
+                        TAG,
+                        "Enabling DSDS without rebooting. "
+                                + getString(R.string.sim_action_enabling_sim_without_carrier_name));
+                showProgressDialog(
+                        getString(R.string.sim_action_enabling_sim_without_carrier_name));
+                mEnableMultiSimSidecar.run(NUM_OF_SIMS_FOR_DSDS);
+                break;
+            case DIALOG_TAG_ENABLE_DSDS_REBOOT_CONFIRMATION:
+                if (!confirmed) {
+                    Log.i(TAG, "User cancel the dialog to reboot to enable DSDS.");
+                    showEnableSimConfirmDialog();
+                    return;
+                }
+                Log.i(TAG, "User confirmed reboot to enable DSDS.");
+                mTelMgr.switchMultiSimConfig(NUM_OF_SIMS_FOR_DSDS);
+                // TODO(b/170507290): Store a bit in preferences for displaying the notification
+                //  after the reboot.
+                break;
+            case DIALOG_TAG_ENABLE_SIM_CONFIRMATION:
+                Log.i(TAG, "User confirmed to enable the subscription.");
+                if (mIsEsimOperation) {
+                    showProgressDialog(
+                            getString(
+                                    R.string.sim_action_switch_sub_dialog_progress,
+                                    mSubInfo.getDisplayName()));
+                    mSwitchToEuiccSubscriptionSidecar.run(mSubInfo.getSubscriptionId());
+                    return;
+                }
+                showProgressDialog(
+                        getString(R.string.sim_action_enabling_sim_without_carrier_name));
+                mSwitchToRemovableSlotSidecar.run(UiccSlotUtil.INVALID_PHYSICAL_SLOT_ID);
                 break;
             default:
                 Log.e(TAG, "Unrecognized confirmation dialog tag: " + tag);
@@ -165,10 +239,105 @@
         }
     }
 
+    private void handleSwitchToRemovableSlotSidecarStateChange() {
+        switch (mSwitchToRemovableSlotSidecar.getState()) {
+            case SidecarFragment.State.SUCCESS:
+                Log.i(TAG, "Successfully switched to removable slot.");
+                mSwitchToRemovableSlotSidecar.reset();
+                handleTogglePsimAction();
+                dismissProgressDialog();
+                finish();
+                break;
+            case SidecarFragment.State.ERROR:
+                Log.e(TAG, "Failed switching to removable slot.");
+                mSwitchToRemovableSlotSidecar.reset();
+                dismissProgressDialog();
+                showErrorDialog(
+                        getString(R.string.sim_action_enable_sim_fail_title),
+                        getString(R.string.sim_action_enable_sim_fail_text));
+                break;
+        }
+    }
+
+    private void handleEnableMultiSimSidecarStateChange() {
+        switch (mEnableMultiSimSidecar.getState()) {
+            case SidecarFragment.State.SUCCESS:
+                mEnableMultiSimSidecar.reset();
+                Log.i(TAG, "Successfully switched to DSDS without reboot.");
+                handleEnableSubscriptionAfterEnablingDsds();
+                break;
+            case SidecarFragment.State.ERROR:
+                mEnableMultiSimSidecar.reset();
+                Log.i(TAG, "Failed to switch to DSDS without rebooting.");
+                ProgressDialogFragment.dismiss(getFragmentManager());
+                showErrorDialog(
+                        getString(R.string.dsds_activation_failure_title),
+                        getString(R.string.dsds_activation_failure_body_msg2));
+                break;
+        }
+    }
+
+    private void handleEnableSubscriptionAfterEnablingDsds() {
+        if (mIsEsimOperation) {
+            Log.i(TAG, "DSDS enabled, start to enable profile: " + mSubInfo.getSubscriptionId());
+            // For eSIM operations, we simply switch to the selected eSIM profile.
+            mSwitchToEuiccSubscriptionSidecar.run(mSubInfo.getSubscriptionId());
+            return;
+        }
+
+        Log.i(TAG, "DSDS enabled, start to enable pSIM profile.");
+        handleTogglePsimAction();
+        ProgressDialogFragment.dismiss(getFragmentManager());
+        finish();
+    }
+
+    private void handleTogglePsimAction() {
+        if (mSubscriptionManager.canDisablePhysicalSubscription() && mSubInfo != null) {
+            mSubscriptionManager.setUiccApplicationsEnabled(mSubInfo.getSubscriptionId(), mEnable);
+        } else {
+            Log.i(
+                    TAG,
+                    "The device does not support toggling pSIM. It is enough to just "
+                            + "enable the removable slot.");
+        }
+    }
+
     /* Handles the enabling SIM action. */
-    private void handleEnablingSubAction() {
-        Log.i(TAG, "handleEnableSub");
-        // TODO(b/160819390): Implement enabling eSIM/pSIM profile.
+    private void showEnableSubDialog() {
+        Log.i(TAG, "Handle subscription enabling.");
+        if (isDsdsConditionSatisfied()) {
+            showEnableDsdsConfirmDialog();
+            return;
+        }
+        if (!mIsEsimOperation && mTelMgr.isMultiSimEnabled()) {
+            Log.i(TAG, "Toggle on pSIM, no dialog displayed.");
+            handleTogglePsimAction();
+            finish();
+            return;
+        }
+        showEnableSimConfirmDialog();
+    }
+
+    private void showEnableDsdsConfirmDialog() {
+        ConfirmDialogFragment.show(
+                this,
+                ConfirmDialogFragment.OnConfirmListener.class,
+                DIALOG_TAG_ENABLE_DSDS_CONFIRMATION,
+                getString(R.string.sim_action_enable_dsds_title),
+                getString(R.string.sim_action_enable_dsds_text),
+                getString(R.string.sim_action_continue),
+                getString(R.string.sim_action_no_thanks));
+    }
+
+    private void showRebootConfirmDialog() {
+        ConfirmDialogFragment.show(
+                this,
+                ConfirmDialogFragment.OnConfirmListener.class,
+                DIALOG_TAG_ENABLE_DSDS_REBOOT_CONFIRMATION,
+                getString(R.string.sim_action_restart_title),
+                getString(R.string.sim_action_enable_dsds_text),
+                getString(R.string.sim_action_reboot),
+                getString(R.string.cancel));
     }
 
     /* Displays the SIM toggling confirmation dialog. */
@@ -191,10 +360,116 @@
                 getString(R.string.cancel));
     }
 
-    /* Dismisses the SIM toggling confirmation dialog. */
-    private void dismissToggleSimConfirmDialog() {
-        if (mToggleSimConfirmDialog != null) {
-            mToggleSimConfirmDialog.dismiss();
+    private void showEnableSimConfirmDialog() {
+        List<SubscriptionInfo> activeSubs =
+                SubscriptionUtil.getActiveSubscriptions(mSubscriptionManager);
+        SubscriptionInfo activeSub = activeSubs.isEmpty() ? null : activeSubs.get(0);
+        if (activeSub == null) {
+            Log.i(TAG, "No active subscriptions available.");
+            showNonSwitchSimConfirmDialog();
+            return;
         }
+        Log.i(TAG, "Found active subscription.");
+        boolean isBetweenEsim = mIsEsimOperation && activeSub.isEmbedded();
+        if (mTelMgr.isMultiSimEnabled() && !isBetweenEsim) {
+            showNonSwitchSimConfirmDialog();
+            return;
+        }
+        ConfirmDialogFragment.show(
+                this,
+                ConfirmDialogFragment.OnConfirmListener.class,
+                DIALOG_TAG_ENABLE_SIM_CONFIRMATION,
+                getSwitchSubscriptionTitle(),
+                getSwitchDialogBodyMsg(activeSub, isBetweenEsim),
+                getSwitchDialogPosBtnText(),
+                getString(android.R.string.cancel));
+    }
+
+    private void showNonSwitchSimConfirmDialog() {
+        ConfirmDialogFragment.show(
+                this,
+                ConfirmDialogFragment.OnConfirmListener.class,
+                DIALOG_TAG_ENABLE_SIM_CONFIRMATION,
+                getEnableSubscriptionTitle(),
+                null /* msg */,
+                getString(R.string.yes),
+                getString(android.R.string.cancel));
+    }
+
+    private String getSwitchDialogPosBtnText() {
+        return mIsEsimOperation
+                ? getString(
+                        R.string.sim_action_switch_sub_dialog_confirm, mSubInfo.getDisplayName())
+                : getString(R.string.sim_switch_button);
+    }
+
+    private String getEnableSubscriptionTitle() {
+        if (mSubInfo == null || TextUtils.isEmpty(mSubInfo.getDisplayName())) {
+            return getString(R.string.sim_action_enable_sub_dialog_title_without_carrier_name);
+        }
+        return getString(R.string.sim_action_enable_sub_dialog_title, mSubInfo.getDisplayName());
+    }
+
+    private String getSwitchSubscriptionTitle() {
+        if (mIsEsimOperation) {
+            return getString(
+                    R.string.sim_action_switch_sub_dialog_title, mSubInfo.getDisplayName());
+        }
+        return getString(R.string.sim_action_switch_psim_dialog_title);
+    }
+
+    private String getSwitchDialogBodyMsg(SubscriptionInfo activeSub, boolean betweenEsim) {
+        if (betweenEsim && mIsEsimOperation) {
+            return getString(
+                    R.string.sim_action_switch_sub_dialog_text_downloaded,
+                    mSubInfo.getDisplayName(),
+                    activeSub.getDisplayName());
+        } else if (mIsEsimOperation) {
+            return getString(
+                    R.string.sim_action_switch_sub_dialog_text,
+                    mSubInfo.getDisplayName(),
+                    activeSub.getDisplayName());
+        } else {
+            return getString(
+                    R.string.sim_action_switch_sub_dialog_text_single_sim,
+                    activeSub.getDisplayName());
+        }
+    }
+
+    private boolean isDsdsConditionSatisfied() {
+        if (mTelMgr.isMultiSimEnabled()) {
+            Log.i(TAG, "DSDS is already enabled. Condition not satisfied.");
+            return false;
+        }
+        if (mTelMgr.isMultiSimSupported() != TelephonyManager.MULTISIM_ALLOWED) {
+            Log.i(TAG, "Hardware does not support DSDS.");
+            return false;
+        }
+        ImmutableList<UiccSlotInfo> slotInfos = UiccSlotUtil.getSlotInfos(mTelMgr);
+        boolean isRemovableSimEnabled =
+                slotInfos.stream()
+                        .anyMatch(
+                                slot ->
+                                        slot != null
+                                                && slot.isRemovable()
+                                                && slot.getIsActive()
+                                                && slot.getCardStateInfo()
+                                                        == UiccSlotInfo.CARD_STATE_INFO_PRESENT);
+        if (mIsEsimOperation && isRemovableSimEnabled) {
+            Log.i(TAG, "eSIM operation and removable SIM is enabled. DSDS condition satisfied.");
+            return true;
+        }
+        boolean isEsimProfileEnabled =
+                SubscriptionUtil.getActiveSubscriptions(mSubscriptionManager).stream()
+                        .anyMatch(SubscriptionInfo::isEmbedded);
+        if (!mIsEsimOperation && isEsimProfileEnabled) {
+            Log.i(
+                    TAG,
+                    "Removable SIM operation and eSIM profile is enabled. DSDS condition"
+                        + " satisfied.");
+            return true;
+        }
+        Log.i(TAG, "DSDS condition not satisfied.");
+        return false;
     }
 }