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.UiccPortInfo;
24 import android.telephony.UiccSlotInfo;
25 import android.telephony.euicc.EuiccManager;
26 import android.text.TextUtils;
27 import android.util.Log;
28 
29 import androidx.annotation.Nullable;
30 
31 import com.android.settings.R;
32 import com.android.settings.network.SatelliteRepository;
33 
34 import com.google.common.util.concurrent.ListenableFuture;
35 
36 import java.util.List;
37 import java.util.concurrent.ExecutionException;
38 import java.util.concurrent.Executor;
39 import java.util.concurrent.Executors;
40 
41 /** The receiver when the slot status changes. */
42 public class SimSlotChangeReceiver extends BroadcastReceiver {
43     private static final String TAG = "SlotChangeReceiver";
44 
45     @Override
onReceive(Context context, Intent intent)46     public void onReceive(Context context, Intent intent) {
47 
48         String action = intent.getAction();
49         if (!TelephonyManager.ACTION_SIM_SLOT_STATUS_CHANGED.equals(action)) {
50             Log.e(TAG, "Ignore slot changes due to unexpected action: " + action);
51             return;
52         }
53 
54         SimSlotChangeService.scheduleSimSlotChange(context);
55     }
56 
runOnBackgroundThread(Context context)57     public static void runOnBackgroundThread(Context context) {
58         if (shouldHandleSlotChange(context)) {
59             Log.d(TAG, "Checking satellite session status");
60             Executor executor = Executors.newSingleThreadExecutor();
61             ListenableFuture<Boolean> isSatelliteSessionStartedFuture =
62                     new SatelliteRepository(context).requestIsSessionStarted(executor);
63             isSatelliteSessionStartedFuture.addListener(() -> {
64                 boolean isSatelliteSessionStarted = false;
65                 try {
66                     isSatelliteSessionStarted = isSatelliteSessionStartedFuture.get();
67                 } catch (ExecutionException | InterruptedException e) {
68                     Log.w(TAG, "Can't get satellite session status", e);
69                 }
70 
71                 if (isSatelliteSessionStarted) {
72                     Log.i(TAG, "Device is in a satellite session. Unable to handle SIM slot"
73                             + " changes");
74                 } else {
75                     Log.i(TAG, "Not in a satellite session. Handle slot changes");
76                     SimSlotChangeHandler.get().onSlotsStatusChange(context.getApplicationContext());
77                 }
78             }, executor);
79         }
80     }
81 
82     // Checks whether the slot event should be handled.
shouldHandleSlotChange(Context context)83     private static boolean shouldHandleSlotChange(Context context) {
84         if (!context.getResources().getBoolean(R.bool.config_handle_sim_slot_change)) {
85             Log.i(TAG, "The flag is off. Ignore slot changes.");
86             return false;
87         }
88 
89         final EuiccManager euiccManager = context.getSystemService(EuiccManager.class);
90         if (euiccManager == null || !euiccManager.isEnabled()) {
91             Log.i(TAG, "Ignore slot changes because EuiccManager is disabled.");
92             return false;
93         }
94 
95         if (euiccManager.getOtaStatus() == EuiccManager.EUICC_OTA_IN_PROGRESS) {
96             Log.i(TAG, "Ignore slot changes because eSIM OTA is in progress.");
97             return false;
98         }
99 
100         if (!isSimSlotStateValid(context)) {
101             Log.i(TAG, "Ignore slot changes because SIM states are not valid.");
102             return false;
103         }
104 
105         return true;
106     }
107 
108     // Checks whether the SIM slot state is valid for slot change event.
isSimSlotStateValid(Context context)109     private static boolean isSimSlotStateValid(Context context) {
110         final TelephonyManager telMgr = context.getSystemService(TelephonyManager.class);
111         UiccSlotInfo[] slotInfos = telMgr.getUiccSlotsInfo();
112         if (slotInfos == null) {
113             Log.e(TAG, "slotInfos is null. Unable to get slot infos.");
114             return false;
115         }
116 
117         boolean isAllCardStringsEmpty = true;
118         for (int i = 0; i < slotInfos.length; i++) {
119             UiccSlotInfo slotInfo = slotInfos[i];
120 
121             if (slotInfo == null) {
122                 return false;
123             }
124 
125             // After pSIM is inserted, there might be a short period that the status of both slots
126             // are not accurate. We drop the event if any of sim presence state is ERROR or
127             // RESTRICTED.
128             if (slotInfo.getCardStateInfo() == UiccSlotInfo.CARD_STATE_INFO_ERROR
129                     || slotInfo.getCardStateInfo() == UiccSlotInfo.CARD_STATE_INFO_RESTRICTED) {
130                 Log.i(TAG, "The SIM state is in an error. Drop the event. SIM info: " + slotInfo);
131                 return false;
132             }
133 
134             UiccCardInfo cardInfo = findUiccCardInfoBySlot(telMgr, i);
135             if (cardInfo == null) {
136                 continue;
137             }
138             for (UiccPortInfo portInfo : cardInfo.getPorts()) {
139                 if (!TextUtils.isEmpty(slotInfo.getCardId())
140                         || !TextUtils.isEmpty(portInfo.getIccId())) {
141                     isAllCardStringsEmpty = false;
142                 }
143             }
144         }
145 
146         // We also drop the event if both the card strings are empty, which usually means it's
147         // between SIM slots switch the slot status is not stable at this moment.
148         if (isAllCardStringsEmpty) {
149             Log.i(TAG, "All UICC card strings are empty. Drop this event.");
150             return false;
151         }
152 
153         return true;
154     }
155 
156     @Nullable
findUiccCardInfoBySlot(TelephonyManager telMgr, int physicalSlotIndex)157     private static UiccCardInfo findUiccCardInfoBySlot(TelephonyManager telMgr,
158             int physicalSlotIndex) {
159         List<UiccCardInfo> cardInfos = telMgr.getUiccCardsInfo();
160         if (cardInfos == null) {
161             return null;
162         }
163         return cardInfos.stream()
164                 .filter(info -> info.getPhysicalSlotIndex() == physicalSlotIndex)
165                 .findFirst()
166                 .orElse(null);
167     }
168 }
169