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.os.UserHandle;
26 import android.provider.Settings;
27 import android.telephony.SubscriptionInfo;
28 import android.telephony.SubscriptionManager;
29 import android.telephony.TelephonyManager;
30 import android.telephony.UiccCardInfo;
31 import android.telephony.UiccSlotInfo;
32 import android.util.Log;
33 
34 import com.android.settings.flags.Flags;
35 import com.android.settings.network.SubscriptionUtil;
36 import com.android.settings.network.UiccSlotUtil;
37 import com.android.settings.network.UiccSlotsException;
38 import com.android.settings.sim.ChooseSimActivity;
39 import com.android.settings.sim.DsdsDialogActivity;
40 import com.android.settings.sim.SimActivationNotifier;
41 import com.android.settings.sim.SimNotificationService;
42 import com.android.settings.sim.SwitchToEsimConfirmDialogActivity;
43 
44 import com.google.common.collect.ImmutableList;
45 
46 import java.util.List;
47 import java.util.stream.Collectors;
48 
49 import javax.annotation.Nullable;
50 
51 /** Perform actions after a slot change event is triggered. */
52 public class SimSlotChangeHandler {
53     private static final String TAG = "SimSlotChangeHandler";
54 
55     private static final String EUICC_PREFS = "euicc_prefs";
56     // Shared preference keys
57     private static final String KEY_REMOVABLE_SLOT_STATE = "removable_slot_state";
58     private static final String KEY_SUW_PSIM_ACTION = "suw_psim_action";
59     // User's last removable SIM insertion / removal action during SUW.
60     private static final int LAST_USER_ACTION_IN_SUW_NONE = 0;
61     private static final int LAST_USER_ACTION_IN_SUW_INSERT = 1;
62     private static final int LAST_USER_ACTION_IN_SUW_REMOVE = 2;
63 
64     private static volatile SimSlotChangeHandler sSlotChangeHandler;
65 
66     /** Returns a SIM slot change handler singleton. */
get()67     public static SimSlotChangeHandler get() {
68         if (sSlotChangeHandler == null) {
69             synchronized (SimSlotChangeHandler.class) {
70                 if (sSlotChangeHandler == null) {
71                     sSlotChangeHandler = new SimSlotChangeHandler();
72                 }
73             }
74         }
75         return sSlotChangeHandler;
76     }
77 
78     private SubscriptionManager mSubMgr;
79     private TelephonyManager mTelMgr;
80     private Context mContext;
81 
onSlotsStatusChange(Context context)82     void onSlotsStatusChange(Context context) {
83         init(context);
84 
85         if (Looper.myLooper() == Looper.getMainLooper()) {
86             throw new IllegalStateException("Cannot be called from main thread.");
87         }
88 
89         UiccSlotInfo removableSlotInfo = getRemovableUiccSlotInfo();
90         if (removableSlotInfo == null) {
91             Log.e(TAG, "Unable to find the removable slot. Do nothing.");
92             return;
93         }
94 
95         int lastRemovableSlotState = getLastRemovableSimSlotState(mContext);
96         int currentRemovableSlotState = removableSlotInfo.getCardStateInfo();
97         Log.i(TAG,
98                 "lastRemovableSlotState: " + lastRemovableSlotState + ",currentRemovableSlotState: "
99                         + currentRemovableSlotState);
100         boolean isRemovableSimInserted =
101                 lastRemovableSlotState == UiccSlotInfo.CARD_STATE_INFO_ABSENT
102                         && currentRemovableSlotState == UiccSlotInfo.CARD_STATE_INFO_PRESENT;
103         boolean isRemovableSimRemoved =
104                 lastRemovableSlotState == UiccSlotInfo.CARD_STATE_INFO_PRESENT
105                         && currentRemovableSlotState == UiccSlotInfo.CARD_STATE_INFO_ABSENT;
106 
107         // Sets the current removable slot state.
108         setRemovableSimSlotState(mContext, currentRemovableSlotState);
109 
110         if (mTelMgr.getActiveModemCount() > 1) {
111             if (!isRemovableSimInserted) {
112                 Log.d(TAG, "Removable Sim is not inserted in DSDS mode. Do nothing.");
113                 return;
114             }
115 
116             if (Flags.isDualSimOnboardingEnabled()) {
117                 // ForNewUi, when the user inserts the psim, showing the sim onboarding for the user
118                 // to setup the sim switching or the default data subscription.
119                 handleRemovableSimInsertWhenDsds();
120             } else if (!isMultipleEnabledProfilesSupported()) {
121                 Log.d(TAG, "The device is already in DSDS mode and no MEP. Do nothing.");
122                 return;
123             } else if (isMultipleEnabledProfilesSupported()) {
124                 handleRemovableSimInsertUnderDsdsMep(removableSlotInfo);
125                 return;
126             }
127             Log.d(TAG, "the device is already in DSDS mode and have the DDS. Do nothing.");
128             return;
129         }
130 
131         if (isRemovableSimInserted) {
132             handleSimInsert(removableSlotInfo);
133             return;
134         }
135         if (isRemovableSimRemoved) {
136             handleSimRemove(removableSlotInfo);
137             return;
138         }
139         Log.i(TAG, "Do nothing on slot status changes.");
140     }
141 
onSuwFinish(Context context)142     void onSuwFinish(Context context) {
143         init(context);
144 
145         if (Looper.myLooper() == Looper.getMainLooper()) {
146             throw new IllegalStateException("Cannot be called from main thread.");
147         }
148 
149         if (mTelMgr.getActiveModemCount() > 1) {
150             Log.i(TAG, "The device is already in DSDS mode. Do nothing.");
151             return;
152         }
153 
154         UiccSlotInfo removableSlotInfo = getRemovableUiccSlotInfo();
155         if (removableSlotInfo == null) {
156             Log.e(TAG, "Unable to find the removable slot. Do nothing.");
157             return;
158         }
159 
160         boolean embeddedSimExist = getGroupedEmbeddedSubscriptions().size() != 0;
161         int removableSlotAction = getSuwRemovableSlotAction(mContext);
162         setSuwRemovableSlotAction(mContext, LAST_USER_ACTION_IN_SUW_NONE);
163 
164         if (embeddedSimExist
165                 && removableSlotInfo.getCardStateInfo() == UiccSlotInfo.CARD_STATE_INFO_PRESENT) {
166             if (mTelMgr.isMultiSimSupported() == TelephonyManager.MULTISIM_ALLOWED) {
167                 Log.i(TAG, "DSDS condition satisfied. Show notification.");
168                 SimNotificationService.scheduleSimNotification(
169                         mContext, SimActivationNotifier.NotificationType.ENABLE_DSDS);
170             } else if (removableSlotAction == LAST_USER_ACTION_IN_SUW_INSERT) {
171                 Log.i(
172                         TAG,
173                         "Both removable SIM and eSIM are present. DSDS condition doesn't"
174                                 + " satisfied. User inserted pSIM during SUW. Show choose SIM"
175                                 + " screen.");
176                 startChooseSimActivity(true);
177             }
178         } else if (removableSlotAction == LAST_USER_ACTION_IN_SUW_REMOVE) {
179             handleSimRemove(removableSlotInfo);
180         }
181     }
182 
init(Context context)183     private void init(Context context) {
184         mSubMgr =
185                 (SubscriptionManager)
186                         context.getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE);
187         mTelMgr = context.getSystemService(TelephonyManager.class);
188         mContext = context;
189     }
190 
handleSimInsert(UiccSlotInfo removableSlotInfo)191     private void handleSimInsert(UiccSlotInfo removableSlotInfo) {
192         Log.i(TAG, "Handle SIM inserted.");
193         if (!isSuwFinished(mContext)) {
194             Log.i(TAG, "Still in SUW. Handle SIM insertion after SUW is finished");
195             setSuwRemovableSlotAction(mContext, LAST_USER_ACTION_IN_SUW_INSERT);
196             return;
197         }
198         if (removableSlotInfo.getPorts().stream().findFirst().get().isActive()) {
199             Log.i(TAG, "The removable slot is already active. Do nothing.");
200             return;
201         }
202 
203         if (hasActiveEsimSubscription()) {
204             if (mTelMgr.isMultiSimSupported() == TelephonyManager.MULTISIM_ALLOWED) {
205                 Log.i(TAG, "Enabled profile exists. DSDS condition satisfied.");
206                 startDsdsDialogActivity();
207             } else {
208                 Log.i(TAG, "Enabled profile exists. DSDS condition not satisfied.");
209                 startChooseSimActivity(true);
210             }
211             return;
212         }
213 
214         Log.i(
215                 TAG,
216                 "No enabled eSIM profile. Ready to switch to removable slot and show"
217                         + " notification.");
218         try {
219             UiccSlotUtil.switchToRemovableSlot(
220                     UiccSlotUtil.INVALID_PHYSICAL_SLOT_ID, mContext.getApplicationContext());
221         } catch (UiccSlotsException e) {
222             Log.e(TAG, "Failed to switch to removable slot.");
223             return;
224         }
225         SimNotificationService.scheduleSimNotification(
226                 mContext, SimActivationNotifier.NotificationType.SWITCH_TO_REMOVABLE_SLOT);
227     }
228 
handleSimRemove(UiccSlotInfo removableSlotInfo)229     private void handleSimRemove(UiccSlotInfo removableSlotInfo) {
230         Log.i(TAG, "Handle SIM removed.");
231 
232         if (!isSuwFinished(mContext)) {
233             Log.i(TAG, "Still in SUW. Handle SIM removal after SUW is finished");
234             setSuwRemovableSlotAction(mContext, LAST_USER_ACTION_IN_SUW_REMOVE);
235             return;
236         }
237 
238         List<SubscriptionInfo> groupedEmbeddedSubscriptions = getGroupedEmbeddedSubscriptions();
239         if (groupedEmbeddedSubscriptions.size() == 0 || !removableSlotInfo.getPorts().stream()
240                 .findFirst().get().isActive()) {
241             Log.i(TAG, "eSIM slot is active or no subscriptions exist. Do nothing."
242                     + " The removableSlotInfo: " + removableSlotInfo
243                     + ", groupedEmbeddedSubscriptions: " + groupedEmbeddedSubscriptions);
244             return;
245         }
246 
247         // If there is only 1 eSIM profile exists, we ask the user if they want to switch to that
248         // profile.
249         if (groupedEmbeddedSubscriptions.size() == 1) {
250             Log.i(TAG, "Only 1 eSIM profile found. Ask user's consent to switch.");
251             startSwitchSlotConfirmDialogActivity(groupedEmbeddedSubscriptions.get(0));
252             return;
253         }
254 
255         // If there are more than 1 eSIM profiles installed, we show a screen to let users to choose
256         // the number they want to use.
257         Log.i(TAG, "Multiple eSIM profiles found. Ask user which subscription to use.");
258         startChooseSimActivity(false);
259     }
260 
handleRemovableSimInsertWhenDsds()261     private void handleRemovableSimInsertWhenDsds() {
262         List<SubscriptionInfo> subscriptionInfos = getAvailableRemovableSubscription();
263         if (subscriptionInfos.isEmpty()) {
264             Log.e(TAG, "Unable to find the removable subscriptionInfo. Do nothing.");
265             return;
266         }
267         Log.d(TAG, "ForNewUi and getAvailableRemovableSubscription:"
268                 + subscriptionInfos);
269         startSimConfirmDialogActivity(subscriptionInfos.get(0).getSubscriptionId());
270     }
271 
handleRemovableSimInsertUnderDsdsMep(UiccSlotInfo removableSlotInfo)272     private void handleRemovableSimInsertUnderDsdsMep(UiccSlotInfo removableSlotInfo) {
273         Log.i(TAG, "Handle Removable SIM inserted under DSDS+Mep.");
274 
275         if (removableSlotInfo.getPorts().stream().findFirst().get().isActive()) {
276             Log.i(TAG, "The removable slot is already active. Do nothing. removableSlotInfo: "
277                     + removableSlotInfo);
278             return;
279         }
280 
281         List<SubscriptionInfo> subscriptionInfos = getAvailableRemovableSubscription();
282         if (subscriptionInfos.isEmpty()) {
283             Log.e(TAG, "Unable to find the removable subscriptionInfo. Do nothing.");
284             return;
285         }
286         Log.d(TAG, "getAvailableRemovableSubscription:" + subscriptionInfos);
287         startSimConfirmDialogActivity(subscriptionInfos.get(0).getSubscriptionId());
288     }
289 
getLastRemovableSimSlotState(Context context)290     private int getLastRemovableSimSlotState(Context context) {
291         final SharedPreferences prefs = context.getSharedPreferences(EUICC_PREFS, MODE_PRIVATE);
292         return prefs.getInt(KEY_REMOVABLE_SLOT_STATE, UiccSlotInfo.CARD_STATE_INFO_ABSENT);
293     }
294 
setRemovableSimSlotState(Context context, int state)295     private void setRemovableSimSlotState(Context context, int state) {
296         final SharedPreferences prefs = context.getSharedPreferences(EUICC_PREFS, MODE_PRIVATE);
297         prefs.edit().putInt(KEY_REMOVABLE_SLOT_STATE, state).apply();
298         Log.d(TAG, "setRemovableSimSlotState: " + state);
299     }
300 
getSuwRemovableSlotAction(Context context)301     private int getSuwRemovableSlotAction(Context context) {
302         final SharedPreferences prefs = context.getSharedPreferences(EUICC_PREFS, MODE_PRIVATE);
303         return prefs.getInt(KEY_SUW_PSIM_ACTION, LAST_USER_ACTION_IN_SUW_NONE);
304     }
305 
setSuwRemovableSlotAction(Context context, int action)306     private void setSuwRemovableSlotAction(Context context, int action) {
307         final SharedPreferences prefs = context.getSharedPreferences(EUICC_PREFS, MODE_PRIVATE);
308         prefs.edit().putInt(KEY_SUW_PSIM_ACTION, action).apply();
309     }
310 
311     @Nullable
getRemovableUiccSlotInfo()312     private UiccSlotInfo getRemovableUiccSlotInfo() {
313         UiccSlotInfo[] slotInfos = mTelMgr.getUiccSlotsInfo();
314         if (slotInfos == null) {
315             Log.e(TAG, "slotInfos is null. Unable to get slot infos.");
316             return null;
317         }
318         for (UiccSlotInfo slotInfo : slotInfos) {
319             if (slotInfo != null && slotInfo.isRemovable()) {
320                 return slotInfo;
321             }
322         }
323         return null;
324     }
325 
isSuwFinished(Context context)326     private static boolean isSuwFinished(Context context) {
327         try {
328             // DEVICE_PROVISIONED is 0 if still in setup wizard. 1 if setup completed.
329             return Settings.Global.getInt(
330                     context.getContentResolver(), Settings.Global.DEVICE_PROVISIONED)
331                     == 1;
332         } catch (Settings.SettingNotFoundException e) {
333             Log.e(TAG, "Cannot get DEVICE_PROVISIONED from the device.", e);
334             return false;
335         }
336     }
337 
hasActiveEsimSubscription()338     private boolean hasActiveEsimSubscription() {
339         List<SubscriptionInfo> activeSubs = SubscriptionUtil.getActiveSubscriptions(mSubMgr);
340         return activeSubs.stream().anyMatch(SubscriptionInfo::isEmbedded);
341     }
342 
getGroupedEmbeddedSubscriptions()343     private List<SubscriptionInfo> getGroupedEmbeddedSubscriptions() {
344         List<SubscriptionInfo> groupedSubscriptions =
345                 SubscriptionUtil.getSelectableSubscriptionInfoList(mContext);
346         if (groupedSubscriptions == null) {
347             return ImmutableList.of();
348         }
349         return ImmutableList.copyOf(
350                 groupedSubscriptions.stream()
351                         .filter(sub -> sub.isEmbedded())
352                         .collect(Collectors.toList()));
353     }
354 
getAvailableRemovableSubscription()355     protected List<SubscriptionInfo> getAvailableRemovableSubscription() {
356         List<SubscriptionInfo> removableSubscriptions =
357                 SubscriptionUtil.getAvailableSubscriptions(mContext);
358         return ImmutableList.copyOf(
359                 removableSubscriptions.stream()
360                         // ToDo: This condition is for psim only. If device supports removable
361                         //  esim, it needs an new condition.
362                         .filter(sub -> !sub.isEmbedded())
363                         .collect(Collectors.toList()));
364     }
365 
startChooseSimActivity(boolean psimInserted)366     private void startChooseSimActivity(boolean psimInserted) {
367         Intent intent = ChooseSimActivity.getIntent(mContext);
368         intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
369         intent.putExtra(ChooseSimActivity.KEY_HAS_PSIM, psimInserted);
370         mContext.startActivityAsUser(intent, UserHandle.SYSTEM);
371     }
372 
startSwitchSlotConfirmDialogActivity(SubscriptionInfo subscriptionInfo)373     private void startSwitchSlotConfirmDialogActivity(SubscriptionInfo subscriptionInfo) {
374         Intent intent = new Intent(mContext, SwitchToEsimConfirmDialogActivity.class);
375         intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
376         intent.putExtra(SwitchToEsimConfirmDialogActivity.KEY_SUB_TO_ENABLE, subscriptionInfo);
377         mContext.startActivityAsUser(intent, UserHandle.SYSTEM);
378     }
379 
startDsdsDialogActivity()380     private void startDsdsDialogActivity() {
381         Intent intent = new Intent(mContext, DsdsDialogActivity.class);
382         intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
383         mContext.startActivityAsUser(intent, UserHandle.SYSTEM);
384     }
385 
startSimConfirmDialogActivity(int subId)386     private void startSimConfirmDialogActivity(int subId) {
387         if (!isSuwFinished(mContext)) {
388             Log.d(TAG, "Still in SUW. Do nothing");
389             return;
390         }
391         if (!SubscriptionManager.isUsableSubscriptionId(subId)) {
392             Log.i(TAG, "Unable to enable subscription due to invalid subscription ID.");
393             return;
394         }
395         Log.d(TAG, "Start ToggleSubscriptionDialogActivity with " + subId + " under DSDS+Mep.");
396         SubscriptionUtil.startToggleSubscriptionDialogActivity(mContext, subId, true);
397     }
398 
isMultipleEnabledProfilesSupported()399     private boolean isMultipleEnabledProfilesSupported() {
400         List<UiccCardInfo> cardInfos = mTelMgr.getUiccCardsInfo();
401         if (cardInfos == null) {
402             Log.d(TAG, "UICC cards info list is empty.");
403             return false;
404         }
405         return cardInfos.stream().anyMatch(
406                 cardInfo -> cardInfo.isMultipleEnabledProfilesSupported());
407     }
408 
SimSlotChangeHandler()409     private SimSlotChangeHandler() {}
410 }
411