1 /*
2  * Copyright (C) 2016 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.phone.vvm;
18 
19 import android.annotation.Nullable;
20 import android.content.BroadcastReceiver;
21 import android.content.Context;
22 import android.content.Intent;
23 import android.os.SystemProperties;
24 import android.telecom.PhoneAccountHandle;
25 import android.telecom.TelecomManager;
26 import android.telephony.CarrierConfigManager;
27 import android.telephony.PhoneStateListener;
28 import android.telephony.ServiceState;
29 import android.telephony.SubscriptionManager;
30 import android.telephony.TelephonyManager;
31 import android.util.ArrayMap;
32 import android.util.ArraySet;
33 
34 import com.android.internal.telephony.IccCardConstants;
35 import com.android.internal.telephony.PhoneConstants;
36 import com.android.internal.telephony.TelephonyIntents;
37 import com.android.phone.PhoneUtils;
38 
39 import java.util.Map;
40 import java.util.Set;
41 
42 /**
43  * Tracks the status of all inserted SIMs. Will notify {@link RemoteVvmTaskManager} of when a SIM
44  * connected to the service for the first time after it was inserted or the system booted, and when
45  * the SIM is removed. Losing cell signal or entering airplane mode will not cause the connected
46  * event to be triggered again. Reinserting the SIM will trigger the connected event. Changing the
47  * carrier config will also trigger the connected event. Events will be delayed until the device has
48  * been fully booted (and left FBE mode).
49  */
50 public class VvmSimStateTracker extends BroadcastReceiver {
51 
52     private static final String TAG = "VvmSimStateTracker";
53 
54     /**
55      * Map to keep track of currently inserted SIMs. If the SIM hasn't been connected to the service
56      * before the value will be a {@link ServiceStateListener} that is still waiting for the
57      * connection. A value of {@code null} means the SIM has been connected to the service before.
58      */
59     private static Map<PhoneAccountHandle, ServiceStateListener> sListeners = new ArrayMap<>();
60 
61     /**
62      * Accounts that has events before the device is booted. The events should be regenerated after
63      * the device has fully booted.
64      */
65     private static Set<PhoneAccountHandle> sPreBootHandles = new ArraySet<>();
66 
67     /**
68      * Waits for the account to become {@link ServiceState#STATE_IN_SERVICE} and notify the
69      * connected event. Will unregister itself once the event has been triggered.
70      */
71     private class ServiceStateListener extends PhoneStateListener {
72 
73         private final PhoneAccountHandle mPhoneAccountHandle;
74         private final Context mContext;
75 
ServiceStateListener(Context context, PhoneAccountHandle phoneAccountHandle)76         public ServiceStateListener(Context context, PhoneAccountHandle phoneAccountHandle) {
77             mContext = context;
78             mPhoneAccountHandle = phoneAccountHandle;
79         }
80 
listen()81         public void listen() {
82             TelephonyManager telephonyManager = getTelephonyManager(mContext, mPhoneAccountHandle);
83             if(telephonyManager == null){
84                 VvmLog.e(TAG, "Cannot create TelephonyManager from " + mPhoneAccountHandle);
85                 return;
86             }
87             telephonyManager.listen(this, PhoneStateListener.LISTEN_SERVICE_STATE);
88         }
89 
unlisten()90         public void unlisten() {
91             // TelephonyManager does not need to be pinned to an account when removing a
92             // PhoneStateListener, and mPhoneAccountHandle might be invalid at this point
93             // (e.g. SIM removal)
94             mContext.getSystemService(TelephonyManager.class)
95                     .listen(this, PhoneStateListener.LISTEN_NONE);
96             sListeners.put(mPhoneAccountHandle, null);
97         }
98 
99         @Override
onServiceStateChanged(ServiceState serviceState)100         public void onServiceStateChanged(ServiceState serviceState) {
101             if (serviceState.getState() == ServiceState.STATE_IN_SERVICE) {
102                 VvmLog.i(TAG, "in service");
103                 sendConnected(mContext, mPhoneAccountHandle);
104                 unlisten();
105             }
106         }
107     }
108 
109     @Override
onReceive(Context context, Intent intent)110     public void onReceive(Context context, Intent intent) {
111 
112         final String action = intent.getAction();
113         if (action == null) {
114             VvmLog.w(TAG, "Null action for intent.");
115             return;
116         }
117         VvmLog.i(TAG, action);
118         switch (action) {
119             case Intent.ACTION_BOOT_COMPLETED:
120                 onBootCompleted(context);
121                 break;
122             case TelephonyIntents.ACTION_SIM_STATE_CHANGED:
123                 if (IccCardConstants.INTENT_VALUE_ICC_ABSENT.equals(
124                         intent.getStringExtra(IccCardConstants.INTENT_KEY_ICC_STATE))) {
125                     // checkRemovedSim will scan all known accounts with isPhoneAccountActive() to find
126                     // which SIM is removed.
127                     // ACTION_SIM_STATE_CHANGED only provides subId which cannot be converted to a
128                     // PhoneAccountHandle when the SIM is absent.
129                     checkRemovedSim(context);
130                 }
131                 break;
132             case CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED:
133                 int subId = intent.getIntExtra(PhoneConstants.SUBSCRIPTION_KEY,
134                         SubscriptionManager.INVALID_SUBSCRIPTION_ID);
135 
136                 if (!SubscriptionManager.isValidSubscriptionId(subId)) {
137                     VvmLog.i(TAG, "Received SIM change for invalid subscription id.");
138                     checkRemovedSim(context);
139                     return;
140                 }
141 
142                 PhoneAccountHandle phoneAccountHandle =
143                         PhoneAccountHandleConverter.fromSubId(subId);
144 
145                 if ("null".equals(phoneAccountHandle.getId())) {
146                     VvmLog.e(TAG,
147                             "null phone account handle ID, possible modem crash."
148                                     + " Ignoring carrier config changed event");
149                     return;
150                 }
151                 onCarrierConfigChanged(context, phoneAccountHandle);
152         }
153     }
154 
onBootCompleted(Context context)155     private void onBootCompleted(Context context) {
156         for (PhoneAccountHandle phoneAccountHandle : sPreBootHandles) {
157             TelephonyManager telephonyManager = getTelephonyManager(context, phoneAccountHandle);
158             if (telephonyManager == null) {
159                 continue;
160             }
161             if (telephonyManager.getServiceState().getState() == ServiceState.STATE_IN_SERVICE) {
162                 sListeners.put(phoneAccountHandle, null);
163                 sendConnected(context, phoneAccountHandle);
164             } else {
165                 listenToAccount(context, phoneAccountHandle);
166             }
167         }
168         sPreBootHandles.clear();
169     }
170 
sendConnected(Context context, PhoneAccountHandle phoneAccountHandle)171     private void sendConnected(Context context, PhoneAccountHandle phoneAccountHandle) {
172         VvmLog.i(TAG, "Service connected on " + phoneAccountHandle);
173         RemoteVvmTaskManager.startCellServiceConnected(context, phoneAccountHandle);
174     }
175 
checkRemovedSim(Context context)176     private void checkRemovedSim(Context context) {
177         SubscriptionManager subscriptionManager = SubscriptionManager.from(context);
178         if (!isBootCompleted()) {
179             for (PhoneAccountHandle phoneAccountHandle : sPreBootHandles) {
180                 if (!PhoneUtils.isPhoneAccountActive(subscriptionManager, phoneAccountHandle)) {
181                     sPreBootHandles.remove(phoneAccountHandle);
182                 }
183             }
184             return;
185         }
186         Set<PhoneAccountHandle> removeList = new ArraySet<>();
187         for (PhoneAccountHandle phoneAccountHandle : sListeners.keySet()) {
188             if (!PhoneUtils.isPhoneAccountActive(subscriptionManager, phoneAccountHandle)) {
189                 removeList.add(phoneAccountHandle);
190                 ServiceStateListener listener = sListeners.get(phoneAccountHandle);
191                 if (listener != null) {
192                     listener.unlisten();
193                 }
194                 sendSimRemoved(context, phoneAccountHandle);
195             }
196         }
197 
198         for (PhoneAccountHandle phoneAccountHandle : removeList) {
199             sListeners.remove(phoneAccountHandle);
200         }
201     }
202 
isBootCompleted()203     private boolean isBootCompleted() {
204         return SystemProperties.getBoolean("sys.boot_completed", false);
205     }
206 
sendSimRemoved(Context context, PhoneAccountHandle phoneAccountHandle)207     private void sendSimRemoved(Context context, PhoneAccountHandle phoneAccountHandle) {
208         VvmLog.i(TAG, "Sim removed on " + phoneAccountHandle);
209         RemoteVvmTaskManager.startSimRemoved(context, phoneAccountHandle);
210     }
211 
onCarrierConfigChanged(Context context, PhoneAccountHandle phoneAccountHandle)212     private void onCarrierConfigChanged(Context context, PhoneAccountHandle phoneAccountHandle) {
213         if (!isBootCompleted()) {
214             sPreBootHandles.add(phoneAccountHandle);
215             return;
216         }
217         TelephonyManager telephonyManager = getTelephonyManager(context, phoneAccountHandle);
218         if(telephonyManager == null){
219             int subId = context.getSystemService(TelephonyManager.class).getSubIdForPhoneAccount(
220                     context.getSystemService(TelecomManager.class)
221                             .getPhoneAccount(phoneAccountHandle));
222             VvmLog.e(TAG, "Cannot create TelephonyManager from " + phoneAccountHandle + ", subId="
223                     + subId);
224             // TODO(b/33945549): investigate more why this is happening. The PhoneAccountHandle was
225             // just converted from a valid subId so createForPhoneAccountHandle shouldn't really
226             // return null.
227             return;
228         }
229         if (telephonyManager.getServiceState().getState()
230                 == ServiceState.STATE_IN_SERVICE) {
231             sendConnected(context, phoneAccountHandle);
232             sListeners.put(phoneAccountHandle, null);
233         } else {
234             listenToAccount(context, phoneAccountHandle);
235         }
236     }
237 
listenToAccount(Context context, PhoneAccountHandle phoneAccountHandle)238     private void listenToAccount(Context context, PhoneAccountHandle phoneAccountHandle) {
239         ServiceStateListener listener = new ServiceStateListener(context, phoneAccountHandle);
240         listener.listen();
241         sListeners.put(phoneAccountHandle, listener);
242     }
243 
244     @Nullable
getTelephonyManager(Context context, PhoneAccountHandle phoneAccountHandle)245     private static TelephonyManager getTelephonyManager(Context context,
246             PhoneAccountHandle phoneAccountHandle) {
247         return context.getSystemService(TelephonyManager.class)
248                 .createForPhoneAccountHandle(phoneAccountHandle);
249     }
250 }
251