diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 81a5ea878f0..f3066ce33ff 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -3661,6 +3661,14 @@
+
+
+
+
+
+
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 205e1bac562..bc86338fa1c 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -12144,6 +12144,10 @@
Switched to another carrier
Your mobile network has changed
+
+ Set up your other SIM
+
+ Choose your active SIM or use 2 SIMs at once
diff --git a/src/com/android/settings/sim/SimActivationNotifier.java b/src/com/android/settings/sim/SimActivationNotifier.java
index a38816a6d1b..735cb463c3e 100644
--- a/src/com/android/settings/sim/SimActivationNotifier.java
+++ b/src/com/android/settings/sim/SimActivationNotifier.java
@@ -66,12 +66,15 @@ public class SimActivationNotifier {
value = {
NotificationType.NETWORK_CONFIG,
NotificationType.SWITCH_TO_REMOVABLE_SLOT,
+ NotificationType.ENABLE_DSDS,
})
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;
+ // The notification to notify users that the device is capable of DSDS.
+ int ENABLE_DSDS = 3;
}
private final Context mContext;
@@ -120,8 +123,8 @@ public class SimActivationNotifier {
return;
}
- CharSequence displayName = SubscriptionUtil.getUniqueSubscriptionDisplayName(
- activeRemovableSub, mContext);
+ CharSequence displayName =
+ SubscriptionUtil.getUniqueSubscriptionDisplayName(activeRemovableSub, mContext);
String carrierName =
TextUtils.isEmpty(displayName)
? mContext.getString(R.string.sim_card_label)
@@ -135,7 +138,8 @@ public class SimActivationNotifier {
TaskStackBuilder.create(mContext).addNextIntent(clickIntent);
PendingIntent contentIntent =
stackBuilder.getPendingIntent(
- 0 /* requestCode */, PendingIntent.FLAG_UPDATE_CURRENT);
+ 0 /* requestCode */,
+ PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
Notification.Builder builder =
new Notification.Builder(mContext, SIM_SETUP_CHANNEL_ID)
@@ -155,7 +159,8 @@ public class SimActivationNotifier {
TaskStackBuilder.create(mContext).addNextIntent(clickIntent);
PendingIntent contentIntent =
stackBuilder.getPendingIntent(
- 0 /* requestCode */, PendingIntent.FLAG_UPDATE_CURRENT);
+ 0 /* requestCode */,
+ PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
String titleText =
TextUtils.isEmpty(carrierName)
? mContext.getString(
@@ -178,6 +183,33 @@ public class SimActivationNotifier {
mNotificationManager.notify(SWITCH_TO_REMOVABLE_SLOT_NOTIFICATION_ID, builder.build());
}
+ /** Sends a push notification for enabling DSDS. */
+ public void sendEnableDsdsNotification() {
+ Intent parentIntent = new Intent(mContext, Settings.MobileNetworkListActivity.class);
+
+ Intent clickIntent = new Intent(mContext, DsdsDialogActivity.class);
+
+ TaskStackBuilder stackBuilder =
+ TaskStackBuilder.create(mContext)
+ .addNextIntentWithParentStack(parentIntent)
+ .addNextIntent(clickIntent);
+ PendingIntent contentIntent =
+ stackBuilder.getPendingIntent(
+ 0 /* requestCode */,
+ PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
+
+ Notification.Builder builder =
+ new Notification.Builder(mContext, SIM_SETUP_CHANNEL_ID)
+ .setContentTitle(
+ mContext.getString(R.string.dsds_notification_after_suw_title))
+ .setContentText(
+ mContext.getString(R.string.dsds_notification_after_suw_text))
+ .setContentIntent(contentIntent)
+ .setSmallIcon(R.drawable.ic_sim_alert)
+ .setAutoCancel(true);
+ mNotificationManager.notify(SIM_ACTIVATION_NOTIFICATION_ID, builder.build());
+ }
+
@Nullable
private SubscriptionInfo getActiveRemovableSub() {
SubscriptionManager subscriptionManager =
diff --git a/src/com/android/settings/sim/SimNotificationService.java b/src/com/android/settings/sim/SimNotificationService.java
index 0f52c8b70fd..42b5e58cd8b 100644
--- a/src/com/android/settings/sim/SimNotificationService.java
+++ b/src/com/android/settings/sim/SimNotificationService.java
@@ -71,6 +71,9 @@ public class SimNotificationService extends JobService {
case SimActivationNotifier.NotificationType.SWITCH_TO_REMOVABLE_SLOT:
new SimActivationNotifier(this).sendSwitchedToRemovableSlotNotification();
break;
+ case SimActivationNotifier.NotificationType.ENABLE_DSDS:
+ new SimActivationNotifier(this).sendEnableDsdsNotification();
+ break;
default:
Log.e(TAG, "Invalid notification type: " + notificationType);
break;
diff --git a/src/com/android/settings/sim/receivers/SimSlotChangeHandler.java b/src/com/android/settings/sim/receivers/SimSlotChangeHandler.java
index c09242861ad..fe44389c6b6 100644
--- a/src/com/android/settings/sim/receivers/SimSlotChangeHandler.java
+++ b/src/com/android/settings/sim/receivers/SimSlotChangeHandler.java
@@ -50,7 +50,13 @@ public class SimSlotChangeHandler {
private static final String TAG = "SimSlotChangeHandler";
private static final String EUICC_PREFS = "euicc_prefs";
+ // Shared preference keys
private static final String KEY_REMOVABLE_SLOT_STATE = "removable_slot_state";
+ private static final String KEY_SUW_PSIM_ACTION = "suw_psim_action";
+ // User's last removable SIM insertion / removal action during SUW.
+ private static final int LAST_USER_ACTION_IN_SUW_NONE = 0;
+ private static final int LAST_USER_ACTION_IN_SUW_INSERT = 1;
+ private static final int LAST_USER_ACTION_IN_SUW_REMOVE = 2;
private static volatile SimSlotChangeHandler sSlotChangeHandler;
@@ -107,6 +113,47 @@ public class SimSlotChangeHandler {
Log.i(TAG, "Do nothing on slot status changes.");
}
+ void onSuwFinish(Context context) {
+ init(context);
+
+ if (Looper.myLooper() == Looper.getMainLooper()) {
+ throw new IllegalStateException("Cannot be called from main thread.");
+ }
+
+ if (mTelMgr.getActiveModemCount() > 1) {
+ Log.i(TAG, "The device is already in DSDS mode. Do nothing.");
+ return;
+ }
+
+ UiccSlotInfo removableSlotInfo = getRemovableUiccSlotInfo();
+ if (removableSlotInfo == null) {
+ Log.e(TAG, "Unable to find the removable slot. Do nothing.");
+ return;
+ }
+
+ boolean embeddedSimExist = getGroupedEmbeddedSubscriptions().size() != 0;
+ int removableSlotAction = getSuwRemovableSlotAction(mContext);
+ setSuwRemovableSlotAction(mContext, LAST_USER_ACTION_IN_SUW_NONE);
+
+ if (embeddedSimExist
+ && removableSlotInfo.getCardStateInfo() == UiccSlotInfo.CARD_STATE_INFO_PRESENT) {
+ if (mTelMgr.isMultiSimSupported() == TelephonyManager.MULTISIM_ALLOWED) {
+ Log.i(TAG, "DSDS condition satisfied. Show notification.");
+ SimNotificationService.scheduleSimNotification(
+ mContext, SimActivationNotifier.NotificationType.ENABLE_DSDS);
+ } else if (removableSlotAction == LAST_USER_ACTION_IN_SUW_INSERT) {
+ Log.i(
+ TAG,
+ "Both removable SIM and eSIM are present. DSDS condition doesn't"
+ + " satisfied. User inserted pSIM during SUW. Show choose SIM"
+ + " screen.");
+ startChooseSimActivity(true);
+ }
+ } else if (removableSlotAction == LAST_USER_ACTION_IN_SUW_REMOVE) {
+ handleSimRemove(removableSlotInfo);
+ }
+ }
+
private void init(Context context) {
mSubMgr =
(SubscriptionManager)
@@ -116,11 +163,11 @@ public class SimSlotChangeHandler {
}
private void handleSimInsert(UiccSlotInfo removableSlotInfo) {
- Log.i(TAG, "Detect SIM inserted.");
+ Log.i(TAG, "Handle SIM inserted.");
if (!isSuwFinished(mContext)) {
- // TODO(b/170508680): Store the action and handle it after SUW is finished.
Log.i(TAG, "Still in SUW. Handle SIM insertion after SUW is finished");
+ setSuwRemovableSlotAction(mContext, LAST_USER_ACTION_IN_SUW_INSERT);
return;
}
@@ -156,11 +203,11 @@ public class SimSlotChangeHandler {
}
private void handleSimRemove(UiccSlotInfo removableSlotInfo) {
- Log.i(TAG, "Detect SIM removed.");
+ Log.i(TAG, "Handle SIM removed.");
if (!isSuwFinished(mContext)) {
- // TODO(b/170508680): Store the action and handle it after SUW is finished.
Log.i(TAG, "Still in SUW. Handle SIM removal after SUW is finished");
+ setSuwRemovableSlotAction(mContext, LAST_USER_ACTION_IN_SUW_REMOVE);
return;
}
@@ -195,6 +242,16 @@ public class SimSlotChangeHandler {
prefs.edit().putInt(KEY_REMOVABLE_SLOT_STATE, state).apply();
}
+ private int getSuwRemovableSlotAction(Context context) {
+ final SharedPreferences prefs = context.getSharedPreferences(EUICC_PREFS, MODE_PRIVATE);
+ return prefs.getInt(KEY_SUW_PSIM_ACTION, LAST_USER_ACTION_IN_SUW_NONE);
+ }
+
+ private void setSuwRemovableSlotAction(Context context, int action) {
+ final SharedPreferences prefs = context.getSharedPreferences(EUICC_PREFS, MODE_PRIVATE);
+ prefs.edit().putInt(KEY_SUW_PSIM_ACTION, action).apply();
+ }
+
@Nullable
private UiccSlotInfo getRemovableUiccSlotInfo() {
UiccSlotInfo[] slotInfos = mTelMgr.getUiccSlotsInfo();
diff --git a/src/com/android/settings/sim/receivers/SuwFinishReceiver.java b/src/com/android/settings/sim/receivers/SuwFinishReceiver.java
new file mode 100644
index 00000000000..7facbe25f85
--- /dev/null
+++ b/src/com/android/settings/sim/receivers/SuwFinishReceiver.java
@@ -0,0 +1,51 @@
+/*
+ * 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.receivers;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.util.Log;
+
+import com.android.settings.R;
+import com.android.settingslib.utils.ThreadUtils;
+
+/** The receiver when SUW is finished. */
+public class SuwFinishReceiver extends BroadcastReceiver {
+ private static final String TAG = "SuwFinishReceiver";
+
+ private final SimSlotChangeHandler mSlotChangeHandler = SimSlotChangeHandler.get();
+ private final Object mLock = new Object();
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (!context.getResources().getBoolean(R.bool.config_handle_sim_slot_change)) {
+ Log.i(TAG, "The flag is off. Ignore SUW finish event.");
+ return;
+ }
+
+ final BroadcastReceiver.PendingResult pendingResult = goAsync();
+ ThreadUtils.postOnBackgroundThread(
+ () -> {
+ synchronized (mLock) {
+ Log.i(TAG, "Detected SUW finished. Checking slot events.");
+ mSlotChangeHandler.onSuwFinish(context.getApplicationContext());
+ }
+ ThreadUtils.postOnMainThread(pendingResult::finish);
+ });
+ }
+}