Remove on/off toggle for physical SIMs

Our original design for mobile network subscription management included
having an on/off toggle for both eSIMs and physical SIMs. However, it
turns out that our current telephony stack has some problems with
disabling physical SIMs, so for now we're removing the on/off
toggle. Because of this, we've added a footer to the SIM details page
for physical SIMS letting users know that to disable them you need to
remove them from the device.

Even though we're removing the on/off toggle for pSIMs, there are still
a few edge cases where you could end up with a disabled one (eg having
two SIMs in single-SIM mode where the eSIM is active and then you erased
the eSIM). In order to have a way to re-enable the pSIM in these cases,
this CL changes the relevant pref's summary to "Tap to activate <name>"
and makes the tap action begin the re-enabling. This can affect either
the Mobile network pref on the Network & internet main page (if this
disabled pSIM is the only SIM), or an entry in the Mobile networks list
page (if there are still multiple SIMs present).

Finally, this also fixes a problem where we weren't showing the on/off
toggle for eSIMs if you only had one SIM total; we actually always want
to show it for eSIMs.

Bug: 132921553
Test: make RunSettingsRoboTests
Change-Id: Id0750ebd5bed46dc2450b65b53cc81847ef09b82
diff --git a/res/xml/mobile_network_settings_v2.xml b/res/xml/mobile_network_settings_v2.xml
index 20ed1c7..506b354 100644
--- a/res/xml/mobile_network_settings_v2.xml
+++ b/res/xml/mobile_network_settings_v2.xml
@@ -203,4 +203,14 @@
         android:title="@string/mobile_network_erase_sim"
         settings:controller="com.android.settings.network.telephony.DeleteSimProfilePreferenceController" />
 
+    <PreferenceCategory
+        android:key="footer_container"
+        android:title="@string/summary_placeholder"
+        android:layout="@layout/preference_category_no_label"
+        settings:controller="com.android.settings.network.telephony.DisableSimFooterPreferenceController">
+        <com.android.settingslib.widget.FooterPreference
+            android:key="disable_sim_explanation"
+            android:title="@string/mobile_network_disable_sim_explanation" />
+    </PreferenceCategory>
+
 </PreferenceScreen>
diff --git a/src/com/android/settings/network/MobileNetworkListController.java b/src/com/android/settings/network/MobileNetworkListController.java
index d0e14ce..ab41fad 100644
--- a/src/com/android/settings/network/MobileNetworkListController.java
+++ b/src/com/android/settings/network/MobileNetworkListController.java
@@ -118,14 +118,19 @@
                 if (mSubscriptionManager.isActiveSubscriptionId(subId)) {
                     pref.setSummary(R.string.mobile_network_active_sim);
                 } else {
-                    pref.setSummary(R.string.mobile_network_inactive_sim);
+                    pref.setSummary(mContext.getString(R.string.mobile_network_tap_to_activate,
+                            SubscriptionUtil.getDisplayName(info)));
                 }
             }
 
             pref.setOnPreferenceClickListener(clickedPref -> {
-                final Intent intent = new Intent(mContext, MobileNetworkActivity.class);
-                intent.putExtra(Settings.EXTRA_SUB_ID, info.getSubscriptionId());
-                mContext.startActivity(intent);
+                if (!info.isEmbedded() && !mSubscriptionManager.isActiveSubscriptionId(subId)) {
+                    mSubscriptionManager.setSubscriptionEnabled(subId, true);
+                } else {
+                    final Intent intent = new Intent(mContext, MobileNetworkActivity.class);
+                    intent.putExtra(Settings.EXTRA_SUB_ID, info.getSubscriptionId());
+                    mContext.startActivity(intent);
+                }
                 return true;
             });
             mPreferences.put(subId, pref);
diff --git a/src/com/android/settings/network/MobileNetworkSummaryController.java b/src/com/android/settings/network/MobileNetworkSummaryController.java
index 9498c4e..0f76f24 100644
--- a/src/com/android/settings/network/MobileNetworkSummaryController.java
+++ b/src/com/android/settings/network/MobileNetworkSummaryController.java
@@ -108,7 +108,14 @@
             }
             return null;
         } else if (subs.size() == 1) {
-            return subs.get(0).getDisplayName();
+            final SubscriptionInfo info = subs.get(0);
+            final int subId = info.getSubscriptionId();
+            if (!info.isEmbedded() && !mSubscriptionManager.isActiveSubscriptionId(subId)) {
+                return mContext.getString(R.string.mobile_network_tap_to_activate,
+                        SubscriptionUtil.getDisplayName(info));
+            } else {
+                return subs.get(0).getDisplayName();
+            }
         } else {
             final int count = subs.size();
             return mContext.getResources().getQuantityString(R.plurals.mobile_network_summary_count,
@@ -154,9 +161,15 @@
 
             if (subs.size() == 1) {
                 mPreference.setOnPreferenceClickListener((Preference pref) -> {
-                    final Intent intent = new Intent(mContext, MobileNetworkActivity.class);
-                    intent.putExtra(Settings.EXTRA_SUB_ID, subs.get(0).getSubscriptionId());
-                    mContext.startActivity(intent);
+                    final SubscriptionInfo info = subs.get(0);
+                    final int subId = info.getSubscriptionId();
+                    if (!info.isEmbedded() && !mSubscriptionManager.isActiveSubscriptionId(subId)) {
+                        mSubscriptionManager.setSubscriptionEnabled(subId, true);
+                    } else {
+                        final Intent intent = new Intent(mContext, MobileNetworkActivity.class);
+                        intent.putExtra(Settings.EXTRA_SUB_ID, subs.get(0).getSubscriptionId());
+                        mContext.startActivity(intent);
+                    }
                     return true;
                 });
             } else {
diff --git a/src/com/android/settings/network/SubscriptionUtil.java b/src/com/android/settings/network/SubscriptionUtil.java
index ebac51f..f723575 100644
--- a/src/com/android/settings/network/SubscriptionUtil.java
+++ b/src/com/android/settings/network/SubscriptionUtil.java
@@ -25,12 +25,10 @@
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
 import android.telephony.UiccSlotInfo;
-import android.text.TextUtils;
 
 import androidx.annotation.VisibleForTesting;
 
 import java.util.ArrayList;
-import java.util.Iterator;
 import java.util.List;
 
 public class SubscriptionUtil {
@@ -107,4 +105,12 @@
         }
         return subscriptions;
     }
+
+    public static String getDisplayName(SubscriptionInfo info) {
+        final CharSequence name = info.getDisplayName();
+        if (name != null) {
+            return name.toString();
+        }
+        return "";
+    }
 }
diff --git a/src/com/android/settings/network/telephony/DisableSimFooterPreferenceController.java b/src/com/android/settings/network/telephony/DisableSimFooterPreferenceController.java
new file mode 100644
index 0000000..ab01b9d
--- /dev/null
+++ b/src/com/android/settings/network/telephony/DisableSimFooterPreferenceController.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2019 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.telephony;
+
+import android.content.Context;
+import android.telephony.SubscriptionInfo;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
+
+import com.android.settings.core.BasePreferenceController;
+import com.android.settings.network.SubscriptionUtil;
+
+public class DisableSimFooterPreferenceController extends BasePreferenceController {
+    private int mSubId;
+
+    public DisableSimFooterPreferenceController(Context context, String preferenceKey) {
+        super(context, preferenceKey);
+        mSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+    }
+
+    public void init(int subId) {
+        mSubId = subId;
+    }
+
+    @Override
+    public int getAvailabilityStatus() {
+        if (mSubId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
+            return CONDITIONALLY_UNAVAILABLE;
+        }
+        for (SubscriptionInfo info : SubscriptionUtil.getAvailableSubscriptions(mContext)) {
+            if (info.getSubscriptionId() == mSubId) {
+                if (info.isEmbedded()) {
+                    return CONDITIONALLY_UNAVAILABLE;
+                }
+                break;
+            }
+        }
+        return AVAILABLE;
+    }
+}
diff --git a/src/com/android/settings/network/telephony/MobileNetworkSettings.java b/src/com/android/settings/network/telephony/MobileNetworkSettings.java
index aa63b74..c8e2247 100644
--- a/src/com/android/settings/network/telephony/MobileNetworkSettings.java
+++ b/src/com/android/settings/network/telephony/MobileNetworkSettings.java
@@ -142,6 +142,7 @@
             use(DisabledSubscriptionController.class).init(getLifecycle(), mSubId);
             use(DeleteSimProfilePreferenceController.class).init(mSubId, this,
                     REQUEST_CODE_DELETE_SUBSCRIPTION);
+            use(DisableSimFooterPreferenceController.class).init(mSubId);
         }
         use(MobileDataPreferenceController.class).init(getFragmentManager(), mSubId);
         use(RoamingPreferenceController.class).init(getFragmentManager(), mSubId);
diff --git a/src/com/android/settings/network/telephony/MobileNetworkSwitchController.java b/src/com/android/settings/network/telephony/MobileNetworkSwitchController.java
index 2037adc..e6422f0 100644
--- a/src/com/android/settings/network/telephony/MobileNetworkSwitchController.java
+++ b/src/com/android/settings/network/telephony/MobileNetworkSwitchController.java
@@ -93,23 +93,23 @@
         if (mSwitchBar == null) {
             return;
         }
-        final List<SubscriptionInfo> subs = SubscriptionUtil.getAvailableSubscriptions(
-                mContext);
-        if (mSubId == SubscriptionManager.INVALID_SUBSCRIPTION_ID ||
-                mSubscriptionManager.isSubscriptionEnabled(mSubId) && subs.size() < 2) {
-            mSwitchBar.hide();
-            return;
-        }
 
-        for (SubscriptionInfo info : subs) {
+        SubscriptionInfo subInfo = null;
+        for (SubscriptionInfo info : SubscriptionUtil.getAvailableSubscriptions(mContext)) {
             if (info.getSubscriptionId() == mSubId) {
-                mSwitchBar.show();
-                mSwitchBar.setChecked(mSubscriptionManager.isSubscriptionEnabled(mSubId));
-                return;
+                subInfo = info;
+                break;
             }
         }
-        // This subscription was not found in the available list.
-        mSwitchBar.hide();
+
+        // For eSIM, we always want the toggle. The telephony stack doesn't currently support
+        // disabling a pSIM directly (b/133379187), so we for now we don't include this on pSIM.
+        if (subInfo == null || !subInfo.isEmbedded()) {
+            mSwitchBar.hide();
+        } else {
+            mSwitchBar.show();
+            mSwitchBar.setChecked(mSubscriptionManager.isSubscriptionEnabled(mSubId));
+        }
     }
 
     @Override
diff --git a/tests/robotests/src/com/android/settings/network/MobileNetworkListControllerTest.java b/tests/robotests/src/com/android/settings/network/MobileNetworkListControllerTest.java
index 342fed5..4a98753 100644
--- a/tests/robotests/src/com/android/settings/network/MobileNetworkListControllerTest.java
+++ b/tests/robotests/src/com/android/settings/network/MobileNetworkListControllerTest.java
@@ -21,6 +21,8 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.times;
@@ -32,6 +34,7 @@
 import android.content.Intent;
 import android.provider.Settings;
 import android.telephony.SubscriptionInfo;
+import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
 import android.telephony.euicc.EuiccManager;
 
@@ -54,9 +57,11 @@
 @RunWith(RobolectricTestRunner.class)
 public class MobileNetworkListControllerTest {
     @Mock
-    TelephonyManager mTelephonyManager;
+    private TelephonyManager mTelephonyManager;
     @Mock
-    EuiccManager mEuiccManager;
+    private EuiccManager mEuiccManager;
+    @Mock
+    private SubscriptionManager mSubscriptionManager;
 
     @Mock
     private Lifecycle mLifecycle;
@@ -74,6 +79,7 @@
         mContext = spy(Robolectric.setupActivity(Activity.class));
         when(mContext.getSystemService(TelephonyManager.class)).thenReturn(mTelephonyManager);
         when(mContext.getSystemService(EuiccManager.class)).thenReturn(mEuiccManager);
+        when(mContext.getSystemService(SubscriptionManager.class)).thenReturn(mSubscriptionManager);
         Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.EUICC_PROVISIONED, 1);
         when(mPreferenceScreen.getContext()).thenReturn(mContext);
         mAddMorePreference = new Preference(mContext);
@@ -114,6 +120,8 @@
     public void displayPreference_twoSubscriptions_correctlySetup() {
         final SubscriptionInfo sub1 = createMockSubscription(1, "sub1");
         final SubscriptionInfo sub2 = createMockSubscription(2, "sub2");
+        doReturn(true).when(mSubscriptionManager).isActiveSubscriptionId(eq(1));
+        doReturn(true).when(mSubscriptionManager).isActiveSubscriptionId(eq(2));
         SubscriptionUtil.setAvailableSubscriptionsForTesting(Arrays.asList(sub1, sub2));
         mController.displayPreference(mPreferenceScreen);
         mController.onResume();
@@ -139,6 +147,33 @@
     }
 
     @Test
+    public void displayPreference_oneActiveESimOneInactivePSim_correctlySetup() {
+        final SubscriptionInfo sub1 = createMockSubscription(1, "sub1");
+        final SubscriptionInfo sub2 = createMockSubscription(2, "sub2");
+        when(sub1.isEmbedded()).thenReturn(true);
+        doReturn(true).when(mSubscriptionManager).isActiveSubscriptionId(eq(1));
+        doReturn(false).when(mSubscriptionManager).isActiveSubscriptionId(eq(2));
+
+        when(sub2.isEmbedded()).thenReturn(false);
+        SubscriptionUtil.setAvailableSubscriptionsForTesting(Arrays.asList(sub1, sub2));
+
+        mController.displayPreference(mPreferenceScreen);
+        mController.onResume();
+
+        // Check that the preferences get created with the correct summaries.
+        final ArgumentCaptor<Preference> preferenceCaptor = ArgumentCaptor.forClass(
+                Preference.class);
+        verify(mPreferenceScreen, times(2)).addPreference(preferenceCaptor.capture());
+        final Preference pref1 = preferenceCaptor.getAllValues().get(0);
+        final Preference pref2 = preferenceCaptor.getAllValues().get(1);
+        assertThat(pref1.getSummary()).isEqualTo("Active / Downloaded SIM");
+        assertThat(pref2.getSummary()).isEqualTo("Tap to activate sub2");
+
+        pref2.getOnPreferenceClickListener().onPreferenceClick(pref2);
+        verify(mSubscriptionManager).setSubscriptionEnabled(eq(2), eq(true));
+    }
+
+    @Test
     public void onSubscriptionsChanged_twoSubscriptionsOneChangesName_preferenceUpdated() {
         final SubscriptionInfo sub1 = createMockSubscription(1, "sub1");
         final SubscriptionInfo sub2 = createMockSubscription(2, "sub2");
diff --git a/tests/robotests/src/com/android/settings/network/MobileNetworkSummaryControllerTest.java b/tests/robotests/src/com/android/settings/network/MobileNetworkSummaryControllerTest.java
index a623850..99c9134 100644
--- a/tests/robotests/src/com/android/settings/network/MobileNetworkSummaryControllerTest.java
+++ b/tests/robotests/src/com/android/settings/network/MobileNetworkSummaryControllerTest.java
@@ -18,6 +18,7 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.ArgumentMatchers.notNull;
 import static org.mockito.Mockito.atLeastOnce;
@@ -64,6 +65,8 @@
     @Mock
     private TelephonyManager mTelephonyManager;
     @Mock
+    private SubscriptionManager mSubscriptionManager;
+    @Mock
     private EuiccManager mEuiccManager;
     @Mock
     private PreferenceScreen mPreferenceScreen;
@@ -79,9 +82,11 @@
         MockitoAnnotations.initMocks(this);
         mContext = spy(Robolectric.setupActivity(Activity.class));
         when(mContext.getSystemService(TelephonyManager.class)).thenReturn(mTelephonyManager);
+        when(mContext.getSystemService(SubscriptionManager.class)).thenReturn(mSubscriptionManager);
         when(mContext.getSystemService(EuiccManager.class)).thenReturn(mEuiccManager);
         when(mContext.getSystemService(UserManager.class)).thenReturn(mUserManager);
         when(mTelephonyManager.getNetworkCountryIso()).thenReturn("");
+        when(mSubscriptionManager.isActiveSubscriptionId(anyInt())).thenReturn(true);
         when(mEuiccManager.isEnabled()).thenReturn(true);
         Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.EUICC_PROVISIONED, 1);
 
@@ -160,6 +165,24 @@
     }
 
     @Test
+    public void getSummary_oneInactivePSim_correctSummaryAndClickHandler() {
+        final SubscriptionInfo sub1 = mock(SubscriptionInfo.class);
+        when(sub1.getSubscriptionId()).thenReturn(1);
+        when(sub1.getDisplayName()).thenReturn("sub1");
+        SubscriptionUtil.setAvailableSubscriptionsForTesting(Arrays.asList(sub1));
+        when(mSubscriptionManager.isActiveSubscriptionId(eq(1))).thenReturn(false);
+
+        mController.displayPreference(mPreferenceScreen);
+        mController.onResume();
+
+        assertThat(mController.getSummary()).isEqualTo("Tap to activate sub1");
+
+        assertThat(mPreference.getFragment()).isNull();
+        mPreference.getOnPreferenceClickListener().onPreferenceClick(mPreference);
+        verify(mSubscriptionManager).setSubscriptionEnabled(eq(sub1.getSubscriptionId()), eq(true));
+    }
+
+    @Test
     public void getSummary_twoSubscriptions_correctSummaryAndFragment() {
         final SubscriptionInfo sub1 = mock(SubscriptionInfo.class);
         final SubscriptionInfo sub2 = mock(SubscriptionInfo.class);
diff --git a/tests/robotests/src/com/android/settings/network/telephony/DisableSimFooterPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/network/telephony/DisableSimFooterPreferenceControllerTest.java
new file mode 100644
index 0000000..a0a4f00
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/network/telephony/DisableSimFooterPreferenceControllerTest.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2019 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.telephony;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.telephony.SubscriptionInfo;
+
+import com.android.settings.network.SubscriptionUtil;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+
+import java.util.Arrays;
+
+@RunWith(RobolectricTestRunner.class)
+public class DisableSimFooterPreferenceControllerTest {
+    private static final String PREF_KEY = "pref_key";
+    private static final int SUB_ID = 111;
+
+    @Mock
+    private SubscriptionInfo mInfo;
+
+    private Context mContext;
+    private DisableSimFooterPreferenceController mController;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mContext = RuntimeEnvironment.application;
+        when(mInfo.getSubscriptionId()).thenReturn(SUB_ID);
+        SubscriptionUtil.setAvailableSubscriptionsForTesting(Arrays.asList(mInfo));
+        mController = new DisableSimFooterPreferenceController(mContext, PREF_KEY);
+    }
+
+    @Test
+    public void isAvailable_noInit_notAvailable() {
+        assertThat(mController.isAvailable()).isFalse();
+    }
+
+    @Test
+    public void isAvailable_eSIM_notAvailable() {
+        when(mInfo.isEmbedded()).thenReturn(true);
+        mController.init(SUB_ID);
+        assertThat(mController.isAvailable()).isFalse();
+    }
+
+    @Test
+    public void isAvailable_pSIM_available() {
+        when(mInfo.isEmbedded()).thenReturn(false);
+        mController.init(SUB_ID);
+        assertThat(mController.isAvailable()).isTrue();
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/network/telephony/MobileNetworkSwitchControllerTest.java b/tests/robotests/src/com/android/settings/network/telephony/MobileNetworkSwitchControllerTest.java
index a10227f..86c380e 100644
--- a/tests/robotests/src/com/android/settings/network/telephony/MobileNetworkSwitchControllerTest.java
+++ b/tests/robotests/src/com/android/settings/network/telephony/MobileNetworkSwitchControllerTest.java
@@ -79,13 +79,14 @@
         mLifecycleOwner = () -> mLifecycle;
         mLifecycle = new Lifecycle(mLifecycleOwner);
 
+        when(mSubscription.isEmbedded()).thenReturn(true);
         when(mSubscription.getSubscriptionId()).thenReturn(mSubId);
         // Most tests want to have 2 available subscriptions so that the switch bar will show.
-        SubscriptionInfo sub2 = mock(SubscriptionInfo.class);
+        final SubscriptionInfo sub2 = mock(SubscriptionInfo.class);
         when(sub2.getSubscriptionId()).thenReturn(456);
         SubscriptionUtil.setAvailableSubscriptionsForTesting(Arrays.asList(mSubscription, sub2));
 
-        String key = "prefKey";
+        final String key = "prefKey";
         mController = new MobileNetworkSwitchController(mContext, key);
         mController.init(mLifecycle, mSubscription.getSubscriptionId());
 
@@ -100,11 +101,18 @@
     }
 
     @Test
-    public void displayPreference_oneEnabledSubscription_switchBarHidden() {
+    public void isAvailable_pSIM_isNotAvailable() {
+        when(mSubscription.isEmbedded()).thenReturn(false);
+        mController.displayPreference(mScreen);
+        assertThat(mSwitchBar.isShowing()).isFalse();
+    }
+
+    @Test
+    public void displayPreference_oneEnabledSubscription_switchBarNotHidden() {
         doReturn(true).when(mSubscriptionManager).isSubscriptionEnabled(mSubId);
         SubscriptionUtil.setAvailableSubscriptionsForTesting(Arrays.asList(mSubscription));
         mController.displayPreference(mScreen);
-        assertThat(mSwitchBar.isShowing()).isFalse();
+        assertThat(mSwitchBar.isShowing()).isTrue();
     }
 
     @Test