| /* |
| * Copyright (C) 2018 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.internal.telephony.emergency; |
| |
| import android.annotation.NonNull; |
| import android.content.BroadcastReceiver; |
| import android.content.Context; |
| import android.content.Intent; |
| import android.content.IntentFilter; |
| import android.content.res.Resources; |
| import android.os.AsyncResult; |
| import android.os.Environment; |
| import android.os.Handler; |
| import android.os.Message; |
| import android.os.ParcelFileDescriptor; |
| import android.os.PersistableBundle; |
| import android.os.SystemProperties; |
| import android.telephony.CarrierConfigManager; |
| import android.telephony.CellIdentity; |
| import android.telephony.PhoneNumberUtils; |
| import android.telephony.ServiceState; |
| import android.telephony.SubscriptionManager; |
| import android.telephony.TelephonyManager; |
| import android.telephony.emergency.EmergencyNumber; |
| import android.telephony.emergency.EmergencyNumber.EmergencyCallRouting; |
| import android.telephony.emergency.EmergencyNumber.EmergencyServiceCategories; |
| import android.text.TextUtils; |
| import android.util.ArrayMap; |
| import android.util.ArraySet; |
| import android.util.LocalLog; |
| |
| import com.android.internal.annotations.VisibleForTesting; |
| import com.android.internal.telephony.CommandsInterface; |
| import com.android.internal.telephony.HalVersion; |
| import com.android.internal.telephony.LocaleTracker; |
| import com.android.internal.telephony.Phone; |
| import com.android.internal.telephony.PhoneConstants; |
| import com.android.internal.telephony.PhoneFactory; |
| import com.android.internal.telephony.ServiceStateTracker; |
| import com.android.internal.telephony.SubscriptionController; |
| import com.android.internal.telephony.metrics.TelephonyMetrics; |
| import com.android.internal.util.IndentingPrintWriter; |
| import com.android.phone.ecc.nano.ProtobufEccData; |
| import com.android.phone.ecc.nano.ProtobufEccData.EccInfo; |
| import com.android.telephony.Rlog; |
| |
| import com.google.i18n.phonenumbers.ShortNumberInfo; |
| |
| import java.io.BufferedInputStream; |
| import java.io.ByteArrayOutputStream; |
| import java.io.File; |
| import java.io.FileDescriptor; |
| import java.io.FileInputStream; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.PrintWriter; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collections; |
| import java.util.List; |
| import java.util.Locale; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.zip.GZIPInputStream; |
| |
| /** |
| * Emergency Number Tracker that handles update of emergency number list from RIL and emergency |
| * number database. This is multi-sim based and each Phone has a EmergencyNumberTracker. |
| */ |
| public class EmergencyNumberTracker extends Handler { |
| private static final String TAG = EmergencyNumberTracker.class.getSimpleName(); |
| |
| private static final int INVALID_DATABASE_VERSION = -1; |
| private static final String EMERGENCY_NUMBER_DB_OTA_FILE_NAME = "emergency_number_db"; |
| private static final String EMERGENCY_NUMBER_DB_OTA_FILE_PATH = |
| "misc/emergencynumberdb/" + EMERGENCY_NUMBER_DB_OTA_FILE_NAME; |
| |
| /** Used for storing overrided (non-default) OTA database file path */ |
| private ParcelFileDescriptor mOverridedOtaDbParcelFileDescriptor = null; |
| |
| /** @hide */ |
| public static boolean DBG = false; |
| /** @hide */ |
| public static final int ADD_EMERGENCY_NUMBER_TEST_MODE = 1; |
| /** @hide */ |
| public static final int REMOVE_EMERGENCY_NUMBER_TEST_MODE = 2; |
| /** @hide */ |
| public static final int RESET_EMERGENCY_NUMBER_TEST_MODE = 3; |
| |
| private final CommandsInterface mCi; |
| private final Phone mPhone; |
| private int mPhoneId; |
| private String mCountryIso; |
| private String mLastKnownEmergencyCountryIso = ""; |
| private int mCurrentDatabaseVersion = INVALID_DATABASE_VERSION; |
| private boolean mIsHalVersionLessThan1Dot4 = false; |
| private Resources mResources = null; |
| /** |
| * Used for storing all specific mnc's along with the list of emergency numbers |
| * for which normal routing should be supported. |
| */ |
| private Map<String, Set<String>> mNormalRoutedNumbers = new ArrayMap<>(); |
| |
| /** |
| * Indicates if the country iso is set by another subscription. |
| * @hide |
| */ |
| public boolean mIsCountrySetByAnotherSub = false; |
| private String[] mEmergencyNumberPrefix = new String[0]; |
| |
| private static final String EMERGENCY_NUMBER_DB_ASSETS_FILE = "eccdata"; |
| |
| private List<EmergencyNumber> mEmergencyNumberListFromDatabase = new ArrayList<>(); |
| private List<EmergencyNumber> mEmergencyNumberListFromRadio = new ArrayList<>(); |
| private List<EmergencyNumber> mEmergencyNumberListWithPrefix = new ArrayList<>(); |
| private List<EmergencyNumber> mEmergencyNumberListFromTestMode = new ArrayList<>(); |
| private List<EmergencyNumber> mEmergencyNumberList = new ArrayList<>(); |
| |
| private final LocalLog mEmergencyNumberListDatabaseLocalLog = new LocalLog(16); |
| private final LocalLog mEmergencyNumberListRadioLocalLog = new LocalLog(16); |
| private final LocalLog mEmergencyNumberListPrefixLocalLog = new LocalLog(16); |
| private final LocalLog mEmergencyNumberListTestModeLocalLog = new LocalLog(16); |
| private final LocalLog mEmergencyNumberListLocalLog = new LocalLog(16); |
| |
| /** Event indicating the update for the emergency number list from the radio. */ |
| private static final int EVENT_UNSOL_EMERGENCY_NUMBER_LIST = 1; |
| /** |
| * Event indicating the update for the emergency number list from the database due to the |
| * change of country code. |
| **/ |
| private static final int EVENT_UPDATE_DB_COUNTRY_ISO_CHANGED = 2; |
| /** Event indicating the update for the emergency number list in the testing mode. */ |
| private static final int EVENT_UPDATE_EMERGENCY_NUMBER_TEST_MODE = 3; |
| /** Event indicating the update for the emergency number prefix from carrier config. */ |
| private static final int EVENT_UPDATE_EMERGENCY_NUMBER_PREFIX = 4; |
| /** Event indicating the update for the OTA emergency number database. */ |
| @VisibleForTesting |
| public static final int EVENT_UPDATE_OTA_EMERGENCY_NUMBER_DB = 5; |
| /** Event indicating the override for the test OTA emergency number database. */ |
| @VisibleForTesting |
| public static final int EVENT_OVERRIDE_OTA_EMERGENCY_NUMBER_DB_FILE_PATH = 6; |
| |
| private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() { |
| @Override |
| public void onReceive(Context context, Intent intent) { |
| if (intent.getAction().equals( |
| CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED)) { |
| onCarrierConfigChanged(); |
| return; |
| } else if (intent.getAction().equals( |
| TelephonyManager.ACTION_NETWORK_COUNTRY_CHANGED)) { |
| int phoneId = intent.getIntExtra(PhoneConstants.PHONE_KEY, -1); |
| if (phoneId == mPhone.getPhoneId()) { |
| String countryIso = intent.getStringExtra( |
| TelephonyManager.EXTRA_NETWORK_COUNTRY); |
| logd("ACTION_NETWORK_COUNTRY_CHANGED: PhoneId: " + phoneId + " CountryIso: " |
| + countryIso); |
| |
| // Update country iso change for available Phones |
| updateEmergencyCountryIsoAllPhones(countryIso == null ? "" : countryIso); |
| } |
| return; |
| } |
| } |
| }; |
| |
| public EmergencyNumberTracker(Phone phone, CommandsInterface ci) { |
| mPhone = phone; |
| mCi = ci; |
| mResources = mPhone.getContext().getResources(); |
| |
| if (mPhone != null) { |
| mPhoneId = phone.getPhoneId(); |
| CarrierConfigManager configMgr = (CarrierConfigManager) |
| mPhone.getContext().getSystemService(Context.CARRIER_CONFIG_SERVICE); |
| if (configMgr != null) { |
| PersistableBundle b = configMgr.getConfigForSubId(mPhone.getSubId()); |
| if (b != null) { |
| mEmergencyNumberPrefix = b.getStringArray( |
| CarrierConfigManager.KEY_EMERGENCY_NUMBER_PREFIX_STRING_ARRAY); |
| } |
| } else { |
| loge("CarrierConfigManager is null."); |
| } |
| |
| // Receive Carrier Config Changes |
| IntentFilter filter = new IntentFilter( |
| CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED); |
| // Receive Telephony Network Country Changes |
| filter.addAction(TelephonyManager.ACTION_NETWORK_COUNTRY_CHANGED); |
| |
| mPhone.getContext().registerReceiver(mIntentReceiver, filter); |
| |
| mIsHalVersionLessThan1Dot4 = mPhone.getHalVersion().lessOrEqual(new HalVersion(1, 3)); |
| } else { |
| loge("mPhone is null."); |
| } |
| |
| initializeDatabaseEmergencyNumberList(); |
| mCi.registerForEmergencyNumberList(this, EVENT_UNSOL_EMERGENCY_NUMBER_LIST, null); |
| } |
| |
| /** |
| * Message handler for updating emergency number list from RIL, updating emergency number list |
| * from database if the country ISO is changed, and notifying the change of emergency number |
| * list. |
| * |
| * @param msg The message |
| */ |
| @Override |
| public void handleMessage(Message msg) { |
| switch (msg.what) { |
| case EVENT_UNSOL_EMERGENCY_NUMBER_LIST: |
| AsyncResult ar = (AsyncResult) msg.obj; |
| if (ar.result == null) { |
| loge("EVENT_UNSOL_EMERGENCY_NUMBER_LIST: Result from RIL is null."); |
| } else if ((ar.result != null) && (ar.exception == null)) { |
| updateRadioEmergencyNumberListAndNotify((List<EmergencyNumber>) ar.result); |
| } else { |
| loge("EVENT_UNSOL_EMERGENCY_NUMBER_LIST: Exception from RIL : " |
| + ar.exception); |
| } |
| break; |
| case EVENT_UPDATE_DB_COUNTRY_ISO_CHANGED: |
| if (msg.obj == null) { |
| loge("EVENT_UPDATE_DB_COUNTRY_ISO_CHANGED: Result from UpdateCountryIso is" |
| + " null."); |
| } else { |
| updateEmergencyNumberListDatabaseAndNotify((String) msg.obj); |
| } |
| break; |
| case EVENT_UPDATE_EMERGENCY_NUMBER_TEST_MODE: |
| if (msg.obj == null && msg.arg1 != RESET_EMERGENCY_NUMBER_TEST_MODE) { |
| loge("EVENT_UPDATE_EMERGENCY_NUMBER_TEST_MODE: Result from" |
| + " executeEmergencyNumberTestModeCommand is null."); |
| } else { |
| updateEmergencyNumberListTestModeAndNotify( |
| msg.arg1, (EmergencyNumber) msg.obj); |
| } |
| break; |
| case EVENT_UPDATE_EMERGENCY_NUMBER_PREFIX: |
| if (msg.obj == null) { |
| loge("EVENT_UPDATE_EMERGENCY_NUMBER_PREFIX: Result from" |
| + " onCarrierConfigChanged is null."); |
| } else { |
| updateEmergencyNumberPrefixAndNotify((String[]) msg.obj); |
| } |
| break; |
| case EVENT_UPDATE_OTA_EMERGENCY_NUMBER_DB: |
| updateOtaEmergencyNumberListDatabaseAndNotify(); |
| break; |
| case EVENT_OVERRIDE_OTA_EMERGENCY_NUMBER_DB_FILE_PATH: |
| if (msg.obj == null) { |
| overrideOtaEmergencyNumberDbFilePath(null); |
| } else { |
| overrideOtaEmergencyNumberDbFilePath((ParcelFileDescriptor) msg.obj); |
| } |
| break; |
| } |
| } |
| |
| private boolean isAirplaneModeEnabled() { |
| ServiceStateTracker serviceStateTracker = mPhone.getServiceStateTracker(); |
| if (serviceStateTracker != null) { |
| if (serviceStateTracker.getServiceState().getState() |
| == ServiceState.STATE_POWER_OFF) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * Checks if it's sim absent to decide whether to apply sim-absent emergency numbers from 3gpp |
| */ |
| @VisibleForTesting |
| public boolean isSimAbsent() { |
| for (Phone phone: PhoneFactory.getPhones()) { |
| int slotId = SubscriptionController.getInstance().getSlotIndex(phone.getSubId()); |
| // If slot id is invalid, it means that there is no sim card. |
| if (slotId != SubscriptionManager.INVALID_SIM_SLOT_INDEX) { |
| // If there is at least one sim active, sim is not absent; it returns false |
| logd("found sim in slotId: " + slotId + " subid: " + phone.getSubId()); |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| private void initializeDatabaseEmergencyNumberList() { |
| // If country iso has been cached when listener is set, don't need to cache the initial |
| // country iso and initial database. |
| if (mCountryIso == null) { |
| String countryForDatabaseCache = getInitialCountryIso().toLowerCase(Locale.ROOT); |
| updateEmergencyCountryIso(countryForDatabaseCache); |
| // Use the last known country to cache the database in APM |
| if (TextUtils.isEmpty(countryForDatabaseCache) |
| && isAirplaneModeEnabled()) { |
| countryForDatabaseCache = getCountryIsoForCachingDatabase(); |
| } |
| cacheEmergencyDatabaseByCountry(countryForDatabaseCache); |
| } |
| } |
| |
| /** |
| * Update Emergency country iso for all the Phones |
| */ |
| @VisibleForTesting |
| public void updateEmergencyCountryIsoAllPhones(String countryIso) { |
| // Notify country iso change for current Phone |
| mIsCountrySetByAnotherSub = false; |
| updateEmergencyNumberDatabaseCountryChange(countryIso); |
| |
| // Share and notify country iso change for other Phones if the country |
| // iso in their emergency number tracker is not available or the country |
| // iso there is set by another active subscription. |
| for (Phone phone: PhoneFactory.getPhones()) { |
| if (phone.getPhoneId() == mPhone.getPhoneId()) { |
| continue; |
| } |
| EmergencyNumberTracker emergencyNumberTracker; |
| if (phone != null && phone.getEmergencyNumberTracker() != null) { |
| emergencyNumberTracker = phone.getEmergencyNumberTracker(); |
| // If signal is lost, do not update the empty country iso for other slots. |
| if (!TextUtils.isEmpty(countryIso)) { |
| if (TextUtils.isEmpty(emergencyNumberTracker.getEmergencyCountryIso()) |
| || emergencyNumberTracker.mIsCountrySetByAnotherSub) { |
| emergencyNumberTracker.mIsCountrySetByAnotherSub = true; |
| emergencyNumberTracker.updateEmergencyNumberDatabaseCountryChange( |
| countryIso); |
| } |
| } |
| } |
| } |
| } |
| |
| private void onCarrierConfigChanged() { |
| if (mPhone != null) { |
| CarrierConfigManager configMgr = (CarrierConfigManager) |
| mPhone.getContext().getSystemService(Context.CARRIER_CONFIG_SERVICE); |
| if (configMgr != null) { |
| PersistableBundle b = configMgr.getConfigForSubId(mPhone.getSubId()); |
| if (b != null) { |
| String[] emergencyNumberPrefix = b.getStringArray( |
| CarrierConfigManager.KEY_EMERGENCY_NUMBER_PREFIX_STRING_ARRAY); |
| if (!Arrays.equals(mEmergencyNumberPrefix, emergencyNumberPrefix)) { |
| this.obtainMessage(EVENT_UPDATE_EMERGENCY_NUMBER_PREFIX, |
| emergencyNumberPrefix).sendToTarget(); |
| } |
| } |
| } |
| } else { |
| loge("onCarrierConfigChanged mPhone is null."); |
| } |
| } |
| |
| private String getInitialCountryIso() { |
| if (mPhone != null) { |
| ServiceStateTracker sst = mPhone.getServiceStateTracker(); |
| if (sst != null) { |
| LocaleTracker lt = sst.getLocaleTracker(); |
| if (lt != null) { |
| return lt.getCurrentCountry(); |
| } |
| } |
| } else { |
| loge("getInitialCountryIso mPhone is null."); |
| |
| } |
| return ""; |
| } |
| |
| /** |
| * Update Emergency Number database based on changed Country ISO. |
| * |
| * @param countryIso |
| * |
| * @hide |
| */ |
| public void updateEmergencyNumberDatabaseCountryChange(String countryIso) { |
| this.obtainMessage(EVENT_UPDATE_DB_COUNTRY_ISO_CHANGED, countryIso).sendToTarget(); |
| } |
| |
| /** |
| * Update changed OTA Emergency Number database. |
| * |
| * @hide |
| */ |
| public void updateOtaEmergencyNumberDatabase() { |
| this.obtainMessage(EVENT_UPDATE_OTA_EMERGENCY_NUMBER_DB).sendToTarget(); |
| } |
| |
| /** |
| * Override the OTA Emergency Number database file path. |
| * |
| * @hide |
| */ |
| public void updateOtaEmergencyNumberDbFilePath(ParcelFileDescriptor otaParcelFileDescriptor) { |
| this.obtainMessage( |
| EVENT_OVERRIDE_OTA_EMERGENCY_NUMBER_DB_FILE_PATH, |
| otaParcelFileDescriptor).sendToTarget(); |
| } |
| |
| /** |
| * Override the OTA Emergency Number database file path. |
| * |
| * @hide |
| */ |
| public void resetOtaEmergencyNumberDbFilePath() { |
| this.obtainMessage( |
| EVENT_OVERRIDE_OTA_EMERGENCY_NUMBER_DB_FILE_PATH, null).sendToTarget(); |
| } |
| |
| private EmergencyNumber convertEmergencyNumberFromEccInfo(EccInfo eccInfo, String countryIso, |
| int emergencyCallRouting) { |
| String phoneNumber = eccInfo.phoneNumber.trim(); |
| if (phoneNumber.isEmpty()) { |
| loge("EccInfo has empty phone number."); |
| return null; |
| } |
| int emergencyServiceCategoryBitmask = 0; |
| for (int typeData : eccInfo.types) { |
| switch (typeData) { |
| case EccInfo.Type.POLICE: |
| emergencyServiceCategoryBitmask |= |
| EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_POLICE; |
| break; |
| case EccInfo.Type.AMBULANCE: |
| emergencyServiceCategoryBitmask |= |
| EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_AMBULANCE; |
| break; |
| case EccInfo.Type.FIRE: |
| emergencyServiceCategoryBitmask |= |
| EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_FIRE_BRIGADE; |
| break; |
| case EccInfo.Type.MARINE_GUARD: |
| emergencyServiceCategoryBitmask |= |
| EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_MARINE_GUARD; |
| break; |
| case EccInfo.Type.MOUNTAIN_RESCUE: |
| emergencyServiceCategoryBitmask |= |
| EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_MOUNTAIN_RESCUE; |
| break; |
| case EccInfo.Type.MIEC: |
| emergencyServiceCategoryBitmask |= |
| EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_MIEC; |
| break; |
| case EccInfo.Type.AIEC: |
| emergencyServiceCategoryBitmask |= |
| EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_AIEC; |
| break; |
| default: |
| // Ignores unknown types. |
| } |
| } |
| return new EmergencyNumber(phoneNumber, countryIso, "", |
| emergencyServiceCategoryBitmask, new ArrayList<String>(), |
| EmergencyNumber.EMERGENCY_NUMBER_SOURCE_DATABASE, emergencyCallRouting); |
| } |
| |
| /** |
| * Get routing type of emergency numbers from DB. Update mnc's list with numbers that are |
| * to supported as normal routing type in the respective mnc's. |
| */ |
| private int getRoutingInfoFromDB(EccInfo eccInfo, |
| Map<String, Set<String>> normalRoutedNumbers) { |
| int emergencyCallRouting = EmergencyNumber.EMERGENCY_CALL_ROUTING_EMERGENCY; |
| String phoneNumber = eccInfo.phoneNumber.trim(); |
| if (phoneNumber.isEmpty()) { |
| loge("EccInfo has empty phone number."); |
| return emergencyCallRouting; |
| } |
| |
| if (eccInfo.isNormalRouted) { |
| emergencyCallRouting = EmergencyNumber.EMERGENCY_CALL_ROUTING_NORMAL; |
| |
| if (((eccInfo.normalRoutingMncs).length != 0) |
| && (eccInfo.normalRoutingMncs[0].length() > 0)) { |
| emergencyCallRouting = EmergencyNumber.EMERGENCY_CALL_ROUTING_EMERGENCY; |
| |
| for (String routingMnc : eccInfo.normalRoutingMncs) { |
| boolean mncExist = normalRoutedNumbers.containsKey(routingMnc); |
| Set phoneNumberList; |
| if (!mncExist) { |
| phoneNumberList = new ArraySet<String>(); |
| phoneNumberList.add(phoneNumber); |
| normalRoutedNumbers.put(routingMnc, phoneNumberList); |
| } else { |
| phoneNumberList = normalRoutedNumbers.get(routingMnc); |
| if (!phoneNumberList.contains(phoneNumber)) { |
| phoneNumberList.add(phoneNumber); |
| } |
| } |
| } |
| logd("Normal routed mncs with phoneNumbers:" + normalRoutedNumbers); |
| } |
| } |
| return emergencyCallRouting; |
| } |
| |
| private void cacheEmergencyDatabaseByCountry(String countryIso) { |
| int assetsDatabaseVersion; |
| Map<String, Set<String>> assetNormalRoutedNumbers = new ArrayMap<>(); |
| |
| // Read the Asset emergency number database |
| List<EmergencyNumber> updatedAssetEmergencyNumberList = new ArrayList<>(); |
| // try-with-resource. The 2 streams are auto closeable. |
| try (BufferedInputStream inputStream = new BufferedInputStream( |
| mPhone.getContext().getAssets().open(EMERGENCY_NUMBER_DB_ASSETS_FILE)); |
| GZIPInputStream gzipInputStream = new GZIPInputStream(inputStream)) { |
| ProtobufEccData.AllInfo allEccMessages = ProtobufEccData.AllInfo.parseFrom( |
| readInputStreamToByteArray(gzipInputStream)); |
| assetsDatabaseVersion = allEccMessages.revision; |
| logd(countryIso + " asset emergency database is loaded. Ver: " + assetsDatabaseVersion |
| + " Phone Id: " + mPhone.getPhoneId()); |
| for (ProtobufEccData.CountryInfo countryEccInfo : allEccMessages.countries) { |
| if (countryEccInfo.isoCode.equals(countryIso.toUpperCase(Locale.ROOT))) { |
| for (ProtobufEccData.EccInfo eccInfo : countryEccInfo.eccs) { |
| int emergencyCallRouting = EmergencyNumber.EMERGENCY_CALL_ROUTING_UNKNOWN; |
| if (!shouldEmergencyNumberRoutingFromDbBeIgnored()) { |
| emergencyCallRouting = getRoutingInfoFromDB(eccInfo, |
| assetNormalRoutedNumbers); |
| } |
| updatedAssetEmergencyNumberList.add(convertEmergencyNumberFromEccInfo( |
| eccInfo, countryIso, emergencyCallRouting)); |
| } |
| } |
| } |
| EmergencyNumber.mergeSameNumbersInEmergencyNumberList(updatedAssetEmergencyNumberList); |
| } catch (IOException ex) { |
| logw("Cache asset emergency database failure: " + ex); |
| return; |
| } |
| |
| // Cache OTA emergency number database |
| int otaDatabaseVersion = cacheOtaEmergencyNumberDatabase(); |
| |
| // Use a valid database that has higher version. |
| if (otaDatabaseVersion == INVALID_DATABASE_VERSION |
| && assetsDatabaseVersion == INVALID_DATABASE_VERSION) { |
| loge("No database available. Phone Id: " + mPhone.getPhoneId()); |
| } else if (assetsDatabaseVersion > otaDatabaseVersion) { |
| logd("Using Asset Emergency database. Version: " + assetsDatabaseVersion); |
| mCurrentDatabaseVersion = assetsDatabaseVersion; |
| mEmergencyNumberListFromDatabase = updatedAssetEmergencyNumberList; |
| mNormalRoutedNumbers.clear(); |
| mNormalRoutedNumbers = assetNormalRoutedNumbers; |
| } else { |
| logd("Using Ota Emergency database. Version: " + otaDatabaseVersion); |
| } |
| } |
| |
| private int cacheOtaEmergencyNumberDatabase() { |
| ProtobufEccData.AllInfo allEccMessages = null; |
| int otaDatabaseVersion = INVALID_DATABASE_VERSION; |
| Map<String, Set<String>> otaNormalRoutedNumbers = new ArrayMap<>(); |
| |
| // Read the OTA emergency number database |
| List<EmergencyNumber> updatedOtaEmergencyNumberList = new ArrayList<>(); |
| |
| File file; |
| // If OTA File partition is not available, try to reload the default one. |
| if (mOverridedOtaDbParcelFileDescriptor == null) { |
| file = new File(Environment.getDataDirectory(), EMERGENCY_NUMBER_DB_OTA_FILE_PATH); |
| } else { |
| try { |
| file = ParcelFileDescriptor.getFile(mOverridedOtaDbParcelFileDescriptor |
| .getFileDescriptor()).getAbsoluteFile(); |
| } catch (IOException ex) { |
| loge("Cache ota emergency database IOException: " + ex); |
| return INVALID_DATABASE_VERSION; |
| } |
| } |
| |
| // try-with-resource. Those 3 streams are all auto closeable. |
| try (FileInputStream fileInputStream = new FileInputStream(file); |
| BufferedInputStream inputStream = new BufferedInputStream(fileInputStream); |
| GZIPInputStream gzipInputStream = new GZIPInputStream(inputStream)) { |
| allEccMessages = ProtobufEccData.AllInfo.parseFrom( |
| readInputStreamToByteArray(gzipInputStream)); |
| String countryIso = getLastKnownEmergencyCountryIso(); |
| logd(countryIso + " ota emergency database is loaded. Ver: " + otaDatabaseVersion); |
| otaDatabaseVersion = allEccMessages.revision; |
| for (ProtobufEccData.CountryInfo countryEccInfo : allEccMessages.countries) { |
| if (countryEccInfo.isoCode.equals(countryIso.toUpperCase(Locale.ROOT))) { |
| for (ProtobufEccData.EccInfo eccInfo : countryEccInfo.eccs) { |
| int emergencyCallRouting = EmergencyNumber.EMERGENCY_CALL_ROUTING_UNKNOWN; |
| if (!shouldEmergencyNumberRoutingFromDbBeIgnored()) { |
| emergencyCallRouting = getRoutingInfoFromDB(eccInfo, |
| otaNormalRoutedNumbers); |
| } |
| updatedOtaEmergencyNumberList.add(convertEmergencyNumberFromEccInfo( |
| eccInfo, countryIso, emergencyCallRouting)); |
| } |
| } |
| } |
| EmergencyNumber.mergeSameNumbersInEmergencyNumberList(updatedOtaEmergencyNumberList); |
| } catch (IOException ex) { |
| loge("Cache ota emergency database IOException: " + ex); |
| return INVALID_DATABASE_VERSION; |
| } |
| |
| // Use a valid database that has higher version. |
| if (otaDatabaseVersion != INVALID_DATABASE_VERSION |
| && mCurrentDatabaseVersion < otaDatabaseVersion) { |
| mCurrentDatabaseVersion = otaDatabaseVersion; |
| mEmergencyNumberListFromDatabase = updatedOtaEmergencyNumberList; |
| mNormalRoutedNumbers.clear(); |
| mNormalRoutedNumbers = otaNormalRoutedNumbers; |
| } |
| return otaDatabaseVersion; |
| } |
| |
| /** |
| * Util function to convert inputStream to byte array before parsing proto data. |
| */ |
| private static byte[] readInputStreamToByteArray(InputStream inputStream) throws IOException { |
| ByteArrayOutputStream buffer = new ByteArrayOutputStream(); |
| int nRead; |
| int size = 16 * 1024; // Read 16k chunks |
| byte[] data = new byte[size]; |
| while ((nRead = inputStream.read(data, 0, data.length)) != -1) { |
| buffer.write(data, 0, nRead); |
| } |
| buffer.flush(); |
| return buffer.toByteArray(); |
| } |
| |
| private void updateRadioEmergencyNumberListAndNotify( |
| List<EmergencyNumber> emergencyNumberListRadio) { |
| Collections.sort(emergencyNumberListRadio); |
| logd("updateRadioEmergencyNumberListAndNotify(): receiving " + emergencyNumberListRadio); |
| if (!emergencyNumberListRadio.equals(mEmergencyNumberListFromRadio)) { |
| try { |
| EmergencyNumber.mergeSameNumbersInEmergencyNumberList(emergencyNumberListRadio); |
| writeUpdatedEmergencyNumberListMetrics(emergencyNumberListRadio); |
| mEmergencyNumberListFromRadio = emergencyNumberListRadio; |
| if (!DBG) { |
| mEmergencyNumberListRadioLocalLog.log("updateRadioEmergencyNumberList:" |
| + emergencyNumberListRadio); |
| } |
| updateEmergencyNumberList(); |
| if (!DBG) { |
| mEmergencyNumberListLocalLog.log("updateRadioEmergencyNumberListAndNotify:" |
| + mEmergencyNumberList); |
| } |
| notifyEmergencyNumberList(); |
| } catch (NullPointerException ex) { |
| loge("updateRadioEmergencyNumberListAndNotify() Phone already destroyed: " + ex |
| + " EmergencyNumberList not notified"); |
| } |
| } |
| } |
| |
| private void updateEmergencyNumberListDatabaseAndNotify(String countryIso) { |
| logd("updateEmergencyNumberListDatabaseAndNotify(): receiving countryIso: " |
| + countryIso); |
| updateEmergencyCountryIso(countryIso.toLowerCase(Locale.ROOT)); |
| // Use cached country iso in APM to load emergency number database. |
| if (TextUtils.isEmpty(countryIso) && isAirplaneModeEnabled()) { |
| countryIso = getCountryIsoForCachingDatabase(); |
| logd("updateEmergencyNumberListDatabaseAndNotify(): using cached APM country " |
| + countryIso); |
| } |
| cacheEmergencyDatabaseByCountry(countryIso); |
| writeUpdatedEmergencyNumberListMetrics(mEmergencyNumberListFromDatabase); |
| if (!DBG) { |
| mEmergencyNumberListDatabaseLocalLog.log( |
| "updateEmergencyNumberListDatabaseAndNotify:" |
| + mEmergencyNumberListFromDatabase); |
| } |
| updateEmergencyNumberList(); |
| if (!DBG) { |
| mEmergencyNumberListLocalLog.log("updateEmergencyNumberListDatabaseAndNotify:" |
| + mEmergencyNumberList); |
| } |
| notifyEmergencyNumberList(); |
| } |
| |
| private void overrideOtaEmergencyNumberDbFilePath( |
| ParcelFileDescriptor otaParcelableFileDescriptor) { |
| logd("overrideOtaEmergencyNumberDbFilePath:" + otaParcelableFileDescriptor); |
| mOverridedOtaDbParcelFileDescriptor = otaParcelableFileDescriptor; |
| } |
| |
| private void updateOtaEmergencyNumberListDatabaseAndNotify() { |
| logd("updateOtaEmergencyNumberListDatabaseAndNotify():" |
| + " receiving Emegency Number database OTA update"); |
| if (cacheOtaEmergencyNumberDatabase() != INVALID_DATABASE_VERSION) { |
| writeUpdatedEmergencyNumberListMetrics(mEmergencyNumberListFromDatabase); |
| if (!DBG) { |
| mEmergencyNumberListDatabaseLocalLog.log( |
| "updateOtaEmergencyNumberListDatabaseAndNotify:" |
| + mEmergencyNumberListFromDatabase); |
| } |
| updateEmergencyNumberList(); |
| if (!DBG) { |
| mEmergencyNumberListLocalLog.log("updateOtaEmergencyNumberListDatabaseAndNotify:" |
| + mEmergencyNumberList); |
| } |
| notifyEmergencyNumberList(); |
| } |
| } |
| |
| private void updateEmergencyNumberPrefixAndNotify(String[] emergencyNumberPrefix) { |
| logd("updateEmergencyNumberPrefixAndNotify(): receiving emergencyNumberPrefix: " |
| + Arrays.toString(emergencyNumberPrefix)); |
| mEmergencyNumberPrefix = emergencyNumberPrefix; |
| updateEmergencyNumberList(); |
| if (!DBG) { |
| mEmergencyNumberListLocalLog.log("updateEmergencyNumberPrefixAndNotify:" |
| + mEmergencyNumberList); |
| } |
| notifyEmergencyNumberList(); |
| } |
| |
| private void notifyEmergencyNumberList() { |
| try { |
| if (getEmergencyNumberList() != null) { |
| mPhone.notifyEmergencyNumberList(); |
| logd("notifyEmergencyNumberList(): notified"); |
| } |
| } catch (NullPointerException ex) { |
| loge("notifyEmergencyNumberList(): failure: Phone already destroyed: " + ex); |
| } |
| } |
| |
| /** |
| * Update emergency numbers based on the radio, database, and test mode, if they are the same |
| * emergency numbers. |
| */ |
| private void updateEmergencyNumberList() { |
| List<EmergencyNumber> mergedEmergencyNumberList = |
| new ArrayList<>(mEmergencyNumberListFromDatabase); |
| mergedEmergencyNumberList.addAll(mEmergencyNumberListFromRadio); |
| // 'updateEmergencyNumberList' is called every time there is a change for emergency numbers |
| // from radio indication, emergency numbers from database, emergency number prefix from |
| // carrier config, or test mode emergency numbers, the emergency number prefix is changed |
| // by carrier config, the emergency number list with prefix needs to be clear, and re-apply |
| // the new prefix for the current emergency numbers. |
| mEmergencyNumberListWithPrefix.clear(); |
| if (mEmergencyNumberPrefix.length != 0) { |
| mEmergencyNumberListWithPrefix.addAll(getEmergencyNumberListWithPrefix( |
| mEmergencyNumberListFromRadio)); |
| mEmergencyNumberListWithPrefix.addAll(getEmergencyNumberListWithPrefix( |
| mEmergencyNumberListFromDatabase)); |
| } |
| if (!DBG) { |
| mEmergencyNumberListPrefixLocalLog.log("updateEmergencyNumberList:" |
| + mEmergencyNumberListWithPrefix); |
| } |
| mergedEmergencyNumberList.addAll(mEmergencyNumberListWithPrefix); |
| mergedEmergencyNumberList.addAll(mEmergencyNumberListFromTestMode); |
| EmergencyNumber.mergeSameNumbersInEmergencyNumberList(mergedEmergencyNumberList); |
| mEmergencyNumberList = mergedEmergencyNumberList; |
| } |
| |
| /** |
| * Get the emergency number list. |
| * |
| * @return the emergency number list based on radio indication or ril.ecclist if radio |
| * indication not support from the HAL. |
| */ |
| public List<EmergencyNumber> getEmergencyNumberList() { |
| List<EmergencyNumber> completeEmergencyNumberList; |
| if (!mEmergencyNumberListFromRadio.isEmpty()) { |
| completeEmergencyNumberList = Collections.unmodifiableList(mEmergencyNumberList); |
| } else { |
| completeEmergencyNumberList = getEmergencyNumberListFromEccListDatabaseAndTest(); |
| } |
| if (shouldAdjustForRouting()) { |
| return adjustRoutingForEmergencyNumbers(completeEmergencyNumberList); |
| } else { |
| return completeEmergencyNumberList; |
| } |
| } |
| |
| /** |
| * Util function to check whether routing type and mnc value in emergency number needs |
| * to be adjusted for the current network mnc. |
| */ |
| private boolean shouldAdjustForRouting() { |
| if (!shouldEmergencyNumberRoutingFromDbBeIgnored() && !mNormalRoutedNumbers.isEmpty()) { |
| CellIdentity cellIdentity = mPhone.getCurrentCellIdentity(); |
| if (cellIdentity != null) { |
| String networkMnc = cellIdentity.getMncString(); |
| if (mNormalRoutedNumbers.containsKey(networkMnc)) { |
| Set<String> phoneNumbers = mNormalRoutedNumbers.get(networkMnc); |
| if (phoneNumbers != null && !phoneNumbers.isEmpty()) { |
| return true; |
| } |
| } |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * Adjust emergency numbers with mnc and routing type based on the current network mnc. |
| */ |
| private List<EmergencyNumber> adjustRoutingForEmergencyNumbers( |
| List<EmergencyNumber> emergencyNumbers) { |
| CellIdentity cellIdentity = mPhone.getCurrentCellIdentity(); |
| if (cellIdentity != null) { |
| String networkMnc = cellIdentity.getMncString(); |
| |
| Set<String> normalRoutedPhoneNumbers = mNormalRoutedNumbers.get(networkMnc); |
| if (normalRoutedPhoneNumbers == null || normalRoutedPhoneNumbers.isEmpty()) { |
| return emergencyNumbers; |
| } |
| Set<String> normalRoutedPhoneNumbersWithPrefix = new ArraySet<String>(); |
| for (String num : normalRoutedPhoneNumbers) { |
| Set<String> phoneNumbersWithPrefix = addPrefixToEmergencyNumber(num); |
| if (phoneNumbersWithPrefix != null && !phoneNumbersWithPrefix.isEmpty()) { |
| normalRoutedPhoneNumbersWithPrefix.addAll(phoneNumbersWithPrefix); |
| } |
| } |
| List<EmergencyNumber> adjustedEmergencyNumberList = new ArrayList<>(); |
| int routing; |
| String mnc; |
| for (EmergencyNumber num : emergencyNumbers) { |
| routing = num.getEmergencyCallRouting(); |
| mnc = num.getMnc(); |
| if (num.isFromSources(EmergencyNumber.EMERGENCY_NUMBER_SOURCE_DATABASE) |
| && (normalRoutedPhoneNumbers.contains(num.getNumber()) |
| || (normalRoutedPhoneNumbersWithPrefix.contains(num.getNumber())))) { |
| routing = EmergencyNumber.EMERGENCY_CALL_ROUTING_NORMAL; |
| mnc = networkMnc; |
| logd("adjustRoutingForEmergencyNumbers for number" + num.getNumber()); |
| } |
| adjustedEmergencyNumberList.add(new EmergencyNumber(num.getNumber(), |
| num.getCountryIso(), mnc, |
| num.getEmergencyServiceCategoryBitmask(), |
| num.getEmergencyUrns(), num.getEmergencyNumberSourceBitmask(), |
| routing)); |
| } |
| return adjustedEmergencyNumberList; |
| } else { |
| return emergencyNumbers; |
| } |
| } |
| |
| |
| /** |
| * Util function to add prefix to the given emergency number. |
| */ |
| private Set<String> addPrefixToEmergencyNumber(String number) { |
| Set<String> phoneNumbersWithPrefix = new ArraySet<String>(); |
| for (String prefix : mEmergencyNumberPrefix) { |
| if (!number.startsWith(prefix)) { |
| phoneNumbersWithPrefix.add(prefix + number); |
| } |
| } |
| return phoneNumbersWithPrefix; |
| } |
| |
| /** |
| * Checks if the number is an emergency number in the current Phone. |
| * |
| * @return {@code true} if it is; {@code false} otherwise. |
| */ |
| public boolean isEmergencyNumber(String number) { |
| if (number == null) { |
| return false; |
| } |
| |
| // Do not treat SIP address as emergency number |
| if (PhoneNumberUtils.isUriNumber(number)) { |
| return false; |
| } |
| |
| // Strip the separators from the number before comparing it |
| // to the list. |
| number = PhoneNumberUtils.extractNetworkPortionAlt(number); |
| |
| if (!mEmergencyNumberListFromRadio.isEmpty()) { |
| for (EmergencyNumber num : mEmergencyNumberList) { |
| if (num.getNumber().equals(number)) { |
| logd("Found in mEmergencyNumberList"); |
| return true; |
| } |
| } |
| return false; |
| } else { |
| boolean inEccList = isEmergencyNumberFromEccList(number); |
| boolean inEmergencyNumberDb = isEmergencyNumberFromDatabase(number); |
| boolean inEmergencyNumberTestList = isEmergencyNumberForTest(number); |
| logd("Search results - inRilEccList:" + inEccList |
| + " inEmergencyNumberDb:" + inEmergencyNumberDb + " inEmergencyNumberTestList: " |
| + inEmergencyNumberTestList); |
| return inEccList || inEmergencyNumberDb || inEmergencyNumberTestList; |
| } |
| } |
| |
| /** |
| * Get the {@link EmergencyNumber} for the corresponding emergency number address. |
| * |
| * @param emergencyNumber - the supplied emergency number. |
| * @return the {@link EmergencyNumber} for the corresponding emergency number address. |
| */ |
| public EmergencyNumber getEmergencyNumber(String emergencyNumber) { |
| emergencyNumber = PhoneNumberUtils.stripSeparators(emergencyNumber); |
| for (EmergencyNumber num : getEmergencyNumberList()) { |
| if (num.getNumber().equals(emergencyNumber)) { |
| return num; |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * Get the emergency service categories for the corresponding emergency number. The only |
| * trusted sources for the categories are the |
| * {@link EmergencyNumber#EMERGENCY_NUMBER_SOURCE_NETWORK_SIGNALING} and |
| * {@link EmergencyNumber#EMERGENCY_NUMBER_SOURCE_SIM}. |
| * |
| * @param emergencyNumber - the supplied emergency number. |
| * @return the emergency service categories for the corresponding emergency number. |
| */ |
| public @EmergencyServiceCategories int getEmergencyServiceCategories(String emergencyNumber) { |
| emergencyNumber = PhoneNumberUtils.stripSeparators(emergencyNumber); |
| for (EmergencyNumber num : getEmergencyNumberList()) { |
| if (num.getNumber().equals(emergencyNumber)) { |
| if (num.isFromSources(EmergencyNumber.EMERGENCY_NUMBER_SOURCE_NETWORK_SIGNALING) |
| || num.isFromSources(EmergencyNumber.EMERGENCY_NUMBER_SOURCE_SIM)) { |
| return num.getEmergencyServiceCategoryBitmask(); |
| } |
| } |
| } |
| return EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_UNSPECIFIED; |
| } |
| |
| /** |
| * Get the emergency call routing for the corresponding emergency number. The only trusted |
| * source for the routing is {@link EmergencyNumber#EMERGENCY_NUMBER_SOURCE_DATABASE}. |
| * |
| * @param emergencyNumber - the supplied emergency number. |
| * @return the emergency call routing for the corresponding emergency number. |
| */ |
| public @EmergencyCallRouting int getEmergencyCallRouting(String emergencyNumber) { |
| emergencyNumber = PhoneNumberUtils.stripSeparators(emergencyNumber); |
| for (EmergencyNumber num : getEmergencyNumberList()) { |
| if (num.getNumber().equals(emergencyNumber)) { |
| if (num.isFromSources(EmergencyNumber.EMERGENCY_NUMBER_SOURCE_DATABASE)) { |
| return num.getEmergencyCallRouting(); |
| } |
| } |
| } |
| return EmergencyNumber.EMERGENCY_CALL_ROUTING_UNKNOWN; |
| } |
| |
| public String getEmergencyCountryIso() { |
| return mCountryIso; |
| } |
| |
| public String getLastKnownEmergencyCountryIso() { |
| return mLastKnownEmergencyCountryIso; |
| } |
| |
| private String getCountryIsoForCachingDatabase() { |
| ServiceStateTracker sst = mPhone.getServiceStateTracker(); |
| if (sst != null) { |
| LocaleTracker lt = sst.getLocaleTracker(); |
| if (lt != null) { |
| return lt.getLastKnownCountryIso(); |
| } |
| } |
| return ""; |
| } |
| |
| public int getEmergencyNumberDbVersion() { |
| return mCurrentDatabaseVersion; |
| } |
| |
| private synchronized void updateEmergencyCountryIso(String countryIso) { |
| mCountryIso = countryIso; |
| if (!TextUtils.isEmpty(mCountryIso)) { |
| mLastKnownEmergencyCountryIso = mCountryIso; |
| } |
| mCurrentDatabaseVersion = INVALID_DATABASE_VERSION; |
| } |
| |
| /** |
| * Get Emergency number list based on EccList. This util is used for solving backward |
| * compatibility if device does not support the 1.4 IRadioIndication HAL that reports |
| * emergency number list. |
| */ |
| private List<EmergencyNumber> getEmergencyNumberListFromEccList() { |
| List<EmergencyNumber> emergencyNumberList = new ArrayList<>(); |
| |
| if (mIsHalVersionLessThan1Dot4) { |
| emergencyNumberList.addAll(getEmergencyNumberListFromEccListForHalv1_3()); |
| } |
| String emergencyNumbers = ((isSimAbsent()) ? "112,911,000,08,110,118,119,999" : "112,911"); |
| for (String emergencyNum : emergencyNumbers.split(",")) { |
| emergencyNumberList.add(getLabeledEmergencyNumberForEcclist(emergencyNum)); |
| } |
| if (mEmergencyNumberPrefix.length != 0) { |
| emergencyNumberList.addAll(getEmergencyNumberListWithPrefix(emergencyNumberList)); |
| } |
| EmergencyNumber.mergeSameNumbersInEmergencyNumberList(emergencyNumberList); |
| return emergencyNumberList; |
| } |
| |
| private String getEmergencyNumberListForHalv1_3() { |
| int slotId = SubscriptionController.getInstance().getSlotIndex(mPhone.getSubId()); |
| |
| String ecclist = (slotId <= 0) ? "ril.ecclist" : ("ril.ecclist" + slotId); |
| String emergencyNumbers = SystemProperties.get(ecclist, ""); |
| |
| if (TextUtils.isEmpty(emergencyNumbers)) { |
| // then read-only ecclist property since old RIL only uses this |
| emergencyNumbers = SystemProperties.get("ro.ril.ecclist"); |
| } |
| logd(ecclist + " emergencyNumbers: " + emergencyNumbers); |
| return emergencyNumbers; |
| } |
| |
| private List<EmergencyNumber> getEmergencyNumberListFromEccListForHalv1_3() { |
| List<EmergencyNumber> emergencyNumberList = new ArrayList<>(); |
| String emergencyNumbers = getEmergencyNumberListForHalv1_3(); |
| |
| if (!TextUtils.isEmpty(emergencyNumbers)) { |
| for (String emergencyNum : emergencyNumbers.split(",")) { |
| emergencyNumberList.add(getLabeledEmergencyNumberForEcclist(emergencyNum)); |
| } |
| } |
| return emergencyNumberList; |
| } |
| |
| private List<EmergencyNumber> getEmergencyNumberListWithPrefix( |
| List<EmergencyNumber> emergencyNumberList) { |
| List<EmergencyNumber> emergencyNumberListWithPrefix = new ArrayList<>(); |
| if (emergencyNumberList != null) { |
| for (EmergencyNumber num : emergencyNumberList) { |
| Set<String> phoneNumbersWithPrefix = addPrefixToEmergencyNumber(num.getNumber()); |
| if (phoneNumbersWithPrefix != null && !phoneNumbersWithPrefix.isEmpty()) { |
| for (String numberWithPrefix : phoneNumbersWithPrefix) { |
| emergencyNumberListWithPrefix.add(new EmergencyNumber( |
| numberWithPrefix, num.getCountryIso(), |
| num.getMnc(), num.getEmergencyServiceCategoryBitmask(), |
| num.getEmergencyUrns(), num.getEmergencyNumberSourceBitmask(), |
| num.getEmergencyCallRouting())); |
| } |
| } |
| } |
| } |
| return emergencyNumberListWithPrefix; |
| } |
| |
| private boolean isEmergencyNumberForTest(String number) { |
| number = PhoneNumberUtils.stripSeparators(number); |
| for (EmergencyNumber num : mEmergencyNumberListFromTestMode) { |
| if (num.getNumber().equals(number)) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| private boolean isEmergencyNumberFromDatabase(String number) { |
| if (mEmergencyNumberListFromDatabase.isEmpty()) { |
| return false; |
| } |
| number = PhoneNumberUtils.stripSeparators(number); |
| for (EmergencyNumber num : mEmergencyNumberListFromDatabase) { |
| if (num.getNumber().equals(number)) { |
| return true; |
| } |
| } |
| List<EmergencyNumber> emergencyNumberListFromDatabaseWithPrefix = |
| getEmergencyNumberListWithPrefix(mEmergencyNumberListFromDatabase); |
| for (EmergencyNumber num : emergencyNumberListFromDatabaseWithPrefix) { |
| if (num.getNumber().equals(number)) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| private EmergencyNumber getLabeledEmergencyNumberForEcclist(String number) { |
| number = PhoneNumberUtils.stripSeparators(number); |
| for (EmergencyNumber num : mEmergencyNumberListFromDatabase) { |
| if (num.getNumber().equals(number)) { |
| return new EmergencyNumber(number, getLastKnownEmergencyCountryIso() |
| .toLowerCase(Locale.ROOT), "", num.getEmergencyServiceCategoryBitmask(), |
| new ArrayList<String>(), EmergencyNumber.EMERGENCY_NUMBER_SOURCE_DATABASE, |
| num.getEmergencyCallRouting()); |
| } |
| } |
| return new EmergencyNumber(number, "", "", |
| EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_UNSPECIFIED, |
| new ArrayList<String>(), 0, |
| EmergencyNumber.EMERGENCY_CALL_ROUTING_UNKNOWN); |
| } |
| |
| /** |
| * Back-up old logics for {@link PhoneNumberUtils#isEmergencyNumberInternal} for legacy |
| * and deprecate purpose. |
| */ |
| private boolean isEmergencyNumberFromEccList(String number) { |
| // If the number passed in is null, just return false: |
| if (number == null) return false; |
| |
| /// M: preprocess number for emergency check @{ |
| // Move following logic to isEmergencyNumber() |
| |
| // If the number passed in is a SIP address, return false, since the |
| // concept of "emergency numbers" is only meaningful for calls placed |
| // over the cell network. |
| // (Be sure to do this check *before* calling extractNetworkPortionAlt(), |
| // since the whole point of extractNetworkPortionAlt() is to filter out |
| // any non-dialable characters (which would turn 'abc911def@example.com' |
| // into '911', for example.)) |
| //if (PhoneNumberUtils.isUriNumber(number)) { |
| // return false; |
| //} |
| |
| // Strip the separators from the number before comparing it |
| // to the list. |
| //number = PhoneNumberUtils.extractNetworkPortionAlt(number); |
| /// @} |
| |
| String emergencyNumbers = ""; |
| String countryIso = getLastKnownEmergencyCountryIso(); |
| logd("country:" + countryIso); |
| |
| if (mIsHalVersionLessThan1Dot4) { |
| emergencyNumbers = getEmergencyNumberListForHalv1_3(); |
| |
| if (!TextUtils.isEmpty(emergencyNumbers)) { |
| return isEmergencyNumberFromEccListForHalv1_3(number, emergencyNumbers); |
| } |
| } |
| |
| logd("System property doesn't provide any emergency numbers." |
| + " Use embedded logic for determining ones."); |
| |
| // According spec 3GPP TS22.101, the following numbers should be |
| // ECC numbers when SIM/USIM is not present. |
| emergencyNumbers = ((isSimAbsent()) ? "112,911,000,08,110,118,119,999" : "112,911"); |
| |
| for (String emergencyNum : emergencyNumbers.split(",")) { |
| if (number.equals(emergencyNum)) { |
| return true; |
| } else { |
| for (String prefix : mEmergencyNumberPrefix) { |
| if (number.equals(prefix + emergencyNum)) { |
| return true; |
| } |
| } |
| } |
| } |
| |
| if (isSimAbsent()) { |
| // No ecclist system property, so use our own list. |
| if (countryIso != null) { |
| ShortNumberInfo info = ShortNumberInfo.getInstance(); |
| if (info.isEmergencyNumber(number, countryIso.toUpperCase(Locale.ROOT))) { |
| return true; |
| } else { |
| for (String prefix : mEmergencyNumberPrefix) { |
| if (info.isEmergencyNumber(prefix + number, |
| countryIso.toUpperCase(Locale.ROOT))) { |
| return true; |
| } |
| } |
| } |
| return false; |
| } |
| } |
| |
| return false; |
| } |
| |
| private boolean isEmergencyNumberFromEccListForHalv1_3(@NonNull String number, |
| @NonNull String emergencyNumbers) { |
| // searches through the comma-separated list for a match, |
| // return true if one is found. |
| for (String emergencyNum : emergencyNumbers.split(",")) { |
| if (number.equals(emergencyNum)) { |
| return true; |
| } else { |
| for (String prefix : mEmergencyNumberPrefix) { |
| if (number.equals(prefix + emergencyNum)) { |
| return true; |
| } |
| } |
| } |
| } |
| // no matches found against the list! |
| return false; |
| } |
| |
| /** |
| * Execute command for updating emergency number for test mode. |
| */ |
| public void executeEmergencyNumberTestModeCommand(int action, EmergencyNumber num) { |
| this.obtainMessage(EVENT_UPDATE_EMERGENCY_NUMBER_TEST_MODE, action, 0, num).sendToTarget(); |
| } |
| |
| /** |
| * Update emergency number list for test mode. |
| */ |
| private void updateEmergencyNumberListTestModeAndNotify(int action, EmergencyNumber num) { |
| if (action == ADD_EMERGENCY_NUMBER_TEST_MODE) { |
| if (!isEmergencyNumber(num.getNumber())) { |
| mEmergencyNumberListFromTestMode.add(num); |
| } |
| } else if (action == RESET_EMERGENCY_NUMBER_TEST_MODE) { |
| mEmergencyNumberListFromTestMode.clear(); |
| } else if (action == REMOVE_EMERGENCY_NUMBER_TEST_MODE) { |
| mEmergencyNumberListFromTestMode.remove(num); |
| } else { |
| loge("updateEmergencyNumberListTestModeAndNotify: Unexpected action in test mode."); |
| return; |
| } |
| if (!DBG) { |
| mEmergencyNumberListTestModeLocalLog.log( |
| "updateEmergencyNumberListTestModeAndNotify:" |
| + mEmergencyNumberListFromTestMode); |
| } |
| updateEmergencyNumberList(); |
| if (!DBG) { |
| mEmergencyNumberListLocalLog.log( |
| "updateEmergencyNumberListTestModeAndNotify:" |
| + mEmergencyNumberList); |
| } |
| notifyEmergencyNumberList(); |
| } |
| |
| private List<EmergencyNumber> getEmergencyNumberListFromEccListDatabaseAndTest() { |
| List<EmergencyNumber> mergedEmergencyNumberList = getEmergencyNumberListFromEccList(); |
| if (!mEmergencyNumberListFromDatabase.isEmpty()) { |
| loge("getEmergencyNumberListFromEccListDatabaseAndTest: radio indication is" |
| + " unavailable in 1.4 HAL."); |
| mergedEmergencyNumberList.addAll(mEmergencyNumberListFromDatabase); |
| mergedEmergencyNumberList.addAll(getEmergencyNumberListWithPrefix( |
| mEmergencyNumberListFromDatabase)); |
| } |
| mergedEmergencyNumberList.addAll(getEmergencyNumberListTestMode()); |
| EmergencyNumber.mergeSameNumbersInEmergencyNumberList(mergedEmergencyNumberList); |
| return mergedEmergencyNumberList; |
| } |
| |
| /** |
| * Get emergency number list for test. |
| */ |
| public List<EmergencyNumber> getEmergencyNumberListTestMode() { |
| return Collections.unmodifiableList(mEmergencyNumberListFromTestMode); |
| } |
| |
| @VisibleForTesting |
| public List<EmergencyNumber> getRadioEmergencyNumberList() { |
| return new ArrayList<>(mEmergencyNumberListFromRadio); |
| } |
| |
| private void logd(String str) { |
| Rlog.d(TAG, "[" + mPhoneId + "]" + str); |
| } |
| |
| private void logw(String str) { |
| Rlog.w(TAG, "[" + mPhoneId + "]" + str); |
| } |
| |
| private void loge(String str) { |
| Rlog.e(TAG, "[" + mPhoneId + "]" + str); |
| } |
| |
| private void writeUpdatedEmergencyNumberListMetrics( |
| List<EmergencyNumber> updatedEmergencyNumberList) { |
| if (updatedEmergencyNumberList == null) { |
| return; |
| } |
| for (EmergencyNumber num : updatedEmergencyNumberList) { |
| TelephonyMetrics.getInstance().writeEmergencyNumberUpdateEvent( |
| mPhone.getPhoneId(), num, getEmergencyNumberDbVersion()); |
| } |
| } |
| |
| /** |
| * @return {@code true} if emergency numbers sourced from modem/config should be ignored. |
| * {@code false} if emergency numbers sourced from modem/config should not be ignored. |
| */ |
| @VisibleForTesting |
| public boolean shouldModemConfigEmergencyNumbersBeIgnored() { |
| return mResources.getBoolean(com.android.internal.R.bool |
| .ignore_modem_config_emergency_numbers); |
| } |
| |
| /** |
| * @return {@code true} if emergency number routing from the android emergency number |
| * database should be ignored. |
| * {@code false} if emergency number routing from the android emergency number database |
| * should not be ignored. |
| */ |
| @VisibleForTesting |
| public boolean shouldEmergencyNumberRoutingFromDbBeIgnored() { |
| return mResources.getBoolean(com.android.internal.R.bool |
| .ignore_emergency_number_routing_from_db); |
| } |
| |
| /** |
| * Dump Emergency Number List info in the tracking |
| * |
| * @param fd FileDescriptor |
| * @param pw PrintWriter |
| * @param args args |
| */ |
| public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { |
| final IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " "); |
| ipw.println(" Hal Version:" + mPhone.getHalVersion()); |
| ipw.println(" ========================================= "); |
| |
| ipw.println(" Country Iso:" + getEmergencyCountryIso()); |
| ipw.println(" ========================================= "); |
| |
| ipw.println(" Database Version:" + getEmergencyNumberDbVersion()); |
| ipw.println(" ========================================= "); |
| |
| ipw.println("mEmergencyNumberListDatabaseLocalLog:"); |
| ipw.increaseIndent(); |
| mEmergencyNumberListDatabaseLocalLog.dump(fd, pw, args); |
| ipw.decreaseIndent(); |
| ipw.println(" ========================================= "); |
| |
| ipw.println("mEmergencyNumberListRadioLocalLog:"); |
| ipw.increaseIndent(); |
| mEmergencyNumberListRadioLocalLog.dump(fd, pw, args); |
| ipw.decreaseIndent(); |
| ipw.println(" ========================================= "); |
| |
| ipw.println("mEmergencyNumberListPrefixLocalLog:"); |
| ipw.increaseIndent(); |
| mEmergencyNumberListPrefixLocalLog.dump(fd, pw, args); |
| ipw.decreaseIndent(); |
| ipw.println(" ========================================= "); |
| |
| ipw.println("mEmergencyNumberListTestModeLocalLog:"); |
| ipw.increaseIndent(); |
| mEmergencyNumberListTestModeLocalLog.dump(fd, pw, args); |
| ipw.decreaseIndent(); |
| ipw.println(" ========================================= "); |
| |
| ipw.println("mEmergencyNumberListLocalLog (valid >= 1.4 HAL):"); |
| ipw.increaseIndent(); |
| mEmergencyNumberListLocalLog.dump(fd, pw, args); |
| ipw.decreaseIndent(); |
| ipw.println(" ========================================= "); |
| |
| if (mIsHalVersionLessThan1Dot4) { |
| getEmergencyNumberListForHalv1_3(); |
| ipw.println(" ========================================= "); |
| } |
| |
| ipw.println("Emergency Number List for Phone" + "(" + mPhone.getPhoneId() + ")"); |
| ipw.increaseIndent(); |
| ipw.println(getEmergencyNumberList()); |
| ipw.decreaseIndent(); |
| ipw.println(" ========================================= "); |
| |
| ipw.flush(); |
| } |
| } |