1 /*
2  * Copyright (C) 2020 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.settings.sim.receivers;
18 
19 import static android.content.Context.MODE_PRIVATE;
20 
21 import android.content.Context;
22 import android.content.Intent;
23 import android.content.SharedPreferences;
24 import android.os.Looper;
25 import android.provider.Settings;
26 import android.telephony.SubscriptionInfo;
27 import android.telephony.SubscriptionManager;
28 import android.telephony.TelephonyManager;
29 import android.telephony.UiccSlotInfo;
30 import android.util.Log;
31 
32 import com.android.settings.network.SubscriptionUtil;
33 import com.android.settings.network.UiccSlotUtil;
34 import com.android.settings.network.UiccSlotsException;
35 import com.android.settings.sim.ChooseSimActivity;
36 import com.android.settings.sim.DsdsDialogActivity;
37 import com.android.settings.sim.SimActivationNotifier;
38 import com.android.settings.sim.SimNotificationService;
39 import com.android.settings.sim.SwitchToEsimConfirmDialogActivity;
40 
41 import com.google.common.collect.ImmutableList;
42 
43 import java.util.List;
44 import java.util.stream.Collectors;
45 
46 import javax.annotation.Nullable;
47 
48 /** Perform actions after a slot change event is triggered. */
49 public class SimSlotChangeHandler {
50     private static final String TAG = "SimSlotChangeHandler";
51 
52     private static final String EUICC_PREFS = "euicc_prefs";
53     // Shared preference keys
54     private static final String KEY_REMOVABLE_SLOT_STATE = "removable_slot_state";
55     private static final String KEY_SUW_PSIM_ACTION = "suw_psim_action";
56     // User's last removable SIM insertion / removal action during SUW.
57     private static final int LAST_USER_ACTION_IN_SUW_NONE = 0;
58     private static final int LAST_USER_ACTION_IN_SUW_INSERT = 1;
59     private static final int LAST_USER_ACTION_IN_SUW_REMOVE = 2;
60 
61     private static volatile SimSlotChangeHandler sSlotChangeHandler;
62 
63     /** Returns a SIM slot change handler singleton. */
get()64     public static SimSlotChangeHandler get() {
65         if (sSlotChangeHandler == null) {
66             synchronized (SimSlotChangeHandler.class) {
67                 if (sSlotChangeHandler == null) {
68                     sSlotChangeHandler = new SimSlotChangeHandler();
69                 }
70             }
71         }
72         return sSlotChangeHandler;
73     }
74 
75     private SubscriptionManager mSubMgr;
76     private TelephonyManager mTelMgr;
77     private Context mContext;
78 
onSlotsStatusChange(Context context)79     void onSlotsStatusChange(Context context) {
80         init(context);
81 
82         if (Looper.myLooper() == Looper.getMainLooper()) {
83             throw new IllegalStateException("Cannot be called from main thread.");
84         }
85 
86         if (mTelMgr.getActiveModemCount() > 1) {
87             Log.i(TAG, "The device is already in DSDS mode. Do nothing.");
88             return;
89         }
90 
91         UiccSlotInfo removableSlotInfo = getRemovableUiccSlotInfo();
92         if (removableSlotInfo == null) {
93             Log.e(TAG, "Unable to find the removable slot. Do nothing.");
94             return;
95         }
96 
97         int lastRemovableSlotState = getLastRemovableSimSlotState(mContext);
98         int currentRemovableSlotState = removableSlotInfo.getCardStateInfo();
99 
100         // Sets the current removable slot state.
101         setRemovableSimSlotState(mContext, currentRemovableSlotState);
102 
103         if (lastRemovableSlotState == UiccSlotInfo.CARD_STATE_INFO_ABSENT
104                 && currentRemovableSlotState == UiccSlotInfo.CARD_STATE_INFO_PRESENT) {
105             handleSimInsert(removableSlotInfo);
106             return;
107         }
108         if (lastRemovableSlotState == UiccSlotInfo.CARD_STATE_INFO_PRESENT
109                 && currentRemovableSlotState == UiccSlotInfo.CARD_STATE_INFO_ABSENT) {
110             handleSimRemove(removableSlotInfo);
111             return;
112         }
113         Log.i(TAG, "Do nothing on slot status changes.");
114     }
115 
onSuwFinish(Context context)116     void onSuwFinish(Context context) {
117         init(context);
118 
119         if (Looper.myLooper() == Looper.getMainLooper()) {
120             throw new IllegalStateException("Cannot be called from main thread.");
121         }
122 
123         if (mTelMgr.getActiveModemCount() > 1) {
124             Log.i(TAG, "The device is already in DSDS mode. Do nothing.");
125             return;
126         }
127 
128         UiccSlotInfo removableSlotInfo = getRemovableUiccSlotInfo();
129         if (removableSlotInfo == null) {
130             Log.e(TAG, "Unable to find the removable slot. Do nothing.");
131             return;
132         }
133 
134         boolean embeddedSimExist = getGroupedEmbeddedSubscriptions().size() != 0;
135         int removableSlotAction = getSuwRemovableSlotAction(mContext);
136         setSuwRemovableSlotAction(mContext, LAST_USER_ACTION_IN_SUW_NONE);
137 
138         if (embeddedSimExist
139                 && removableSlotInfo.getCardStateInfo() == UiccSlotInfo.CARD_STATE_INFO_PRESENT) {
140             if (mTelMgr.isMultiSimSupported() == TelephonyManager.MULTISIM_ALLOWED) {
141                 Log.i(TAG, "DSDS condition satisfied. Show notification.");
142                 SimNotificationService.scheduleSimNotification(
143                         mContext, SimActivationNotifier.NotificationType.ENABLE_DSDS);
144             } else if (removableSlotAction == LAST_USER_ACTION_IN_SUW_INSERT) {
145                 Log.i(
146                         TAG,
147                         "Both removable SIM and eSIM are present. DSDS condition doesn't"
148                             + " satisfied. User inserted pSIM during SUW. Show choose SIM"
149                             + " screen.");
150                 startChooseSimActivity(true);
151             }
152         } else if (removableSlotAction == LAST_USER_ACTION_IN_SUW_REMOVE) {
153             handleSimRemove(removableSlotInfo);
154         }
155     }
156 
init(Context context)157     private void init(Context context) {
158         mSubMgr =
159                 (SubscriptionManager)
160                         context.getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE);
161         mTelMgr = context.getSystemService(TelephonyManager.class);
162         mContext = context;
163     }
164 
handleSimInsert(UiccSlotInfo removableSlotInfo)165     private void handleSimInsert(UiccSlotInfo removableSlotInfo) {
166         Log.i(TAG, "Handle SIM inserted.");
167 
168         if (!isSuwFinished(mContext)) {
169             Log.i(TAG, "Still in SUW. Handle SIM insertion after SUW is finished");
170             setSuwRemovableSlotAction(mContext, LAST_USER_ACTION_IN_SUW_INSERT);
171             return;
172         }
173 
174         if (removableSlotInfo.getIsActive()) {
175             Log.i(TAG, "The removable slot is already active. Do nothing.");
176             return;
177         }
178 
179         if (hasActiveEsimSubscription()) {
180             if (mTelMgr.isMultiSimSupported() == TelephonyManager.MULTISIM_ALLOWED) {
181                 Log.i(TAG, "Enabled profile exists. DSDS condition satisfied.");
182                 startDsdsDialogActivity();
183             } else {
184                 Log.i(TAG, "Enabled profile exists. DSDS condition not satisfied.");
185                 startChooseSimActivity(true);
186             }
187             return;
188         }
189 
190         Log.i(
191                 TAG,
192                 "No enabled eSIM profile. Ready to switch to removable slot and show"
193                         + " notification.");
194         try {
195             UiccSlotUtil.switchToRemovableSlot(
196                     UiccSlotUtil.INVALID_PHYSICAL_SLOT_ID, mContext.getApplicationContext());
197         } catch (UiccSlotsException e) {
198             Log.e(TAG, "Failed to switch to removable slot.");
199             return;
200         }
201         SimNotificationService.scheduleSimNotification(
202                 mContext, SimActivationNotifier.NotificationType.SWITCH_TO_REMOVABLE_SLOT);
203     }
204 
handleSimRemove(UiccSlotInfo removableSlotInfo)205     private void handleSimRemove(UiccSlotInfo removableSlotInfo) {
206         Log.i(TAG, "Handle SIM removed.");
207 
208         if (!isSuwFinished(mContext)) {
209             Log.i(TAG, "Still in SUW. Handle SIM removal after SUW is finished");
210             setSuwRemovableSlotAction(mContext, LAST_USER_ACTION_IN_SUW_REMOVE);
211             return;
212         }
213 
214         List<SubscriptionInfo> groupedEmbeddedSubscriptions = getGroupedEmbeddedSubscriptions();
215 
216         if (groupedEmbeddedSubscriptions.size() == 0 || !removableSlotInfo.getIsActive()) {
217             Log.i(TAG, "eSIM slot is active or no subscriptions exist. Do nothing.");
218             return;
219         }
220 
221         // If there is only 1 eSIM profile exists, we ask the user if they want to switch to that
222         // profile.
223         if (groupedEmbeddedSubscriptions.size() == 1) {
224             Log.i(TAG, "Only 1 eSIM profile found. Ask user's consent to switch.");
225             startSwitchSlotConfirmDialogActivity(groupedEmbeddedSubscriptions.get(0));
226             return;
227         }
228 
229         // If there are more than 1 eSIM profiles installed, we show a screen to let users to choose
230         // the number they want to use.
231         Log.i(TAG, "Multiple eSIM profiles found. Ask user which subscription to use.");
232         startChooseSimActivity(false);
233     }
234 
getLastRemovableSimSlotState(Context context)235     private int getLastRemovableSimSlotState(Context context) {
236         final SharedPreferences prefs = context.getSharedPreferences(EUICC_PREFS, MODE_PRIVATE);
237         return prefs.getInt(KEY_REMOVABLE_SLOT_STATE, UiccSlotInfo.CARD_STATE_INFO_ABSENT);
238     }
239 
setRemovableSimSlotState(Context context, int state)240     private void setRemovableSimSlotState(Context context, int state) {
241         final SharedPreferences prefs = context.getSharedPreferences(EUICC_PREFS, MODE_PRIVATE);
242         prefs.edit().putInt(KEY_REMOVABLE_SLOT_STATE, state).apply();
243     }
244 
getSuwRemovableSlotAction(Context context)245     private int getSuwRemovableSlotAction(Context context) {
246         final SharedPreferences prefs = context.getSharedPreferences(EUICC_PREFS, MODE_PRIVATE);
247         return prefs.getInt(KEY_SUW_PSIM_ACTION, LAST_USER_ACTION_IN_SUW_NONE);
248     }
249 
setSuwRemovableSlotAction(Context context, int action)250     private void setSuwRemovableSlotAction(Context context, int action) {
251         final SharedPreferences prefs = context.getSharedPreferences(EUICC_PREFS, MODE_PRIVATE);
252         prefs.edit().putInt(KEY_SUW_PSIM_ACTION, action).apply();
253     }
254 
255     @Nullable
getRemovableUiccSlotInfo()256     private UiccSlotInfo getRemovableUiccSlotInfo() {
257         UiccSlotInfo[] slotInfos = mTelMgr.getUiccSlotsInfo();
258         if (slotInfos == null) {
259             Log.e(TAG, "slotInfos is null. Unable to get slot infos.");
260             return null;
261         }
262         for (UiccSlotInfo slotInfo : slotInfos) {
263             if (slotInfo != null && slotInfo.isRemovable()) {
264 
265                 return slotInfo;
266             }
267         }
268         return null;
269     }
270 
isSuwFinished(Context context)271     private static boolean isSuwFinished(Context context) {
272         try {
273             // DEVICE_PROVISIONED is 0 if still in setup wizard. 1 if setup completed.
274             return Settings.Global.getInt(
275                             context.getContentResolver(), Settings.Global.DEVICE_PROVISIONED)
276                     == 1;
277         } catch (Settings.SettingNotFoundException e) {
278             Log.e(TAG, "Cannot get DEVICE_PROVISIONED from the device.", e);
279             return false;
280         }
281     }
282 
hasActiveEsimSubscription()283     private boolean hasActiveEsimSubscription() {
284         List<SubscriptionInfo> activeSubs = SubscriptionUtil.getActiveSubscriptions(mSubMgr);
285         return activeSubs.stream().anyMatch(SubscriptionInfo::isEmbedded);
286     }
287 
getGroupedEmbeddedSubscriptions()288     private List<SubscriptionInfo> getGroupedEmbeddedSubscriptions() {
289         List<SubscriptionInfo> groupedSubscriptions =
290                 SubscriptionUtil.getSelectableSubscriptionInfoList(mContext);
291         if (groupedSubscriptions == null) {
292             return ImmutableList.of();
293         }
294         return ImmutableList.copyOf(
295                 groupedSubscriptions.stream()
296                         .filter(sub -> sub.isEmbedded())
297                         .collect(Collectors.toList()));
298     }
299 
startChooseSimActivity(boolean psimInserted)300     private void startChooseSimActivity(boolean psimInserted) {
301         Intent intent = ChooseSimActivity.getIntent(mContext);
302         intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
303         intent.putExtra(ChooseSimActivity.KEY_HAS_PSIM, psimInserted);
304         mContext.startActivity(intent);
305     }
306 
startSwitchSlotConfirmDialogActivity(SubscriptionInfo subscriptionInfo)307     private void startSwitchSlotConfirmDialogActivity(SubscriptionInfo subscriptionInfo) {
308         Intent intent = new Intent(mContext, SwitchToEsimConfirmDialogActivity.class);
309         intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
310         intent.putExtra(SwitchToEsimConfirmDialogActivity.KEY_SUB_TO_ENABLE, subscriptionInfo);
311         mContext.startActivity(intent);
312     }
313 
startDsdsDialogActivity()314     private void startDsdsDialogActivity() {
315         Intent intent = new Intent(mContext, DsdsDialogActivity.class);
316         intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
317         mContext.startActivity(intent);
318     }
319 
SimSlotChangeHandler()320     private SimSlotChangeHandler() {}
321 }
322