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 package com.android.settings.sim.receivers;
17 
18 import android.content.BroadcastReceiver;
19 import android.content.Context;
20 import android.content.Intent;
21 import android.telephony.TelephonyManager;
22 import android.telephony.UiccCardInfo;
23 import android.telephony.UiccSlotInfo;
24 import android.telephony.euicc.EuiccManager;
25 import android.text.TextUtils;
26 import android.util.Log;
27 
28 import androidx.annotation.Nullable;
29 
30 import com.android.settings.R;
31 import com.android.settingslib.utils.ThreadUtils;
32 
33 import java.util.List;
34 
35 /** The receiver when the slot status changes. */
36 public class SimSlotChangeReceiver extends BroadcastReceiver {
37     private static final String TAG = "SlotChangeReceiver";
38 
39     private final SimSlotChangeHandler mSlotChangeHandler = SimSlotChangeHandler.get();
40     private final Object mLock = new Object();
41 
42     @Override
onReceive(Context context, Intent intent)43     public void onReceive(Context context, Intent intent) {
44 
45         String action = intent.getAction();
46         if (!TelephonyManager.ACTION_SIM_SLOT_STATUS_CHANGED.equals(action)) {
47             Log.e(TAG, "Ignore slot changes due to unexpected action: " + action);
48             return;
49         }
50 
51         final PendingResult pendingResult = goAsync();
52         ThreadUtils.postOnBackgroundThread(
53                 () -> {
54                     synchronized (mLock) {
55                         if (shouldHandleSlotChange(context)) {
56                             mSlotChangeHandler.onSlotsStatusChange(context.getApplicationContext());
57                         }
58                     }
59                     ThreadUtils.postOnMainThread(pendingResult::finish);
60                 });
61     }
62 
63     // Checks whether the slot event should be handled.
shouldHandleSlotChange(Context context)64     private boolean shouldHandleSlotChange(Context context) {
65         if (!context.getResources().getBoolean(R.bool.config_handle_sim_slot_change)) {
66             Log.i(TAG, "The flag is off. Ignore slot changes.");
67             return false;
68         }
69 
70         final EuiccManager euiccManager = context.getSystemService(EuiccManager.class);
71         if (euiccManager == null || !euiccManager.isEnabled()) {
72             Log.i(TAG, "Ignore slot changes because EuiccManager is disabled.");
73             return false;
74         }
75 
76         if (euiccManager.getOtaStatus() == EuiccManager.EUICC_OTA_IN_PROGRESS) {
77             Log.i(TAG, "Ignore slot changes because eSIM OTA is in progress.");
78             return false;
79         }
80 
81         if (!isSimSlotStateValid(context)) {
82             Log.i(TAG, "Ignore slot changes because SIM states are not valid.");
83             return false;
84         }
85 
86         return true;
87     }
88 
89     // Checks whether the SIM slot state is valid for slot change event.
isSimSlotStateValid(Context context)90     private boolean isSimSlotStateValid(Context context) {
91         final TelephonyManager telMgr = context.getSystemService(TelephonyManager.class);
92         UiccSlotInfo[] slotInfos = telMgr.getUiccSlotsInfo();
93         if (slotInfos == null) {
94             Log.e(TAG, "slotInfos is null. Unable to get slot infos.");
95             return false;
96         }
97 
98         boolean isAllCardStringsEmpty = true;
99         for (int i = 0; i < slotInfos.length; i++) {
100             UiccSlotInfo slotInfo = slotInfos[i];
101 
102             if (slotInfo == null) {
103                 return false;
104             }
105 
106             // After pSIM is inserted, there might be a short period that the status of both slots
107             // are not accurate. We drop the event if any of sim presence state is ERROR or
108             // RESTRICTED.
109             if (slotInfo.getCardStateInfo() == UiccSlotInfo.CARD_STATE_INFO_ERROR
110                     || slotInfo.getCardStateInfo() == UiccSlotInfo.CARD_STATE_INFO_RESTRICTED) {
111                 Log.i(TAG, "The SIM state is in an error. Drop the event. SIM info: " + slotInfo);
112                 return false;
113             }
114 
115             UiccCardInfo cardInfo = findUiccCardInfoBySlot(telMgr, i);
116             if (cardInfo == null) {
117                 continue;
118             }
119             if (!TextUtils.isEmpty(slotInfo.getCardId())
120                     || !TextUtils.isEmpty(cardInfo.getIccId())) {
121                 isAllCardStringsEmpty = false;
122             }
123         }
124 
125         // We also drop the event if both the card strings are empty, which usually means it's
126         // between SIM slots switch the slot status is not stable at this moment.
127         if (isAllCardStringsEmpty) {
128             Log.i(TAG, "All UICC card strings are empty. Drop this event.");
129             return false;
130         }
131 
132         return true;
133     }
134 
135     @Nullable
findUiccCardInfoBySlot(TelephonyManager telMgr, int physicalSlotIndex)136     private UiccCardInfo findUiccCardInfoBySlot(TelephonyManager telMgr, int physicalSlotIndex) {
137         List<UiccCardInfo> cardInfos = telMgr.getUiccCardsInfo();
138         if (cardInfos == null) {
139             return null;
140         }
141         return cardInfos.stream()
142                 .filter(info -> info.getSlotIndex() == physicalSlotIndex)
143                 .findFirst()
144                 .orElse(null);
145     }
146 }
147