Slot Change Receiver Migration

Implemented slot change cases when pSIM is inserted and removed.
Bug: 153811431
Bug: 170508680
Test: Manually tested

Change-Id: Ib0a96da1d7d702f7c64e75b929c73b8548f8e459
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 8b3160f..8aabdb7 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -3669,6 +3669,26 @@
             </intent-filter>
         </receiver>
 
+        <activity
+            android:name=".sim.ChooseSimActivity"
+            android:theme="@style/GlifV3Theme.DayNight.NoActionBar"
+            android:launchMode="singleInstance"
+            android:exported="false"/>
+
+        <activity
+            android:name=".sim.SwitchToEsimConfirmDialogActivity"
+            android:exported="false"
+            android:permission="android.permission.WRITE_EMBEDDED_SUBSCRIPTIONS"
+            android:launchMode="singleInstance"
+            android:theme="@style/Transparent" />
+
+        <activity
+            android:name=".sim.DsdsDialogActivity"
+            android:exported="false"
+            android:permission="android.permission.WRITE_EMBEDDED_SUBSCRIPTIONS"
+            android:launchMode="singleInstance"
+            android:theme="@style/Transparent" />
+
         <service android:name=".sim.SimNotificationService"
                  android:permission="android.permission.BIND_JOB_SERVICE" />
 
diff --git a/res/drawable/ic_network_signal_blue.xml b/res/drawable/ic_network_signal_blue.xml
new file mode 100644
index 0000000..3d7f63c
--- /dev/null
+++ b/res/drawable/ic_network_signal_blue.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2021 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.
+-->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="32dp"
+        android:height="32dp"
+        android:viewportWidth="32"
+        android:viewportHeight="32">
+
+    <path
+        android:pathData="M 0 0 H 32 V 32 H 0 V 0 Z" />
+    <path
+        android:fillColor="@color/homepage_generic_icon_background"
+        android:pathData="M24,5.33h1.33c0.74,0,1.33,0.6,1.33,1.33v18.67c0,0.74-0.6,1.33-1.33,1.33H24c-0.74,0-1.33-0.6-1.33-1.33
+V6.67C22.67,5.93,23.26,5.33,24,5.33z" />
+    <path
+        android:fillColor="@color/homepage_generic_icon_background"
+        android:pathData="M8,18.67h1.33c0.74,0,1.33,0.6,1.33,1.33v5.33c0,0.74-0.6,1.33-1.33,1.33H8c-0.74,0-1.33-0.6-1.33-1.33V20
+C6.67,19.26,7.26,18.67,8,18.67z" />
+    <path
+        android:fillColor="@color/homepage_generic_icon_background"
+        android:pathData="M16,12h1.33c0.74,0,1.33,0.6,1.33,1.33v12c0,0.74-0.6,1.33-1.33,1.33H16c-0.74,0-1.33-0.6-1.33-1.33v-12
+C14.67,12.6,15.26,12,16,12z" />
+</vector>
diff --git a/res/layout/choose_sim_activity.xml b/res/layout/choose_sim_activity.xml
new file mode 100644
index 0000000..05ab230
--- /dev/null
+++ b/res/layout/choose_sim_activity.xml
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2021 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.
+-->
+
+<com.google.android.setupdesign.GlifLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/glif_layout"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:icon="@drawable/ic_network_signal_blue">
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="vertical">
+
+        <LinearLayout
+            style="@style/SudContentFrame"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:paddingBottom="@dimen/subtitle_bottom_padding">
+            <TextView
+                android:id="@+id/subtitle"
+                style="@style/SudDescription.Glif"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content" />
+        </LinearLayout>
+
+        <com.google.android.setupdesign.GlifRecyclerLayout
+            android:id="@+id/recycler_list"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:entries="@xml/items_multiple_carrier" />
+
+    </LinearLayout>
+</com.google.android.setupdesign.GlifLayout>
diff --git a/res/values-night/themes_suw.xml b/res/values-night/themes_suw.xml
index 16aba76..dea7b1a 100644
--- a/res/values-night/themes_suw.xml
+++ b/res/values-night/themes_suw.xml
@@ -21,6 +21,7 @@
     <style name="GlifTheme.DayNight" parent="GlifTheme" />
     <style name="GlifV2Theme.DayNight" parent="GlifV2Theme" />
     <style name="GlifV3Theme.DayNight" parent="GlifV3Theme" />
+    <style name="GlifV3Theme.DayNight.NoActionBar" parent="GlifV3Theme.NoActionBar" />
     <style name="GlifV2Theme.DayNight.Transparent" parent="GlifV2Theme.Transparent" />
     <style name="GlifV3Theme.DayNight.Transparent" parent="GlifV3Theme.Transparent" />
     <style name="SetupWizardTheme.DayNight.Transparent" parent="SetupWizardTheme.Transparent" />
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 8f6be68..ab41aca 100755
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -440,4 +440,7 @@
 
     <!-- Text padding for EmptyTextSettings -->
     <dimen name="empty_text_padding">24dp</dimen>
+
+    <!--  Choose SIM Activity dimens  -->
+    <dimen name="subtitle_bottom_padding">24dp</dimen>
 </resources>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index dda89ee..65c514a 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -12129,6 +12129,34 @@
     <string name="post_dsds_reboot_notification_title_with_carrier"><xliff:g id="carrier_name" example="Google Fi">%1$s</xliff:g> is active</string>
     <!--  The body text of post DSDS reboot notification. [CHAR LIMIT=NONE] -->
     <string name="post_dsds_reboot_notification_text">Tap to update SIM settings</string>
+    <!-- Title on a push notification indicating that the user's device switched to a new mobile network. [CHAR LIMIT=NONE] -->
+    <string name="switch_to_removable_notification">Switched to <xliff:g id="carrier_name" example="Google Fi">%1$s</xliff:g></string>
+    <!-- Title on a push notification indicating that the user's device switched to a new mobile network. [CHAR LIMIT=NONE] -->
+    <string name="switch_to_removable_notification_no_carrier_name">Switched to another carrier</string>
+    <!-- Message in a push notification indicating that the user's phone has connected to a different mobile network. [CHAR LIMIT=NONE] -->
+    <string name="network_changed_notification_text">Your mobile network has changed</string>
+
+    <!-- Strings for choose SIM activity -->
+    <!--  The title text of choose SIM activity. [CHAR LIMIT=NONE] -->
+    <string name="choose_sim_title">Choose a number to use</string>
+    <!--  The body text of choose SIM activity. [CHAR LIMIT=NONE] -->
+    <string name="choose_sim_text"><xliff:g id="number" example="2">%1$d</xliff:g> numbers are available on this device, but only one can be used at a time</string>
+    <!-- String indicating that we are activating the profile [CHAR LIMIT=NONE] -->
+    <string name="choose_sim_activating">Activating<xliff:g id="ellipsis" example="...">&#8230;</xliff:g></string>
+    <!-- String indicating that we failed to activate the selected profile [CHAR LIMIT=NONE] -->
+    <string name="choose_sim_could_not_activate">Couldn\u2019t be activated right now</string>
+    <!-- String indicating that the number for the specified profile is unknown [CHAR LIMIT=NONE] -->
+    <string name="choose_sim_item_summary_unknown">Unknown number</string>
+
+    <!-- Strings for switch SIM confirmation dialog. -->
+    <!--  The title text of switch SIM confirmation dialog. [CHAR LIMIT=NONE] -->
+    <string name="switch_sim_dialog_title">Use <xliff:g id="carrier_name" example="Google Fi">%1$s</xliff:g>?</string>
+    <!--  The body text of switch SIM confirmation dialog. [CHAR LIMIT=NONE] -->
+    <string name="switch_sim_dialog_text"><xliff:g id="carrier_name" example="Google Fi">%1$s</xliff:g> will be used for mobile data, calls, and SMS.</string>
+    <!--  The title text of skip sim switch dialog. [CHAR LIMIT=NONE] -->
+    <string name="switch_sim_dialog_no_switch_title">No active SIMs available</string>
+    <!--  The body text of skip sim switch dialog. [CHAR LIMIT=NONE] -->
+    <string name="switch_sim_dialog_no_switch_text">To use mobile data, call features, and SMS at a later time, go to your network settings</string>
 
     <!-- Button label of the removable sim card. [CHAR LIMIT=NONE] -->
     <string name="sim_card_label">SIM card</string>
diff --git a/res/values/themes_suw.xml b/res/values/themes_suw.xml
index 959ffb3..b286a51 100644
--- a/res/values/themes_suw.xml
+++ b/res/values/themes_suw.xml
@@ -133,6 +133,14 @@
         <item name="*android:lockPatternStyle">@style/LockPatternStyle.Setup</item>
     </style>
 
+    <style name="GlifV3Theme.Light.NoActionBar" parent="GlifV3Theme.Light">
+        <item name="android:windowActionBar">false</item>
+    </style>
+
+    <style name="GlifV3Theme.NoActionBar" parent="GlifV3Theme">
+        <item name="android:windowActionBar">false</item>
+    </style>
+
     <style name="GlifV2Theme.Transparent">
         <item name="android:windowBackground">@android:color/transparent</item>
         <item name="android:windowNoTitle">true</item>
@@ -216,6 +224,7 @@
     <style name="GlifTheme.DayNight" parent="GlifTheme.Light" />
     <style name="GlifV2Theme.DayNight" parent="GlifV2Theme.Light" />
     <style name="GlifV3Theme.DayNight" parent="GlifV3Theme.Light" />
+    <style name="GlifV3Theme.DayNight.NoActionBar" parent="GlifV3Theme.Light.NoActionBar" />
     <style name="GlifV2Theme.DayNight.Transparent" parent="GlifV2Theme.Light.Transparent" />
     <style name="GlifV3Theme.DayNight.Transparent" parent="GlifV3Theme.Light.Transparent" />
     <style name="SetupWizardTheme.DayNight.Transparent" parent="SetupWizardTheme.Light.Transparent" />
diff --git a/res/xml/items_multiple_carrier.xml b/res/xml/items_multiple_carrier.xml
new file mode 100644
index 0000000..b65e0cb
--- /dev/null
+++ b/res/xml/items_multiple_carrier.xml
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2021 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.
+-->
+<ItemGroup xmlns:android="http://schemas.android.com/apk/res/android" />
diff --git a/src/com/android/settings/network/SubscriptionUtil.java b/src/com/android/settings/network/SubscriptionUtil.java
index ff5de3e..ba0ae99 100644
--- a/src/com/android/settings/network/SubscriptionUtil.java
+++ b/src/com/android/settings/network/SubscriptionUtil.java
@@ -24,15 +24,18 @@
 import android.annotation.Nullable;
 import android.content.Context;
 import android.os.ParcelUuid;
+import android.telephony.PhoneNumberUtils;
 import android.telephony.SubscriptionInfo;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
+import android.telephony.UiccCardInfo;
 import android.telephony.UiccSlotInfo;
 import android.text.TextUtils;
 import android.util.Log;
 
 import androidx.annotation.VisibleForTesting;
 
+import com.android.internal.telephony.MccTable;
 import com.android.settings.network.telephony.DeleteEuiccSubscriptionDialogActivity;
 import com.android.settings.network.telephony.ToggleSubscriptionDialogActivity;
 import com.android.settingslib.DeviceInfoUtils;
@@ -514,4 +517,64 @@
                 .filter(sub -> sub.isEmbedded() && groupUuid.equals(sub.getGroupUuid()))
                 .collect(Collectors.toList());
     }
+
+    /** Returns the formatted phone number of a subscription. */
+    @Nullable
+    public static String getFormattedPhoneNumber(
+            Context context, SubscriptionInfo subscriptionInfo) {
+        if (subscriptionInfo == null) {
+            Log.e(TAG, "Invalid subscription.");
+            return null;
+        }
+
+        TelephonyManager telephonyManager = context.getSystemService(TelephonyManager.class);
+        String rawPhoneNumber =
+                telephonyManager.getLine1Number(subscriptionInfo.getSubscriptionId());
+        String countryIso = MccTable.countryCodeForMcc(subscriptionInfo.getMccString());
+        if (TextUtils.isEmpty(rawPhoneNumber)) {
+            return null;
+        }
+        return PhoneNumberUtils.formatNumber(rawPhoneNumber, countryIso);
+    }
+
+    /**
+     * Returns the subscription on a removable sim card. The device does not need to be on removable
+     * slot.
+     */
+    @Nullable
+    public static SubscriptionInfo getFirstRemovableSubscription(Context context) {
+        TelephonyManager telephonyManager = context.getSystemService(TelephonyManager.class);
+        SubscriptionManager subscriptionManager =
+                context.getSystemService(SubscriptionManager.class);
+        List<UiccCardInfo> cardInfos = telephonyManager.getUiccCardsInfo();
+        if (cardInfos == null) {
+            Log.w(TAG, "UICC cards info list is empty.");
+            return null;
+        }
+        List<SubscriptionInfo> allSubscriptions = subscriptionManager.getAllSubscriptionInfoList();
+        if (allSubscriptions == null) {
+            Log.w(TAG, "All subscription info list is empty.");
+            return null;
+        }
+        for (UiccCardInfo cardInfo : cardInfos) {
+            if (cardInfo == null) {
+                Log.w(TAG, "Got null card.");
+                continue;
+            }
+            if (!cardInfo.isRemovable()
+                    || cardInfo.getCardId() == TelephonyManager.UNSUPPORTED_CARD_ID) {
+                Log.i(TAG, "Skip embedded card or invalid cardId on slot: "
+                        + cardInfo.getSlotIndex());
+                continue;
+            }
+            Log.i(TAG, "Target removable cardId :" + cardInfo.getCardId());
+            for (SubscriptionInfo subInfo : allSubscriptions) {
+                // Match the removable card id with subscription card id.
+                if (cardInfo.getCardId() == subInfo.getCardId()) {
+                    return subInfo;
+                }
+            }
+        }
+        return null;
+    }
 }
diff --git a/src/com/android/settings/network/telephony/ToggleSubscriptionDialogActivity.java b/src/com/android/settings/network/telephony/ToggleSubscriptionDialogActivity.java
index c1be63c..d3b2af6 100644
--- a/src/com/android/settings/network/telephony/ToggleSubscriptionDialogActivity.java
+++ b/src/com/android/settings/network/telephony/ToggleSubscriptionDialogActivity.java
@@ -177,10 +177,7 @@
                     showRebootConfirmDialog();
                     return;
                 }
-                Log.i(
-                        TAG,
-                        "Enabling DSDS without rebooting. "
-                                + getString(R.string.sim_action_enabling_sim_without_carrier_name));
+                Log.i(TAG, "Enabling DSDS without rebooting.");
                 showProgressDialog(
                         getString(R.string.sim_action_enabling_sim_without_carrier_name));
                 mEnableMultiSimSidecar.run(NUM_OF_SIMS_FOR_DSDS);
@@ -272,7 +269,7 @@
             case SidecarFragment.State.ERROR:
                 mEnableMultiSimSidecar.reset();
                 Log.i(TAG, "Failed to switch to DSDS without rebooting.");
-                ProgressDialogFragment.dismiss(getFragmentManager());
+                dismissProgressDialog();
                 showErrorDialog(
                         getString(R.string.dsds_activation_failure_title),
                         getString(R.string.dsds_activation_failure_body_msg2));
@@ -290,7 +287,7 @@
 
         Log.i(TAG, "DSDS enabled, start to enable pSIM profile.");
         handleTogglePsimAction();
-        ProgressDialogFragment.dismiss(getFragmentManager());
+        dismissProgressDialog();
         finish();
     }
 
diff --git a/src/com/android/settings/sim/ChooseSimActivity.java b/src/com/android/settings/sim/ChooseSimActivity.java
new file mode 100644
index 0000000..f8bdc30
--- /dev/null
+++ b/src/com/android/settings/sim/ChooseSimActivity.java
@@ -0,0 +1,321 @@
+/*
+ * Copyright (C) 2021 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.sim;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.telephony.SubscriptionInfo;
+import android.telephony.SubscriptionManager;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.View;
+import android.widget.TextView;
+
+import com.android.settings.R;
+import com.android.settings.SidecarFragment;
+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.android.setupdesign.GlifLayout;
+import com.google.android.setupdesign.GlifRecyclerLayout;
+import com.google.android.setupdesign.items.Dividable;
+import com.google.android.setupdesign.items.IItem;
+import com.google.android.setupdesign.items.Item;
+import com.google.android.setupdesign.items.ItemGroup;
+import com.google.android.setupdesign.items.RecyclerItemAdapter;
+import com.google.android.setupdesign.view.HeaderRecyclerView;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/** Activity to show a list of profiles for user to choose. */
+public class ChooseSimActivity extends Activity
+        implements RecyclerItemAdapter.OnItemSelectedListener, SidecarFragment.Listener {
+    // Whether there is a pSIM profile in the selection list.
+    public static final String KEY_HAS_PSIM = "has_psim";
+    // After the user selects eSIM profile, whether continue to show Mobile Network Settings screen
+    // to select other preferences.
+    // Note: KEY_NO_PSIM_CONTINUE_TO_SETTINGS and mNoPsimContinueToSettings are not used for now
+    // for UI changes. We may use them in the future.
+    public static final String KEY_NO_PSIM_CONTINUE_TO_SETTINGS = "no_psim_continue_to_settings";
+
+    private static final String TAG = "ChooseSimActivity";
+    private static final int INDEX_PSIM = -1;
+    private static final String STATE_SELECTED_INDEX = "selected_index";
+    private static final String STATE_IS_SWITCHING = "is_switching";
+
+    private boolean mHasPsim;
+    private boolean mNoPsimContinueToSettings;
+    private ArrayList<SubscriptionInfo> mEmbeddedSubscriptions = new ArrayList<>();
+    private SubscriptionInfo mRemovableSubscription = null;
+
+    private ItemGroup mItemGroup;
+    private SwitchToEuiccSubscriptionSidecar mSwitchToEuiccSubscriptionSidecar;
+    private SwitchToRemovableSlotSidecar mSwitchToRemovableSlotSidecar;
+
+    // Variables have states.
+    private int mSelectedItemIndex;
+    private boolean mIsSwitching;
+
+    /** Returns an intent of {@code ChooseSimActivity} */
+    public static Intent getIntent(Context context) {
+        return new Intent(context, ChooseSimActivity.class);
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        setContentView(R.layout.choose_sim_activity);
+
+        Intent intent = getIntent();
+        mHasPsim = intent.getBooleanExtra(KEY_HAS_PSIM, false);
+        mNoPsimContinueToSettings = intent.getBooleanExtra(KEY_NO_PSIM_CONTINUE_TO_SETTINGS, false);
+
+        updateSubscriptions();
+
+        if (mEmbeddedSubscriptions.size() == 0) {
+            Log.e(TAG, "Unable to find available eSIM subscriptions.");
+            finish();
+            return;
+        }
+
+        if (savedInstanceState != null) {
+            mSelectedItemIndex = savedInstanceState.getInt(STATE_SELECTED_INDEX);
+            mIsSwitching = savedInstanceState.getBoolean(STATE_IS_SWITCHING);
+        }
+
+        GlifLayout layout = findViewById(R.id.glif_layout);
+        TextView textView = findViewById(R.id.subtitle);
+        int subscriptionCount = mEmbeddedSubscriptions.size();
+        if (mHasPsim) { // Choose a number to use
+            subscriptionCount++;
+        }
+        layout.setHeaderText(getString(R.string.choose_sim_title));
+        textView.setText(getString(R.string.choose_sim_text, subscriptionCount));
+
+        displaySubscriptions();
+
+        mSwitchToRemovableSlotSidecar = SwitchToRemovableSlotSidecar.get(getFragmentManager());
+        mSwitchToEuiccSubscriptionSidecar =
+                SwitchToEuiccSubscriptionSidecar.get(getFragmentManager());
+    }
+
+    @Override
+    public void onResume() {
+        super.onResume();
+        mSwitchToRemovableSlotSidecar.addListener(this);
+        mSwitchToEuiccSubscriptionSidecar.addListener(this);
+    }
+
+    @Override
+    public void onPause() {
+        mSwitchToEuiccSubscriptionSidecar.removeListener(this);
+        mSwitchToRemovableSlotSidecar.removeListener(this);
+        super.onPause();
+    }
+
+    @Override
+    protected void onSaveInstanceState(Bundle outState) {
+        outState.putInt(STATE_SELECTED_INDEX, mSelectedItemIndex);
+        outState.putBoolean(STATE_IS_SWITCHING, mIsSwitching);
+        super.onSaveInstanceState(outState);
+    }
+
+    @Override
+    public void onItemSelected(IItem item) {
+        if (mIsSwitching) {
+            // If we already selected an item, do not try to switch to another one.
+            return;
+        }
+        mIsSwitching = true;
+        Item subItem = (Item) item;
+        subItem.setSummary(getString(R.string.choose_sim_activating));
+        mSelectedItemIndex = subItem.getId();
+        if (mSelectedItemIndex == INDEX_PSIM) {
+            Log.i(TAG, "Ready to switch to pSIM slot.");
+            mSwitchToRemovableSlotSidecar.run(UiccSlotUtil.INVALID_PHYSICAL_SLOT_ID);
+        } else {
+            Log.i(TAG, "Ready to switch to eSIM subscription with index: " + mSelectedItemIndex);
+            mSwitchToEuiccSubscriptionSidecar.run(
+                    mEmbeddedSubscriptions.get(mSelectedItemIndex).getSubscriptionId());
+        }
+    }
+
+    @Override
+    public void onStateChange(SidecarFragment fragment) {
+        if (fragment == mSwitchToRemovableSlotSidecar) {
+            switch (mSwitchToRemovableSlotSidecar.getState()) {
+                case SidecarFragment.State.SUCCESS:
+                    mSwitchToRemovableSlotSidecar.reset();
+                    Log.i(TAG, "Switch slot successfully.");
+                    SubscriptionManager subMgr = getSystemService(SubscriptionManager.class);
+                    if (subMgr.canDisablePhysicalSubscription()) {
+                        SubscriptionInfo removableSub =
+                                SubscriptionUtil.getFirstRemovableSubscription(this);
+                        if (removableSub != null) {
+                            subMgr.setUiccApplicationsEnabled(
+                                    removableSub.getSubscriptionId(), true);
+                        }
+                    }
+                    finish();
+                    break;
+                case SidecarFragment.State.ERROR:
+                    mSwitchToRemovableSlotSidecar.reset();
+                    Log.e(TAG, "Failed to switch slot in ChooseSubscriptionsActivity.");
+                    handleEnableRemovableSimError();
+                    // We don't call finish() and just stay on this page.
+                    break;
+            }
+        } else if (fragment == mSwitchToEuiccSubscriptionSidecar) {
+            switch (mSwitchToEuiccSubscriptionSidecar.getState()) {
+                case SidecarFragment.State.SUCCESS:
+                    mSwitchToEuiccSubscriptionSidecar.reset();
+                    if (mNoPsimContinueToSettings) {
+                        // Currently, there shouldn't be a case that mNoPsimContinueToSettings is
+                        // true. If this can be true in the future, we should finish() this page
+                        // and direct to Settings page here.
+                        Log.e(
+                                TAG,
+                                "mNoPsimContinueToSettings is true which is not supported for"
+                                        + " now.");
+                    } else {
+                        Log.i(TAG, "User finished selecting eSIM profile.");
+                        finish();
+                    }
+                    break;
+                case SidecarFragment.State.ERROR:
+                    mSwitchToEuiccSubscriptionSidecar.reset();
+                    Log.e(TAG, "Failed to switch subscription in ChooseSubscriptionsActivity.");
+                    Item item = (Item) mItemGroup.getItemAt(mSelectedItemIndex);
+                    item.setEnabled(false);
+                    item.setSummary(getString(R.string.choose_sim_could_not_activate));
+                    mIsSwitching = false;
+                    // We don't call finish() and just stay on this page.
+                    break;
+            }
+        }
+    }
+
+    private void displaySubscriptions() {
+        View rootView = findViewById(android.R.id.content);
+        GlifRecyclerLayout layout = rootView.findViewById(R.id.recycler_list);
+        RecyclerItemAdapter adapter = (RecyclerItemAdapter) layout.getAdapter();
+        adapter.setOnItemSelectedListener(this);
+        mItemGroup = (ItemGroup) adapter.getRootItemHierarchy();
+
+        // Display pSIM profile.
+        if (mHasPsim) {
+            Item item = new DisableableItem();
+            // Title
+            CharSequence title = null;
+            if (mRemovableSubscription != null) {
+                title =
+                        SubscriptionUtil.getUniqueSubscriptionDisplayName(
+                                mRemovableSubscription.getSubscriptionId(), this);
+            }
+            item.setTitle(TextUtils.isEmpty(title) ? getString(R.string.sim_card_label) : title);
+
+            if (mIsSwitching && mSelectedItemIndex == INDEX_PSIM) {
+                item.setSummary(getString(R.string.choose_sim_activating));
+            } else {
+                // Phone number
+                String phoneNumber =
+                        SubscriptionUtil.getFormattedPhoneNumber(this, mRemovableSubscription);
+                item.setSummary(TextUtils.isEmpty(phoneNumber) ? "" : phoneNumber);
+            }
+
+            // pSIM profile has index -1.
+            item.setId(INDEX_PSIM);
+            mItemGroup.addChild(item);
+        }
+
+        // Display all eSIM profiles.
+        int index = 0;
+        for (SubscriptionInfo sub : mEmbeddedSubscriptions) {
+            Item item = new DisableableItem();
+            CharSequence title =
+                    SubscriptionUtil.getUniqueSubscriptionDisplayName(
+                            sub.getSubscriptionId(), this);
+            item.setTitle(TextUtils.isEmpty(title) ? sub.getDisplayName() : title);
+            if (mIsSwitching && mSelectedItemIndex == index) {
+                item.setSummary(getString(R.string.choose_sim_activating));
+            } else {
+                String phoneNumber = SubscriptionUtil.getFormattedPhoneNumber(this, sub);
+                item.setSummary(TextUtils.isEmpty(phoneNumber) ? "" : phoneNumber);
+            }
+            item.setId(index++);
+            mItemGroup.addChild(item);
+        }
+
+        // This removes the unused header artifact from GlifRecyclerLayout.
+        HeaderRecyclerView rv = (HeaderRecyclerView) layout.getRecyclerView();
+        rv.getHeader().setVisibility(View.GONE);
+    }
+
+    private void updateSubscriptions() {
+        List<SubscriptionInfo> subscriptions =
+                SubscriptionUtil.getSelectableSubscriptionInfoList(this);
+        if (subscriptions != null) {
+            for (SubscriptionInfo sub : subscriptions) {
+                if (sub == null) {
+                    continue;
+                }
+                if (sub.isEmbedded()) {
+                    mEmbeddedSubscriptions.add(sub);
+                } else {
+                    mRemovableSubscription = sub;
+                }
+            }
+        }
+    }
+
+    private void handleEnableRemovableSimError() {
+        // mSelectedItemIndex will be -1 if pSIM is selected. Since pSIM is always be
+        // listed at index 0, we change the itemIndex to 0 if pSIM is selected.
+        int itemIndex = mSelectedItemIndex == INDEX_PSIM ? 0 : mSelectedItemIndex;
+        Item item = (Item) mItemGroup.getItemAt(itemIndex);
+        item.setEnabled(false);
+        item.setSummary(getString(R.string.choose_sim_could_not_activate));
+        mIsSwitching = false;
+    }
+
+    class DisableableItem extends Item implements Dividable {
+        @Override
+        public boolean isDividerAllowedAbove() {
+            return true;
+        }
+
+        @Override
+        public boolean isDividerAllowedBelow() {
+            return true;
+        }
+
+        @Override
+        public void onBindView(View view) {
+            super.onBindView(view);
+            TextView title = view.findViewById(R.id.sud_items_title);
+            TextView summary = view.findViewById(R.id.sud_items_summary);
+            title.setEnabled(isEnabled());
+            summary.setEnabled(isEnabled());
+        }
+    }
+}
diff --git a/src/com/android/settings/sim/DsdsDialogActivity.java b/src/com/android/settings/sim/DsdsDialogActivity.java
new file mode 100644
index 0000000..1390c81
--- /dev/null
+++ b/src/com/android/settings/sim/DsdsDialogActivity.java
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2021 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.sim;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.telephony.TelephonyManager;
+import android.util.Log;
+
+import com.android.settings.R;
+import com.android.settings.SidecarFragment;
+import com.android.settings.network.EnableMultiSimSidecar;
+import com.android.settings.network.telephony.ConfirmDialogFragment;
+import com.android.settings.network.telephony.SubscriptionActionDialogActivity;
+
+/** Activity to show the enabling DSDS dialog. */
+public class DsdsDialogActivity extends SubscriptionActionDialogActivity
+        implements SidecarFragment.Listener, ConfirmDialogFragment.OnConfirmListener {
+
+    private static final String TAG = "DsdsDialogActivity";
+    // Dialog tags
+    private static final int DIALOG_TAG_ENABLE_DSDS_CONFIRMATION = 1;
+    private static final int DIALOG_TAG_ENABLE_DSDS_REBOOT_CONFIRMATION = 2;
+    // Number of SIMs for DSDS
+    private static final int NUM_OF_SIMS_FOR_DSDS = 2;
+
+    private EnableMultiSimSidecar mEnableMultiSimSidecar;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        mEnableMultiSimSidecar = EnableMultiSimSidecar.get(getFragmentManager());
+        if (savedInstanceState == null) {
+            showEnableDsdsConfirmDialog();
+        }
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+        mEnableMultiSimSidecar.addListener(this);
+    }
+
+    @Override
+    protected void onPause() {
+        mEnableMultiSimSidecar.removeListener(this);
+        super.onPause();
+    }
+
+    @Override
+    public void onStateChange(SidecarFragment fragment) {
+        if (fragment == mEnableMultiSimSidecar) {
+            switch (fragment.getState()) {
+                case SidecarFragment.State.SUCCESS:
+                    mEnableMultiSimSidecar.reset();
+                    Log.i(TAG, "Enabled DSDS successfully");
+                    dismissProgressDialog();
+                    finish();
+                    break;
+                case SidecarFragment.State.ERROR:
+                    mEnableMultiSimSidecar.reset();
+                    Log.e(TAG, "Failed to enable DSDS");
+                    dismissProgressDialog();
+                    showErrorDialog(
+                            getString(R.string.dsds_activation_failure_title),
+                            getString(R.string.dsds_activation_failure_body_msg2));
+                    break;
+            }
+        }
+    }
+
+    @Override
+    public void onConfirm(int tag, boolean confirmed) {
+        if (!confirmed) {
+            Log.i(TAG, "User cancel the dialog to enable DSDS.");
+            startChooseSimActivity();
+            return;
+        }
+
+        TelephonyManager telephonyManager = getSystemService(TelephonyManager.class);
+        switch (tag) {
+            case DIALOG_TAG_ENABLE_DSDS_CONFIRMATION:
+                if (telephonyManager.doesSwitchMultiSimConfigTriggerReboot()) {
+                    Log.i(TAG, "Device does not support reboot free DSDS.");
+                    showRebootConfirmDialog();
+                    return;
+                }
+                Log.i(TAG, "Enabling DSDS without rebooting.");
+                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:
+                Log.i(TAG, "User confirmed reboot to enable DSDS.");
+                SimActivationNotifier.setShowSimSettingsNotification(this, true);
+                telephonyManager.switchMultiSimConfig(NUM_OF_SIMS_FOR_DSDS);
+                break;
+            default:
+                Log.e(TAG, "Unrecognized confirmation dialog tag: " + tag);
+                break;
+        }
+    }
+
+    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));
+    }
+
+    private void startChooseSimActivity() {
+        Intent intent = ChooseSimActivity.getIntent(this);
+        intent.putExtra(ChooseSimActivity.KEY_HAS_PSIM, true);
+        startActivity(intent);
+        finish();
+    }
+}
diff --git a/src/com/android/settings/sim/SimActivationNotifier.java b/src/com/android/settings/sim/SimActivationNotifier.java
index 4a4edef..a38816a 100644
--- a/src/com/android/settings/sim/SimActivationNotifier.java
+++ b/src/com/android/settings/sim/SimActivationNotifier.java
@@ -26,8 +26,10 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.SharedPreferences;
+import android.telephony.CarrierConfigManager;
 import android.telephony.SubscriptionInfo;
 import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
 import android.text.TextUtils;
 import android.util.Log;
 
@@ -40,6 +42,8 @@
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 
+import javax.annotation.Nullable;
+
 /**
  * This class manages the notification of SIM activation notification including creating and
  * canceling the notifications.
@@ -48,21 +52,26 @@
 
     private static final String TAG = "SimActivationNotifier";
     private static final String SIM_SETUP_CHANNEL_ID = "sim_setup";
+    private static final String SWITCH_SLOT_CHANNEL_ID = "carrier_switching";
     private static final String SIM_PREFS = "sim_prefs";
     private static final String KEY_SHOW_SIM_SETTINGS_NOTIFICATION =
             "show_sim_settings_notification";
 
     public static final int SIM_ACTIVATION_NOTIFICATION_ID = 1;
+    public static final int SWITCH_TO_REMOVABLE_SLOT_NOTIFICATION_ID = 2;
 
     /** Notification types */
     @Retention(RetentionPolicy.SOURCE)
     @IntDef(
             value = {
                 NotificationType.NETWORK_CONFIG,
+                NotificationType.SWITCH_TO_REMOVABLE_SLOT,
             })
     public @interface NotificationType {
         // The notification to remind users to config network Settings.
         int NETWORK_CONFIG = 1;
+        // The notification to notify users that the device is switched to the removable slot.
+        int SWITCH_TO_REMOVABLE_SLOT = 2;
     }
 
     private final Context mContext;
@@ -104,13 +113,7 @@
 
     /** Sends a push notification for the SIM activation. It should be called after DSDS reboot. */
     public void sendNetworkConfigNotification() {
-        SubscriptionManager subscriptionManager =
-                mContext.getSystemService(SubscriptionManager.class);
-        SubscriptionInfo activeRemovableSub =
-                SubscriptionUtil.getActiveSubscriptions(subscriptionManager).stream()
-                        .filter(sub -> !sub.isEmbedded())
-                        .findFirst()
-                        .orElse(null);
+        SubscriptionInfo activeRemovableSub = getActiveRemovableSub();
 
         if (activeRemovableSub == null) {
             Log.e(TAG, "No removable subscriptions found. Do not show notification.");
@@ -143,4 +146,65 @@
                         .setAutoCancel(true);
         mNotificationManager.notify(SIM_ACTIVATION_NOTIFICATION_ID, builder.build());
     }
+
+    /** Sends a push notification for switching to the removable slot. */
+    public void sendSwitchedToRemovableSlotNotification() {
+        String carrierName = getActiveCarrierName();
+        Intent clickIntent = new Intent(mContext, Settings.MobileNetworkListActivity.class);
+        TaskStackBuilder stackBuilder =
+                TaskStackBuilder.create(mContext).addNextIntent(clickIntent);
+        PendingIntent contentIntent =
+                stackBuilder.getPendingIntent(
+                        0 /* requestCode */, PendingIntent.FLAG_UPDATE_CURRENT);
+        String titleText =
+                TextUtils.isEmpty(carrierName)
+                        ? mContext.getString(
+                                R.string.switch_to_removable_notification_no_carrier_name)
+                        : mContext.getString(
+                                R.string.switch_to_removable_notification, carrierName);
+        Notification.Builder builder =
+                new Notification.Builder(mContext, SWITCH_SLOT_CHANNEL_ID)
+                        .setContentTitle(titleText)
+                        .setContentText(
+                                mContext.getString(R.string.network_changed_notification_text))
+                        .setContentIntent(contentIntent)
+                        .setSmallIcon(R.drawable.ic_sim_alert)
+                        .setColor(
+                                mContext.getResources()
+                                        .getColor(
+                                                R.color.homepage_generic_icon_background,
+                                                null /* theme */))
+                        .setAutoCancel(true);
+        mNotificationManager.notify(SWITCH_TO_REMOVABLE_SLOT_NOTIFICATION_ID, builder.build());
+    }
+
+    @Nullable
+    private SubscriptionInfo getActiveRemovableSub() {
+        SubscriptionManager subscriptionManager =
+                mContext.getSystemService(SubscriptionManager.class);
+        return SubscriptionUtil.getActiveSubscriptions(subscriptionManager).stream()
+                .filter(sub -> !sub.isEmbedded())
+                .findFirst()
+                .orElse(null);
+    }
+
+    @Nullable
+    private String getActiveCarrierName() {
+        CarrierConfigManager configManager = mContext.getSystemService(CarrierConfigManager.class);
+        TelephonyManager telManager = mContext.getSystemService(TelephonyManager.class);
+        String telName = telManager.getSimOperatorName();
+        if (configManager != null && configManager.getConfig() != null) {
+            boolean override =
+                    configManager
+                            .getConfig()
+                            .getBoolean(CarrierConfigManager.KEY_CARRIER_NAME_OVERRIDE_BOOL);
+            String configName =
+                    configManager
+                            .getConfig()
+                            .getString(CarrierConfigManager.KEY_CARRIER_NAME_STRING);
+
+            return override || TextUtils.isEmpty(telName) ? configName : telName;
+        }
+        return telName;
+    }
 }
diff --git a/src/com/android/settings/sim/SimNotificationService.java b/src/com/android/settings/sim/SimNotificationService.java
index 303c21d..0f52c8b 100644
--- a/src/com/android/settings/sim/SimNotificationService.java
+++ b/src/com/android/settings/sim/SimNotificationService.java
@@ -35,6 +35,7 @@
 
     /**
      * Schedules a service to send SIM push notifications.
+     *
      * @param context
      * @param notificationType indicates which SIM notification to send.
      */
@@ -67,6 +68,9 @@
                 SimActivationNotifier.setShowSimSettingsNotification(this, false);
                 new SimActivationNotifier(this).sendNetworkConfigNotification();
                 break;
+            case SimActivationNotifier.NotificationType.SWITCH_TO_REMOVABLE_SLOT:
+                new SimActivationNotifier(this).sendSwitchedToRemovableSlotNotification();
+                break;
             default:
                 Log.e(TAG, "Invalid notification type: " + notificationType);
                 break;
diff --git a/src/com/android/settings/sim/SwitchToEsimConfirmDialogActivity.java b/src/com/android/settings/sim/SwitchToEsimConfirmDialogActivity.java
new file mode 100644
index 0000000..385deff
--- /dev/null
+++ b/src/com/android/settings/sim/SwitchToEsimConfirmDialogActivity.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2021 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.sim;
+
+import android.os.Bundle;
+import android.telephony.SubscriptionInfo;
+import android.util.Log;
+
+import com.android.settings.R;
+import com.android.settings.SidecarFragment;
+import com.android.settings.network.SwitchToEuiccSubscriptionSidecar;
+import com.android.settings.network.telephony.AlertDialogFragment;
+import com.android.settings.network.telephony.ConfirmDialogFragment;
+import com.android.settings.network.telephony.SubscriptionActionDialogActivity;
+
+/**
+ * Starts a confirm dialog asking the user to switch to the eSIM slot/subscription. The caller needs
+ * to pass in the current enabled eSIM subscription, which is also the subscription to switch to.
+ */
+public class SwitchToEsimConfirmDialogActivity extends SubscriptionActionDialogActivity
+        implements SidecarFragment.Listener, ConfirmDialogFragment.OnConfirmListener {
+
+    public static final String KEY_SUB_TO_ENABLE = "sub_to_enable";
+
+    private static final String TAG = "SwitchToEsimConfirmDialogActivity";
+    private static final int TAG_CONFIRM = 1;
+
+    private SubscriptionInfo mSubToEnabled = null;
+    private SwitchToEuiccSubscriptionSidecar mSwitchToEuiccSubscriptionSidecar;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        mSubToEnabled = getIntent().getParcelableExtra(KEY_SUB_TO_ENABLE);
+        mSwitchToEuiccSubscriptionSidecar =
+                SwitchToEuiccSubscriptionSidecar.get(getFragmentManager());
+
+        if (mSubToEnabled == null) {
+            Log.e(TAG, "Cannot find SIM to enable.");
+            finish();
+            return;
+        }
+
+        if (savedInstanceState == null) {
+            ConfirmDialogFragment.show(
+                    this,
+                    ConfirmDialogFragment.OnConfirmListener.class,
+                    TAG_CONFIRM,
+                    getString(R.string.switch_sim_dialog_title, mSubToEnabled.getDisplayName()),
+                    getString(R.string.switch_sim_dialog_text, mSubToEnabled.getDisplayName()),
+                    getString(R.string.okay),
+                    getString(R.string.cancel));
+        }
+    }
+
+    @Override
+    public void onResume() {
+        super.onResume();
+        mSwitchToEuiccSubscriptionSidecar.addListener(this);
+    }
+
+    @Override
+    public void onPause() {
+        mSwitchToEuiccSubscriptionSidecar.removeListener(this);
+        super.onPause();
+    }
+
+    @Override
+    public void onStateChange(SidecarFragment fragment) {
+        if (fragment == mSwitchToEuiccSubscriptionSidecar) {
+            switch (mSwitchToEuiccSubscriptionSidecar.getState()) {
+                case SidecarFragment.State.SUCCESS:
+                    mSwitchToEuiccSubscriptionSidecar.reset();
+                    Log.i(TAG, "Successfully switched to eSIM slot.");
+                    dismissProgressDialog();
+                    finish();
+                    break;
+                case SidecarFragment.State.ERROR:
+                    mSwitchToEuiccSubscriptionSidecar.reset();
+                    Log.e(TAG, "Failed switching to eSIM slot.");
+                    dismissProgressDialog();
+                    finish();
+                    break;
+            }
+        }
+    }
+
+    @Override
+    public void onConfirm(int tag, boolean confirmed) {
+        if (!confirmed) {
+            AlertDialogFragment.show(
+                    this,
+                    getString(R.string.switch_sim_dialog_no_switch_title),
+                    getString(R.string.switch_sim_dialog_no_switch_text));
+            return;
+        }
+        Log.i(TAG, "User confirmed to switch to embedded slot.");
+        mSwitchToEuiccSubscriptionSidecar.run(mSubToEnabled.getSubscriptionId());
+        showProgressDialog(
+                getString(
+                        R.string.sim_action_switch_sub_dialog_progress,
+                        mSubToEnabled.getDisplayName()));
+    }
+}
diff --git a/src/com/android/settings/sim/receivers/SimSlotChangeHandler.java b/src/com/android/settings/sim/receivers/SimSlotChangeHandler.java
index 814f1a4..c092428 100644
--- a/src/com/android/settings/sim/receivers/SimSlotChangeHandler.java
+++ b/src/com/android/settings/sim/receivers/SimSlotChangeHandler.java
@@ -19,6 +19,7 @@
 import static android.content.Context.MODE_PRIVATE;
 
 import android.content.Context;
+import android.content.Intent;
 import android.content.SharedPreferences;
 import android.os.Looper;
 import android.provider.Settings;
@@ -29,6 +30,13 @@
 import android.util.Log;
 
 import com.android.settings.network.SubscriptionUtil;
+import com.android.settings.network.UiccSlotUtil;
+import com.android.settings.network.UiccSlotsException;
+import com.android.settings.sim.ChooseSimActivity;
+import com.android.settings.sim.DsdsDialogActivity;
+import com.android.settings.sim.SimActivationNotifier;
+import com.android.settings.sim.SimNotificationService;
+import com.android.settings.sim.SwitchToEsimConfirmDialogActivity;
 
 import com.google.common.collect.ImmutableList;
 
@@ -121,14 +129,13 @@
             return;
         }
 
-        if (!hasActiveEsimSubscription()) {
-            if (mTelMgr.isMultiSimEnabled()) {
+        if (hasActiveEsimSubscription()) {
+            if (mTelMgr.isMultiSimSupported() == TelephonyManager.MULTISIM_ALLOWED) {
                 Log.i(TAG, "Enabled profile exists. DSDS condition satisfied.");
-                // TODO(b/170508680): Display DSDS dialog to ask users whether to enable DSDS.
+                startDsdsDialogActivity();
             } else {
                 Log.i(TAG, "Enabled profile exists. DSDS condition not satisfied.");
-                // TODO(b/170508680): Display Choose a number to use screen for subscription
-                //  selection.
+                startChooseSimActivity(true);
             }
             return;
         }
@@ -137,7 +144,15 @@
                 TAG,
                 "No enabled eSIM profile. Ready to switch to removable slot and show"
                         + " notification.");
-        // TODO(b/170508680): Switch the slot to the removebale slot and show the notification.
+        try {
+            UiccSlotUtil.switchToRemovableSlot(
+                    UiccSlotUtil.INVALID_PHYSICAL_SLOT_ID, mContext.getApplicationContext());
+        } catch (UiccSlotsException e) {
+            Log.e(TAG, "Failed to switch to removable slot.");
+            return;
+        }
+        SimNotificationService.scheduleSimNotification(
+                mContext, SimActivationNotifier.NotificationType.SWITCH_TO_REMOVABLE_SLOT);
     }
 
     private void handleSimRemove(UiccSlotInfo removableSlotInfo) {
@@ -160,14 +175,14 @@
         // profile.
         if (groupedEmbeddedSubscriptions.size() == 1) {
             Log.i(TAG, "Only 1 eSIM profile found. Ask user's consent to switch.");
-            // TODO(b/170508680): Display a dialog to ask users to switch.
+            startSwitchSlotConfirmDialogActivity(groupedEmbeddedSubscriptions.get(0));
             return;
         }
 
         // If there are more than 1 eSIM profiles installed, we show a screen to let users to choose
         // the number they want to use.
         Log.i(TAG, "Multiple eSIM profiles found. Ask user which subscription to use.");
-        // TODO(b/170508680): Display a dialog to ask user which SIM to switch.
+        startChooseSimActivity(false);
     }
 
     private int getLastRemovableSimSlotState(Context context) {
@@ -225,5 +240,25 @@
                         .collect(Collectors.toList()));
     }
 
+    private void startChooseSimActivity(boolean psimInserted) {
+        Intent intent = ChooseSimActivity.getIntent(mContext);
+        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        intent.putExtra(ChooseSimActivity.KEY_HAS_PSIM, psimInserted);
+        mContext.startActivity(intent);
+    }
+
+    private void startSwitchSlotConfirmDialogActivity(SubscriptionInfo subscriptionInfo) {
+        Intent intent = new Intent(mContext, SwitchToEsimConfirmDialogActivity.class);
+        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        intent.putExtra(SwitchToEsimConfirmDialogActivity.KEY_SUB_TO_ENABLE, subscriptionInfo);
+        mContext.startActivity(intent);
+    }
+
+    private void startDsdsDialogActivity() {
+        Intent intent = new Intent(mContext, DsdsDialogActivity.class);
+        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        mContext.startActivity(intent);
+    }
+
     private SimSlotChangeHandler() {}
 }
diff --git a/src/com/android/settings/sim/receivers/SimSlotChangeReceiver.java b/src/com/android/settings/sim/receivers/SimSlotChangeReceiver.java
index a730dd1..563b4ef 100644
--- a/src/com/android/settings/sim/receivers/SimSlotChangeReceiver.java
+++ b/src/com/android/settings/sim/receivers/SimSlotChangeReceiver.java
@@ -48,14 +48,16 @@
             return;
         }
 
+        final PendingResult pendingResult = goAsync();
         ThreadUtils.postOnBackgroundThread(
                 () -> {
                     synchronized (mLock) {
                         if (!shouldHandleSlotChange(context)) {
                             return;
                         }
-                        mSlotChangeHandler.onSlotsStatusChange(context);
+                        mSlotChangeHandler.onSlotsStatusChange(context.getApplicationContext());
                     }
+                    ThreadUtils.postOnMainThread(pendingResult::finish);
                 });
     }