/* * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package com.android.phone.vvm; import android.annotation.Nullable; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.os.Handler; import android.os.HandlerExecutor; import android.os.Looper; import android.os.SystemProperties; import android.telecom.PhoneAccountHandle; import android.telecom.TelecomManager; import android.telephony.CarrierConfigManager; import android.telephony.ServiceState; import android.telephony.SubscriptionManager; import android.telephony.TelephonyCallback; import android.telephony.TelephonyManager; import android.util.ArrayMap; import android.util.ArraySet; import com.android.internal.telephony.IccCardConstants; import com.android.internal.telephony.PhoneConstants; import com.android.internal.telephony.TelephonyIntents; import com.android.phone.PhoneUtils; import java.util.Map; import java.util.Set; /** * Tracks the status of all inserted SIMs. Will notify {@link RemoteVvmTaskManager} of when a SIM * connected to the service for the first time after it was inserted or the system booted, and when * the SIM is removed. Losing cell signal or entering airplane mode will not cause the connected * event to be triggered again. Reinserting the SIM will trigger the connected event. Changing the * carrier config will also trigger the connected event. Events will be delayed until the device has * been fully booted (and left FBE mode). */ public class VvmSimStateTracker extends BroadcastReceiver { private static final String TAG = "VvmSimStateTracker"; /** * Map to keep track of currently inserted SIMs. If the SIM hasn't been connected to the service * before the value will be a {@link ServiceStateListener} that is still waiting for the * connection. A value of {@code null} means the SIM has been connected to the service before. */ private static Map sListeners = new ArrayMap<>(); /** * Accounts that has events before the device is booted. The events should be regenerated after * the device has fully booted. */ private static Set sPreBootHandles = new ArraySet<>(); /** * Waits for the account to become {@link ServiceState#STATE_IN_SERVICE} and notify the * connected event. Will unregister itself once the event has been triggered. */ private class ServiceStateListener extends TelephonyCallback implements TelephonyCallback.ServiceStateListener { private final PhoneAccountHandle mPhoneAccountHandle; private final Context mContext; public ServiceStateListener(Context context, PhoneAccountHandle phoneAccountHandle) { mContext = context; mPhoneAccountHandle = phoneAccountHandle; } public void listen() { TelephonyManager telephonyManager = getTelephonyManager(mContext, mPhoneAccountHandle); if(telephonyManager == null){ VvmLog.e(TAG, "Cannot create TelephonyManager from " + mPhoneAccountHandle); return; } telephonyManager.registerTelephonyCallback( new HandlerExecutor(new Handler(Looper.getMainLooper())), this); } public void unlisten() { // TelephonyManager does not need to be pinned to an account when removing a // PhoneStateListener, and mPhoneAccountHandle might be invalid at this point // (e.g. SIM removal) mContext.getSystemService(TelephonyManager.class) .unregisterTelephonyCallback(this); sListeners.put(mPhoneAccountHandle, null); } @Override public void onServiceStateChanged(ServiceState serviceState) { if (serviceState.getState() == ServiceState.STATE_IN_SERVICE) { VvmLog.i(TAG, "in service"); sendConnected(mContext, mPhoneAccountHandle); unlisten(); } } } @Override public void onReceive(Context context, Intent intent) { final String action = intent.getAction(); if (action == null) { VvmLog.w(TAG, "Null action for intent."); return; } VvmLog.i(TAG, action); switch (action) { case Intent.ACTION_BOOT_COMPLETED: onBootCompleted(context); break; case TelephonyIntents.ACTION_SIM_STATE_CHANGED: if (IccCardConstants.INTENT_VALUE_ICC_ABSENT.equals( intent.getStringExtra(IccCardConstants.INTENT_KEY_ICC_STATE))) { // checkRemovedSim will scan all known accounts with isPhoneAccountActive() to find // which SIM is removed. // ACTION_SIM_STATE_CHANGED only provides subId which cannot be converted to a // PhoneAccountHandle when the SIM is absent. checkRemovedSim(context); } break; case CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED: int subId = intent.getIntExtra(PhoneConstants.SUBSCRIPTION_KEY, SubscriptionManager.INVALID_SUBSCRIPTION_ID); if (!SubscriptionManager.isValidSubscriptionId(subId)) { VvmLog.i(TAG, "Received SIM change for invalid subscription id."); checkRemovedSim(context); return; } PhoneAccountHandle phoneAccountHandle = PhoneAccountHandleConverter.fromSubId(subId); if ("null".equals(phoneAccountHandle.getId())) { VvmLog.e(TAG, "null phone account handle ID, possible modem crash." + " Ignoring carrier config changed event"); return; } onCarrierConfigChanged(context, phoneAccountHandle); } } private void onBootCompleted(Context context) { for (PhoneAccountHandle phoneAccountHandle : sPreBootHandles) { TelephonyManager telephonyManager = getTelephonyManager(context, phoneAccountHandle); if (telephonyManager == null) { continue; } if (telephonyManager.getServiceState().getState() == ServiceState.STATE_IN_SERVICE) { sListeners.put(phoneAccountHandle, null); sendConnected(context, phoneAccountHandle); } else { listenToAccount(context, phoneAccountHandle); } } sPreBootHandles.clear(); } private void sendConnected(Context context, PhoneAccountHandle phoneAccountHandle) { VvmLog.i(TAG, "Service connected on " + phoneAccountHandle); RemoteVvmTaskManager.startCellServiceConnected(context, phoneAccountHandle); } private void checkRemovedSim(Context context) { SubscriptionManager subscriptionManager = SubscriptionManager.from(context); if (!isBootCompleted()) { for (PhoneAccountHandle phoneAccountHandle : sPreBootHandles) { if (!PhoneUtils.isPhoneAccountActive(subscriptionManager, phoneAccountHandle)) { sPreBootHandles.remove(phoneAccountHandle); } } return; } Set removeList = new ArraySet<>(); for (PhoneAccountHandle phoneAccountHandle : sListeners.keySet()) { if (!PhoneUtils.isPhoneAccountActive(subscriptionManager, phoneAccountHandle)) { removeList.add(phoneAccountHandle); ServiceStateListener listener = sListeners.get(phoneAccountHandle); if (listener != null) { listener.unlisten(); } sendSimRemoved(context, phoneAccountHandle); } } for (PhoneAccountHandle phoneAccountHandle : removeList) { sListeners.remove(phoneAccountHandle); } } private boolean isBootCompleted() { return SystemProperties.getBoolean("sys.boot_completed", false); } private void sendSimRemoved(Context context, PhoneAccountHandle phoneAccountHandle) { VvmLog.i(TAG, "Sim removed on " + phoneAccountHandle); RemoteVvmTaskManager.startSimRemoved(context, phoneAccountHandle); } private void onCarrierConfigChanged(Context context, PhoneAccountHandle phoneAccountHandle) { if (!isBootCompleted()) { sPreBootHandles.add(phoneAccountHandle); return; } TelephonyManager telephonyManager = getTelephonyManager(context, phoneAccountHandle); if(telephonyManager == null){ int subId = context.getSystemService(TelephonyManager.class).getSubIdForPhoneAccount( context.getSystemService(TelecomManager.class) .getPhoneAccount(phoneAccountHandle)); VvmLog.e(TAG, "Cannot create TelephonyManager from " + phoneAccountHandle + ", subId=" + subId); // TODO(b/33945549): investigate more why this is happening. The PhoneAccountHandle was // just converted from a valid subId so createForPhoneAccountHandle shouldn't really // return null. return; } if (telephonyManager.getServiceState().getState() == ServiceState.STATE_IN_SERVICE) { sendConnected(context, phoneAccountHandle); sListeners.put(phoneAccountHandle, null); } else { listenToAccount(context, phoneAccountHandle); } } private void listenToAccount(Context context, PhoneAccountHandle phoneAccountHandle) { ServiceStateListener listener = new ServiceStateListener(context, phoneAccountHandle); listener.listen(); sListeners.put(phoneAccountHandle, listener); } @Nullable private static TelephonyManager getTelephonyManager(Context context, PhoneAccountHandle phoneAccountHandle) { return context.getSystemService(TelephonyManager.class) .createForPhoneAccountHandle(phoneAccountHandle); } }