diff --git a/res/values/strings.xml b/res/values/strings.xml
index c861ed9c3c8..46069950728 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -2398,6 +2398,13 @@
%1$s is the only SIM in your device. Do you want to use this SIM for mobile data, calls, and SMS messages?
+
+ Switch SIMs automatically?
+
+ Allow your phone to automatically switch to %1$s for mobile data when it has better availability.
+
+ \n\nCalls, messages, and network traffic may be visible to your organization.
+
Incorrect SIM PIN code you must now contact your carrier to unlock your device.
diff --git a/src/com/android/settings/sim/EnableAutoDataSwitchDialogFragment.java b/src/com/android/settings/sim/EnableAutoDataSwitchDialogFragment.java
new file mode 100644
index 00000000000..b1b5f8e69ed
--- /dev/null
+++ b/src/com/android/settings/sim/EnableAutoDataSwitchDialogFragment.java
@@ -0,0 +1,209 @@
+/*
+ * Copyright (C) 2022 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.Dialog;
+import android.app.settings.SettingsEnums;
+import android.content.DialogInterface;
+import android.os.Bundle;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.telephony.SubscriptionInfo;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+import androidx.appcompat.app.AlertDialog;
+
+import com.android.settings.R;
+import com.android.settings.network.SubscriptionUtil;
+
+import java.util.List;
+
+/**
+ * Show a dialog prompting the user to enable auto data switch following the dialog where user chose
+ * default data SIM.
+ */
+public class EnableAutoDataSwitchDialogFragment extends SimDialogFragment implements
+ DialogInterface.OnClickListener {
+ private static final String TAG = "EnableAutoDataSwitchDialogFragment";
+ /** Sub Id of the non-default data SIM */
+ private int mBackupDataSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+
+ /** @return a new instance of this fragment */
+ public static EnableAutoDataSwitchDialogFragment newInstance() {
+ final EnableAutoDataSwitchDialogFragment fragment =
+ new EnableAutoDataSwitchDialogFragment();
+ final Bundle args = initArguments(SimDialogActivity.ENABLE_AUTO_DATA_SWITCH,
+ R.string.enable_auto_data_switch_dialog_title);
+ fragment.setArguments(args);
+ return fragment;
+ }
+
+ @NonNull
+ @Override
+ public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
+ final AlertDialog dialog = new AlertDialog.Builder(getContext())
+ .setPositiveButton(R.string.yes, this)
+ .setNegativeButton(R.string.sim_action_no_thanks, null)
+ .create();
+ updateDialog(dialog);
+ return dialog;
+ }
+
+ @Override
+ public int getMetricsCategory() {
+ return SettingsEnums.DIALOG_AUTO_DATA_SWITCH;
+ }
+
+ /** update dialog */
+ public void updateDialog(AlertDialog dialog) {
+ Log.d(TAG, "Dialog updated, dismiss status: " + mWasDismissed);
+
+ if (mWasDismissed) {
+ return;
+ }
+
+ if (dialog == null) {
+ Log.d(TAG, "Dialog is null.");
+ dismiss();
+ return;
+ }
+
+ // Set message
+ View content = LayoutInflater.from(getContext()).inflate(
+ R.layout.sim_confirm_dialog_multiple_enabled_profiles_supported, null);
+ TextView dialogMessage = content != null ? content.findViewById(R.id.msg) : null;
+ final String message = getMessage();
+ if (TextUtils.isEmpty(message) || dialogMessage == null) {
+ onDismiss(dialog);
+ return;
+ }
+ dialogMessage.setText(message);
+ dialogMessage.setVisibility(View.VISIBLE);
+ dialog.setView(content);
+
+ // Set title
+ View titleView = LayoutInflater.from(getContext()).inflate(
+ R.layout.sim_confirm_dialog_title_multiple_enabled_profiles_supported, null);
+ TextView titleTextView = titleView.findViewById(R.id.title);
+ titleTextView.setText(getContext().getString(getTitleResId()));
+ dialog.setCustomTitle(titleTextView);
+ }
+
+ /**
+ * @return The message of the dialog. {@code null} if the dialog shouldn't be displayed.
+ */
+ @VisibleForTesting
+ protected String getMessage() {
+ int ddsSubId = getDefaultDataSubId();
+ if (ddsSubId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) return null;
+ Log.d(TAG, "DDS SubId: " + ddsSubId);
+
+ SubscriptionManager subscriptionManager = getSubscriptionManager();
+ List activeSubscriptions = subscriptionManager
+ .getActiveSubscriptionInfoList();
+ if (activeSubscriptions == null) return null;
+
+ // Find if a backup data sub exists.
+ SubscriptionInfo backupSubInfo = activeSubscriptions.stream()
+ .filter(subInfo -> subInfo.getSubscriptionId() != ddsSubId)
+ .findFirst()
+ .orElse(null);
+ if (backupSubInfo == null) return null;
+ mBackupDataSubId = backupSubInfo.getSubscriptionId();
+
+ // Check if auto data switch is already enabled
+ final TelephonyManager telephonyManager = getTelephonyManagerForSub(mBackupDataSubId);
+ if (telephonyManager == null) {
+ Log.d(TAG, "telephonyManager for " + mBackupDataSubId + " is null");
+ return null;
+ }
+ if (telephonyManager.isMobileDataPolicyEnabled(
+ TelephonyManager.MOBILE_DATA_POLICY_AUTO_DATA_SWITCH)) {
+ Log.d(TAG, "AUTO_DATA_SWITCH already enabled");
+ return null;
+ }
+
+ Log.d(TAG, "Backup data sub Id: " + mBackupDataSubId);
+ // The description of the feature
+ String message =
+ getContext().getString(
+ R.string.enable_auto_data_switch_dialog_message,
+ SubscriptionUtil.getUniqueSubscriptionDisplayName(
+ backupSubInfo, getContext()));
+ UserManager userManager = getUserManager();
+ if (userManager == null) return message;
+
+ // If one of the sub is dedicated to work profile(enterprise-managed), which means we might
+ // switching between personal & work profile, append a warning to the message.
+ UserHandle ddsUserHandle = subscriptionManager.getSubscriptionUserHandle(ddsSubId);
+ UserHandle nDdsUserHandle = subscriptionManager.getSubscriptionUserHandle(mBackupDataSubId);
+ boolean isDdsManaged = ddsUserHandle != null && userManager.isManagedProfile(
+ ddsUserHandle.getIdentifier());
+ boolean isNDdsManaged = nDdsUserHandle != null && userManager.isManagedProfile(
+ nDdsUserHandle.getIdentifier());
+ Log.d(TAG, "isDdsManaged= " + isDdsManaged + " isNDdsManaged=" + isNDdsManaged);
+ if (isDdsManaged ^ isNDdsManaged) {
+ message += getContext().getString(
+ R.string.auto_data_switch_dialog_managed_profile_warning);
+ }
+
+ return message;
+ }
+
+ @Override
+ public void updateDialog() {
+ updateDialog((AlertDialog) getDialog());
+ }
+
+ @Override
+ public void onClick(DialogInterface dialog, int buttonClicked) {
+ if (buttonClicked != DialogInterface.BUTTON_POSITIVE) {
+ return;
+ }
+ final SimDialogActivity activity = (SimDialogActivity) getActivity();
+ if (mBackupDataSubId != SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
+ activity.onSubscriptionSelected(getDialogType(), mBackupDataSubId);
+ }
+ }
+
+ private TelephonyManager getTelephonyManagerForSub(int subId) {
+ return getContext().getSystemService(TelephonyManager.class)
+ .createForSubscriptionId(subId);
+ }
+
+ private SubscriptionManager getSubscriptionManager() {
+ return getContext().getSystemService(SubscriptionManager.class);
+ }
+
+ @VisibleForTesting
+ protected int getDefaultDataSubId() {
+ return SubscriptionManager.getDefaultDataSubscriptionId();
+ }
+
+ private UserManager getUserManager() {
+ return getContext().getSystemService(UserManager.class);
+ }
+}
diff --git a/src/com/android/settings/sim/SelectSpecificDataSimDialogFragment.java b/src/com/android/settings/sim/SelectSpecificDataSimDialogFragment.java
index b2ca6210a86..6ac0067d382 100644
--- a/src/com/android/settings/sim/SelectSpecificDataSimDialogFragment.java
+++ b/src/com/android/settings/sim/SelectSpecificDataSimDialogFragment.java
@@ -72,14 +72,31 @@ public class SelectSpecificDataSimDialogFragment extends SimDialogFragment imple
}
@Override
- public void onClick(DialogInterface dialog, int buttonClicked) {
- if (buttonClicked != DialogInterface.BUTTON_POSITIVE) {
- return;
+ public void onDismiss(@NonNull DialogInterface dialog) {
+ Log.d(TAG, "Dialog onDismiss, dismiss status: " + mWasDismissed);
+ if (!mWasDismissed) {
+ // This dialog might be called onDismiss twice due to first time called by onDismiss()
+ // as a consequence of user action. We need this fragment alive so the activity
+ // doesn't end, which allows the following dialog to attach. Upon the second dialog
+ // dismiss, this fragment is removed from SimDialogActivity.onFragmentDismissed to
+ // end the activity.
+ mWasDismissed = true;
+ final SimDialogActivity activity = (SimDialogActivity) getActivity();
+ activity.showEnableAutoDataSwitchDialog();
+ // Not using super.onDismiss because it will result in an immediate end of the activity,
+ // before the second auto data switch dialog can attach.
+ if (getDialog() != null) getDialog().dismiss();
}
+ }
+
+ @Override
+ public void onClick(DialogInterface dialog, int buttonClicked) {
final SimDialogActivity activity = (SimDialogActivity) getActivity();
- final SubscriptionInfo info = getTargetSubscriptionInfo();
- if (info != null) {
- activity.onSubscriptionSelected(getDialogType(), info.getSubscriptionId());
+ if (buttonClicked == DialogInterface.BUTTON_POSITIVE) {
+ final SubscriptionInfo info = getTargetSubscriptionInfo();
+ if (info != null) {
+ activity.onSubscriptionSelected(getDialogType(), info.getSubscriptionId());
+ }
}
}
diff --git a/src/com/android/settings/sim/SimDialogActivity.java b/src/com/android/settings/sim/SimDialogActivity.java
index 464ba9b4a51..a1258be64b0 100644
--- a/src/com/android/settings/sim/SimDialogActivity.java
+++ b/src/com/android/settings/sim/SimDialogActivity.java
@@ -16,8 +16,6 @@
package com.android.settings.sim;
-import static android.content.Context.MODE_PRIVATE;
-
import android.app.Activity;
import android.content.Intent;
import android.content.SharedPreferences;
@@ -61,6 +59,8 @@ public class SimDialogActivity extends FragmentActivity {
public static final int SMS_PICK_FOR_MESSAGE = 4;
// Dismiss the current dialog and finish the activity.
public static final int PICK_DISMISS = 5;
+ // Show auto data switch dialog(when user enables multi-SIM)
+ public static final int ENABLE_AUTO_DATA_SWITCH = 6;
@Override
protected void onCreate(Bundle savedInstanceState) {
@@ -122,7 +122,7 @@ public class SimDialogActivity extends FragmentActivity {
private SimDialogFragment createFragment(int dialogType) {
switch (dialogType) {
case DATA_PICK:
- return getDataPickDialogFramgent();
+ return getDataPickDialogFragment();
case CALLS_PICK:
return CallsSimListDialogFragment.newInstance(dialogType,
R.string.select_sim_for_calls,
@@ -141,12 +141,14 @@ public class SimDialogActivity extends FragmentActivity {
return SimListDialogFragment.newInstance(dialogType, R.string.select_sim_for_sms,
false /* includeAskEveryTime */,
false /* isCancelItemShowed */);
+ case ENABLE_AUTO_DATA_SWITCH:
+ return EnableAutoDataSwitchDialogFragment.newInstance();
default:
throw new IllegalArgumentException("Invalid dialog type " + dialogType + " sent.");
}
}
- private SimDialogFragment getDataPickDialogFramgent() {
+ private SimDialogFragment getDataPickDialogFragment() {
if (SubscriptionManager.getDefaultDataSubscriptionId()
== SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
return SimListDialogFragment.newInstance(DATA_PICK, R.string.select_sim_for_data,
@@ -181,15 +183,40 @@ public class SimDialogActivity extends FragmentActivity {
intent.putExtra(RESULT_SUB_ID, subId);
setResult(Activity.RESULT_OK, intent);
break;
+ case ENABLE_AUTO_DATA_SWITCH:
+ onEnableAutoDataSwitch(subId);
+ break;
default:
throw new IllegalArgumentException(
"Invalid dialog type " + dialogType + " sent.");
}
}
+ /**
+ * Show dialog prompting the user to enable auto data switch
+ */
+ public void showEnableAutoDataSwitchDialog() {
+ final FragmentManager fragmentManager = getSupportFragmentManager();
+ SimDialogFragment fragment = createFragment(ENABLE_AUTO_DATA_SWITCH);
+ fragment.show(fragmentManager, Integer.toString(ENABLE_AUTO_DATA_SWITCH));
+ }
+
+ /**
+ * @param subId The sub Id to enable auto data switch
+ */
+ public void onEnableAutoDataSwitch(int subId) {
+ Log.d(TAG, "onEnableAutoDataSwitch subId:" + subId);
+ final TelephonyManager telephonyManager = getSystemService(
+ TelephonyManager.class).createForSubscriptionId(subId);
+ telephonyManager.setMobileDataPolicyEnabled(
+ TelephonyManager.MOBILE_DATA_POLICY_AUTO_DATA_SWITCH, true);
+ }
+
public void onFragmentDismissed(SimDialogFragment simDialogFragment) {
final List fragments = getSupportFragmentManager().getFragments();
- if (fragments.size() == 1 && fragments.get(0) == simDialogFragment) {
+ if (fragments.size() == 1 && fragments.get(0) == simDialogFragment
+ || simDialogFragment.getDialogType() == ENABLE_AUTO_DATA_SWITCH) {
+ Log.d(TAG, "onFragmentDismissed dialogType:" + simDialogFragment.getDialogType());
finishAndRemoveTask();
}
}
@@ -200,7 +227,8 @@ public class SimDialogActivity extends FragmentActivity {
TelephonyManager.class).createForSubscriptionId(subId);
subscriptionManager.setDefaultDataSubId(subId);
if (subId != SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
- telephonyManager.setDataEnabled(true);
+ telephonyManager.setDataEnabledForReason(TelephonyManager.DATA_ENABLED_REASON_USER,
+ true);
Toast.makeText(this, R.string.data_switch_started, Toast.LENGTH_LONG).show();
}
}
diff --git a/src/com/android/settings/sim/SimListDialogFragment.java b/src/com/android/settings/sim/SimListDialogFragment.java
index 245d31e8980..5b84d7b67c0 100644
--- a/src/com/android/settings/sim/SimListDialogFragment.java
+++ b/src/com/android/settings/sim/SimListDialogFragment.java
@@ -109,16 +109,18 @@ public class SimListDialogFragment extends SimDialogFragment {
* @param selectionIndex the index of item in the list.
*/
public void onClick(int selectionIndex) {
+ final SimDialogActivity activity = (SimDialogActivity) getActivity();
if (selectionIndex >= 0 && selectionIndex < mSubscriptions.size()) {
int subId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
final SubscriptionInfo subscription = mSubscriptions.get(selectionIndex);
if (subscription != null) {
subId = subscription.getSubscriptionId();
}
- final SimDialogActivity activity = (SimDialogActivity) getActivity();
activity.onSubscriptionSelected(getDialogType(), subId);
}
- dismiss();
+ Log.d(TAG, "Start showing auto data switch dialog");
+ activity.showEnableAutoDataSwitchDialog();
+ if (getDialog() != null) getDialog().dismiss();
}
protected List getCurrentSubscriptions() {
diff --git a/tests/robotests/src/com/android/settings/sim/EnableAutoDataSwitchDialogFragmentTest.java b/tests/robotests/src/com/android/settings/sim/EnableAutoDataSwitchDialogFragmentTest.java
new file mode 100644
index 00000000000..ad60d06dc04
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/sim/EnableAutoDataSwitchDialogFragmentTest.java
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2022 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 static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.spy;
+
+import android.content.Context;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
+
+import com.android.settings.R;
+import com.android.settings.testutils.shadow.ShadowAlertDialogCompat;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.Config;
+
+import java.util.Arrays;
+import java.util.Collections;
+
+@RunWith(RobolectricTestRunner.class)
+@Config(shadows = ShadowAlertDialogCompat.class)
+public class EnableAutoDataSwitchDialogFragmentTest
+ extends SimDialogFragmentTestBase {
+ private static final String SUMMARY = "fake summary";
+ private static final String WARNING = "fake warning";
+
+ // Mock
+ @Mock
+ private Context mContext;
+ @Mock
+ private SubscriptionManager mSubscriptionManager;
+ @Mock
+ private TelephonyManager mTelephonyManager;
+ @Mock
+ private UserManager mUserManager;
+
+ @Before
+ public void setUp() {
+ super.setUp();
+ mFragment = spy(EnableAutoDataSwitchDialogFragment.newInstance());
+ doReturn(mContext).when(mFragment).getContext();
+
+ doReturn(mSubscriptionManager).when(mContext).getSystemService(SubscriptionManager.class);
+ doReturn(mTelephonyManager).when(mContext).getSystemService(TelephonyManager.class);
+ doReturn(mTelephonyManager).when(mTelephonyManager).createForSubscriptionId(anyInt());
+ doReturn(mUserManager).when(mContext).getSystemService(UserManager.class);
+
+ doReturn(SIM1_ID).when(mFragment).getDefaultDataSubId();
+ doReturn(Arrays.asList(mSim1, mSim2)).when(mSubscriptionManager)
+ .getActiveSubscriptionInfoList();
+ doReturn(true).when(mUserManager)
+ .isManagedProfile(UserHandle.MIN_SECONDARY_USER_ID);
+
+ doReturn(SUMMARY).when(mContext).getString(
+ eq(R.string.enable_auto_data_switch_dialog_message), any());
+ doReturn(WARNING).when(mContext).getString(
+ R.string.auto_data_switch_dialog_managed_profile_warning);
+ }
+
+ @After
+ public void tearDown() {
+ mFragment = null;
+ }
+
+ @Test
+ public void updateDialog_getMessage_noDdsExists() {
+ doReturn(SubscriptionManager.INVALID_SUBSCRIPTION_ID).when(mFragment).getDefaultDataSubId();
+ String msg = mFragment.getMessage();
+ assertThat(msg).isEqualTo(null);
+ }
+
+ @Test
+ public void updateDialog_getMessage_noBackupSubExists() {
+ doReturn(Collections.singletonList(mSim1)).when(mSubscriptionManager)
+ .getActiveSubscriptionInfoList();
+ String msg = mFragment.getMessage();
+ assertThat(msg).isEqualTo(null);
+ }
+
+ @Test
+ public void updateDialog_getMessage_autoSwitchAlreadyEnabled() {
+ doReturn(true).when(mTelephonyManager).isMobileDataPolicyEnabled(
+ TelephonyManager.MOBILE_DATA_POLICY_AUTO_DATA_SWITCH);
+ String msg = mFragment.getMessage();
+ assertThat(msg).isEqualTo(null);
+ }
+
+ @Test
+ public void updateDialog_getMessage_noManagedProfile() {
+ UserHandle userHandle = UserHandle.of(UserHandle.USER_NULL);
+ UserHandle userHandle2 = UserHandle.of(UserHandle.USER_SYSTEM);
+ doReturn(userHandle).when(mSubscriptionManager).getSubscriptionUserHandle(SIM1_ID);
+ doReturn(userHandle2).when(mSubscriptionManager).getSubscriptionUserHandle(SIM2_ID);
+ String msg = mFragment.getMessage();
+ assertThat(msg).contains(SUMMARY);
+ assertThat(msg).doesNotContain(WARNING);
+ }
+
+ @Test
+ public void updateDialog_getMessage_hasManagedProfile() {
+ UserHandle userHandle = UserHandle.of(UserHandle.USER_NULL);
+ UserHandle userHandle2 = UserHandle.of(UserHandle.MIN_SECONDARY_USER_ID);
+ doReturn(userHandle).when(mSubscriptionManager).getSubscriptionUserHandle(SIM1_ID);
+ doReturn(userHandle2).when(mSubscriptionManager).getSubscriptionUserHandle(SIM2_ID);
+ String msg = mFragment.getMessage();
+ assertThat(msg).contains(SUMMARY);
+ assertThat(msg).contains(WARNING);
+ }
+
+ @Test
+ public void updateDialog_getMessage_BothManagedProfile() {
+ UserHandle userHandle = UserHandle.of(UserHandle.MIN_SECONDARY_USER_ID);
+ UserHandle userHandle2 = UserHandle.of(UserHandle.MIN_SECONDARY_USER_ID);
+ doReturn(userHandle).when(mSubscriptionManager).getSubscriptionUserHandle(SIM1_ID);
+ doReturn(userHandle2).when(mSubscriptionManager).getSubscriptionUserHandle(SIM2_ID);
+ String msg = mFragment.getMessage();
+ assertThat(msg).contains(SUMMARY);
+ assertThat(msg).doesNotContain(WARNING);
+ }
+}