1 /* 2 * Copyright (C) 2018 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.internal.telephony.emergency; 18 19 import android.annotation.NonNull; 20 import android.content.BroadcastReceiver; 21 import android.content.Context; 22 import android.content.Intent; 23 import android.content.IntentFilter; 24 import android.content.pm.PackageManager; 25 import android.content.res.Resources; 26 import android.os.AsyncResult; 27 import android.os.Environment; 28 import android.os.Handler; 29 import android.os.Message; 30 import android.os.ParcelFileDescriptor; 31 import android.os.PersistableBundle; 32 import android.telephony.CarrierConfigManager; 33 import android.telephony.CellIdentity; 34 import android.telephony.PhoneNumberUtils; 35 import android.telephony.ServiceState; 36 import android.telephony.SubscriptionManager; 37 import android.telephony.TelephonyManager; 38 import android.telephony.emergency.EmergencyNumber; 39 import android.telephony.emergency.EmergencyNumber.EmergencyCallRouting; 40 import android.telephony.emergency.EmergencyNumber.EmergencyServiceCategories; 41 import android.text.TextUtils; 42 import android.util.ArrayMap; 43 import android.util.ArraySet; 44 import android.util.LocalLog; 45 46 import com.android.internal.annotations.VisibleForTesting; 47 import com.android.internal.telephony.CommandsInterface; 48 import com.android.internal.telephony.LocaleTracker; 49 import com.android.internal.telephony.Phone; 50 import com.android.internal.telephony.PhoneConstants; 51 import com.android.internal.telephony.PhoneFactory; 52 import com.android.internal.telephony.ServiceStateTracker; 53 import com.android.internal.telephony.TelephonyCapabilities; 54 import com.android.internal.telephony.flags.FeatureFlags; 55 import com.android.internal.telephony.metrics.EmergencyNumberStats; 56 import com.android.internal.telephony.metrics.TelephonyMetrics; 57 import com.android.internal.telephony.nano.PersistAtomsProto; 58 import com.android.internal.telephony.subscription.SubscriptionManagerService; 59 import com.android.internal.util.IndentingPrintWriter; 60 import com.android.phone.ecc.nano.ProtobufEccData; 61 import com.android.phone.ecc.nano.ProtobufEccData.EccInfo; 62 import com.android.telephony.Rlog; 63 64 import com.google.i18n.phonenumbers.ShortNumberInfo; 65 66 import java.io.BufferedInputStream; 67 import java.io.ByteArrayOutputStream; 68 import java.io.File; 69 import java.io.FileDescriptor; 70 import java.io.FileInputStream; 71 import java.io.IOException; 72 import java.io.InputStream; 73 import java.io.PrintWriter; 74 import java.util.ArrayList; 75 import java.util.Arrays; 76 import java.util.Collections; 77 import java.util.List; 78 import java.util.Locale; 79 import java.util.Map; 80 import java.util.Set; 81 import java.util.zip.GZIPInputStream; 82 83 /** 84 * Emergency Number Tracker that handles update of emergency number list from RIL and emergency 85 * number database. This is multi-sim based and each Phone has a EmergencyNumberTracker. 86 */ 87 public class EmergencyNumberTracker extends Handler { 88 private static final String TAG = EmergencyNumberTracker.class.getSimpleName(); 89 90 private static final int INVALID_DATABASE_VERSION = -1; 91 private static final String EMERGENCY_NUMBER_DB_OTA_FILE_NAME = "emergency_number_db"; 92 private static final String EMERGENCY_NUMBER_DB_OTA_FILE_PATH = 93 "misc/emergencynumberdb/" + EMERGENCY_NUMBER_DB_OTA_FILE_NAME; 94 95 /** Used for storing overrided (non-default) OTA database file path */ 96 private ParcelFileDescriptor mOverridedOtaDbParcelFileDescriptor = null; 97 98 /** @hide */ 99 public static boolean DBG = false; 100 /** @hide */ 101 public static final int ADD_EMERGENCY_NUMBER_TEST_MODE = 1; 102 /** @hide */ 103 public static final int REMOVE_EMERGENCY_NUMBER_TEST_MODE = 2; 104 /** @hide */ 105 public static final int RESET_EMERGENCY_NUMBER_TEST_MODE = 3; 106 107 private final CommandsInterface mCi; 108 private final Phone mPhone; 109 private final @NonNull FeatureFlags mFeatureFlags; 110 private int mPhoneId; 111 private String mCountryIso; 112 private String mLastKnownEmergencyCountryIso = ""; 113 private int mCurrentDatabaseVersion = INVALID_DATABASE_VERSION; 114 private int mCurrentOtaDatabaseVersion = INVALID_DATABASE_VERSION; 115 private Resources mResources = null; 116 /** 117 * Used for storing all specific mnc's along with the list of emergency numbers 118 * for which normal routing should be supported. 119 */ 120 private Map<String, Set<String>> mNormalRoutedNumbers = new ArrayMap<>(); 121 122 /** 123 * Indicates if the country iso is set by another subscription. 124 * @hide 125 */ 126 public boolean mIsCountrySetByAnotherSub = false; 127 private String[] mEmergencyNumberPrefix = new String[0]; 128 129 private static final String EMERGENCY_NUMBER_DB_ASSETS_FILE = "eccdata"; 130 131 private List<EmergencyNumber> mEmergencyNumberListFromDatabase = new ArrayList<>(); 132 private List<EmergencyNumber> mEmergencyNumberListFromRadio = new ArrayList<>(); 133 private List<EmergencyNumber> mEmergencyNumberListWithPrefix = new ArrayList<>(); 134 private List<EmergencyNumber> mEmergencyNumberListFromTestMode = new ArrayList<>(); 135 private List<EmergencyNumber> mEmergencyNumberList = new ArrayList<>(); 136 137 private final LocalLog mEmergencyNumberListDatabaseLocalLog = new LocalLog(16); 138 private final LocalLog mEmergencyNumberListRadioLocalLog = new LocalLog(16); 139 private final LocalLog mEmergencyNumberListPrefixLocalLog = new LocalLog(16); 140 private final LocalLog mEmergencyNumberListTestModeLocalLog = new LocalLog(16); 141 private final LocalLog mEmergencyNumberListLocalLog = new LocalLog(16); 142 143 /** Event indicating the update for the emergency number list from the radio. */ 144 private static final int EVENT_UNSOL_EMERGENCY_NUMBER_LIST = 1; 145 /** 146 * Event indicating the update for the emergency number list from the database due to the 147 * change of country code. 148 **/ 149 private static final int EVENT_UPDATE_DB_COUNTRY_ISO_CHANGED = 2; 150 /** Event indicating the update for the emergency number list in the testing mode. */ 151 private static final int EVENT_UPDATE_EMERGENCY_NUMBER_TEST_MODE = 3; 152 /** Event indicating the update for the emergency number prefix from carrier config. */ 153 private static final int EVENT_UPDATE_EMERGENCY_NUMBER_PREFIX = 4; 154 /** Event indicating the update for the OTA emergency number database. */ 155 @VisibleForTesting 156 public static final int EVENT_UPDATE_OTA_EMERGENCY_NUMBER_DB = 5; 157 /** Event indicating the override for the test OTA emergency number database. */ 158 @VisibleForTesting 159 public static final int EVENT_OVERRIDE_OTA_EMERGENCY_NUMBER_DB_FILE_PATH = 6; 160 161 private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() { 162 @Override 163 public void onReceive(Context context, Intent intent) { 164 if (intent.getAction().equals( 165 TelephonyManager.ACTION_NETWORK_COUNTRY_CHANGED)) { 166 int phoneId = intent.getIntExtra(PhoneConstants.PHONE_KEY, -1); 167 if (phoneId == mPhone.getPhoneId()) { 168 String countryIso = intent.getStringExtra( 169 TelephonyManager.EXTRA_NETWORK_COUNTRY); 170 logd("ACTION_NETWORK_COUNTRY_CHANGED: PhoneId: " + phoneId + " CountryIso: " 171 + countryIso); 172 173 // Update country iso change for available Phones 174 updateEmergencyCountryIsoAllPhones(countryIso == null ? "" : countryIso); 175 } 176 return; 177 } 178 } 179 }; 180 EmergencyNumberTracker(Phone phone, CommandsInterface ci, @NonNull FeatureFlags featureFlags)181 public EmergencyNumberTracker(Phone phone, CommandsInterface ci, 182 @NonNull FeatureFlags featureFlags) { 183 Context ctx = phone.getContext(); 184 185 mPhone = phone; 186 mCi = ci; 187 mFeatureFlags = featureFlags; 188 mResources = ctx.getResources(); 189 190 if (TelephonyCapabilities.minimalTelephonyCdmCheck(mFeatureFlags)) { 191 if (!ctx.getPackageManager().hasSystemFeature(PackageManager.FEATURE_TELEPHONY_CALLING) 192 && !ctx.getPackageManager() 193 .hasSystemFeature(PackageManager.FEATURE_TELEPHONY_MESSAGING)) { 194 throw new UnsupportedOperationException( 195 "EmergencyNumberTracker requires telephony calling or messaging feature to" 196 + " be enabled"); 197 } 198 } 199 200 if (mPhone != null) { 201 mPhoneId = phone.getPhoneId(); 202 CarrierConfigManager configMgr = (CarrierConfigManager) 203 mPhone.getContext().getSystemService(Context.CARRIER_CONFIG_SERVICE); 204 if (configMgr != null) { 205 PersistableBundle b = CarrierConfigManager.getCarrierConfigSubset( 206 mPhone.getContext(), 207 mPhone.getSubId(), 208 CarrierConfigManager.KEY_EMERGENCY_NUMBER_PREFIX_STRING_ARRAY); 209 if (!b.isEmpty()) { 210 mEmergencyNumberPrefix = b.getStringArray( 211 CarrierConfigManager.KEY_EMERGENCY_NUMBER_PREFIX_STRING_ARRAY); 212 } 213 214 // Callback which directly handle config change should be executed on handler thread 215 configMgr.registerCarrierConfigChangeListener(this::post, 216 (slotIndex, subId, carrierId, specificCarrierId) -> 217 onCarrierConfigUpdated(slotIndex)); 218 219 //register country change listener 220 IntentFilter filter = new IntentFilter( 221 TelephonyManager.ACTION_NETWORK_COUNTRY_CHANGED); 222 mPhone.getContext().registerReceiver(mIntentReceiver, filter); 223 224 } else { 225 loge("CarrierConfigManager is null."); 226 } 227 } else { 228 loge("mPhone is null."); 229 } 230 231 initializeDatabaseEmergencyNumberList(); 232 mCi.registerForEmergencyNumberList(this, EVENT_UNSOL_EMERGENCY_NUMBER_LIST, null); 233 } 234 235 /** 236 * Message handler for updating emergency number list from RIL, updating emergency number list 237 * from database if the country ISO is changed, and notifying the change of emergency number 238 * list. 239 * 240 * @param msg The message 241 */ 242 @Override handleMessage(Message msg)243 public void handleMessage(Message msg) { 244 switch (msg.what) { 245 case EVENT_UNSOL_EMERGENCY_NUMBER_LIST: 246 AsyncResult ar = (AsyncResult) msg.obj; 247 if (ar.result == null) { 248 loge("EVENT_UNSOL_EMERGENCY_NUMBER_LIST: Result from RIL is null."); 249 } else if ((ar.result != null) && (ar.exception == null)) { 250 updateRadioEmergencyNumberListAndNotify((List<EmergencyNumber>) ar.result); 251 } else { 252 loge("EVENT_UNSOL_EMERGENCY_NUMBER_LIST: Exception from RIL : " 253 + ar.exception); 254 } 255 break; 256 case EVENT_UPDATE_DB_COUNTRY_ISO_CHANGED: 257 if (msg.obj == null) { 258 loge("EVENT_UPDATE_DB_COUNTRY_ISO_CHANGED: Result from UpdateCountryIso is" 259 + " null."); 260 } else { 261 updateEmergencyNumberListDatabaseAndNotify((String) msg.obj); 262 } 263 break; 264 case EVENT_UPDATE_EMERGENCY_NUMBER_TEST_MODE: 265 if (msg.obj == null && msg.arg1 != RESET_EMERGENCY_NUMBER_TEST_MODE) { 266 loge("EVENT_UPDATE_EMERGENCY_NUMBER_TEST_MODE: Result from" 267 + " executeEmergencyNumberTestModeCommand is null."); 268 } else { 269 updateEmergencyNumberListTestModeAndNotify( 270 msg.arg1, (EmergencyNumber) msg.obj); 271 } 272 break; 273 case EVENT_UPDATE_EMERGENCY_NUMBER_PREFIX: 274 if (msg.obj == null) { 275 loge("EVENT_UPDATE_EMERGENCY_NUMBER_PREFIX: Result from" 276 + " onCarrierConfigChanged is null."); 277 } else { 278 updateEmergencyNumberPrefixAndNotify((String[]) msg.obj); 279 } 280 break; 281 case EVENT_UPDATE_OTA_EMERGENCY_NUMBER_DB: 282 updateOtaEmergencyNumberListDatabaseAndNotify(); 283 break; 284 case EVENT_OVERRIDE_OTA_EMERGENCY_NUMBER_DB_FILE_PATH: 285 if (msg.obj == null) { 286 overrideOtaEmergencyNumberDbFilePath(null); 287 } else { 288 overrideOtaEmergencyNumberDbFilePath((ParcelFileDescriptor) msg.obj); 289 } 290 break; 291 } 292 } 293 isAirplaneModeEnabled()294 private boolean isAirplaneModeEnabled() { 295 ServiceStateTracker serviceStateTracker = mPhone.getServiceStateTracker(); 296 if (serviceStateTracker != null) { 297 if (serviceStateTracker.getServiceState().getState() 298 == ServiceState.STATE_POWER_OFF) { 299 return true; 300 } 301 } 302 return false; 303 } 304 305 /** 306 * Checks if it's sim absent to decide whether to apply sim-absent emergency numbers from 3gpp 307 */ 308 @VisibleForTesting isSimAbsent()309 public boolean isSimAbsent() { 310 for (Phone phone: PhoneFactory.getPhones()) { 311 int slotId = SubscriptionManagerService.getInstance().getSlotIndex(phone.getSubId()); 312 // If slot id is invalid, it means that there is no sim card. 313 if (slotId != SubscriptionManager.INVALID_SIM_SLOT_INDEX) { 314 // If there is at least one sim active, sim is not absent; it returns false 315 logd("found sim in slotId: " + slotId + " subid: " + phone.getSubId()); 316 return false; 317 } 318 } 319 return true; 320 } 321 initializeDatabaseEmergencyNumberList()322 private void initializeDatabaseEmergencyNumberList() { 323 // If country iso has been cached when listener is set, don't need to cache the initial 324 // country iso and initial database. 325 if (mCountryIso == null) { 326 String countryForDatabaseCache = getInitialCountryIso().toLowerCase(Locale.ROOT); 327 updateEmergencyCountryIso(countryForDatabaseCache); 328 // Use the last known country to cache the database in APM 329 if (TextUtils.isEmpty(countryForDatabaseCache) 330 && isAirplaneModeEnabled()) { 331 countryForDatabaseCache = getCountryIsoForCachingDatabase(); 332 } 333 cacheEmergencyDatabaseByCountry(countryForDatabaseCache); 334 } 335 } 336 337 /** 338 * Update Emergency country iso for all the Phones 339 */ 340 @VisibleForTesting updateEmergencyCountryIsoAllPhones(String countryIso)341 public void updateEmergencyCountryIsoAllPhones(String countryIso) { 342 // Notify country iso change for current Phone 343 mIsCountrySetByAnotherSub = false; 344 updateEmergencyNumberDatabaseCountryChange(countryIso); 345 346 // Share and notify country iso change for other Phones if the country 347 // iso in their emergency number tracker is not available or the country 348 // iso there is set by another active subscription. 349 for (Phone phone: PhoneFactory.getPhones()) { 350 if (phone.getPhoneId() == mPhone.getPhoneId()) { 351 continue; 352 } 353 EmergencyNumberTracker emergencyNumberTracker; 354 if (phone != null && phone.getEmergencyNumberTracker() != null) { 355 emergencyNumberTracker = phone.getEmergencyNumberTracker(); 356 // If signal is lost, do not update the empty country iso for other slots. 357 if (!TextUtils.isEmpty(countryIso)) { 358 if (TextUtils.isEmpty(emergencyNumberTracker.getEmergencyCountryIso()) 359 || emergencyNumberTracker.mIsCountrySetByAnotherSub) { 360 emergencyNumberTracker.mIsCountrySetByAnotherSub = true; 361 emergencyNumberTracker.updateEmergencyNumberDatabaseCountryChange( 362 countryIso); 363 } 364 } 365 } 366 } 367 } 368 onCarrierConfigUpdated(int slotIndex)369 private void onCarrierConfigUpdated(int slotIndex) { 370 if (mPhone != null) { 371 if (slotIndex != mPhone.getPhoneId()) return; 372 373 PersistableBundle b = 374 CarrierConfigManager.getCarrierConfigSubset( 375 mPhone.getContext(), 376 mPhone.getSubId(), 377 CarrierConfigManager.KEY_EMERGENCY_NUMBER_PREFIX_STRING_ARRAY); 378 if (!b.isEmpty()) { 379 String[] emergencyNumberPrefix = 380 b.getStringArray( 381 CarrierConfigManager.KEY_EMERGENCY_NUMBER_PREFIX_STRING_ARRAY); 382 if (!Arrays.equals(mEmergencyNumberPrefix, emergencyNumberPrefix)) { 383 this.obtainMessage(EVENT_UPDATE_EMERGENCY_NUMBER_PREFIX, emergencyNumberPrefix) 384 .sendToTarget(); 385 } 386 } 387 } else { 388 loge("onCarrierConfigurationChanged mPhone is null."); 389 } 390 } 391 getInitialCountryIso()392 private String getInitialCountryIso() { 393 if (mPhone != null) { 394 ServiceStateTracker sst = mPhone.getServiceStateTracker(); 395 if (sst != null) { 396 LocaleTracker lt = sst.getLocaleTracker(); 397 if (lt != null) { 398 return lt.getCurrentCountry(); 399 } 400 } 401 } else { 402 loge("getInitialCountryIso mPhone is null."); 403 404 } 405 return ""; 406 } 407 408 /** 409 * Update Emergency Number database based on changed Country ISO. 410 * 411 * @param countryIso 412 * 413 * @hide 414 */ updateEmergencyNumberDatabaseCountryChange(String countryIso)415 public void updateEmergencyNumberDatabaseCountryChange(String countryIso) { 416 this.obtainMessage(EVENT_UPDATE_DB_COUNTRY_ISO_CHANGED, countryIso).sendToTarget(); 417 } 418 419 /** 420 * Update changed OTA Emergency Number database. 421 * 422 * @hide 423 */ updateOtaEmergencyNumberDatabase()424 public void updateOtaEmergencyNumberDatabase() { 425 this.obtainMessage(EVENT_UPDATE_OTA_EMERGENCY_NUMBER_DB).sendToTarget(); 426 } 427 428 /** 429 * Override the OTA Emergency Number database file path. 430 * 431 * @hide 432 */ updateOtaEmergencyNumberDbFilePath(ParcelFileDescriptor otaParcelFileDescriptor)433 public void updateOtaEmergencyNumberDbFilePath(ParcelFileDescriptor otaParcelFileDescriptor) { 434 this.obtainMessage( 435 EVENT_OVERRIDE_OTA_EMERGENCY_NUMBER_DB_FILE_PATH, 436 otaParcelFileDescriptor).sendToTarget(); 437 } 438 439 /** 440 * Override the OTA Emergency Number database file path. 441 * 442 * @hide 443 */ resetOtaEmergencyNumberDbFilePath()444 public void resetOtaEmergencyNumberDbFilePath() { 445 this.obtainMessage( 446 EVENT_OVERRIDE_OTA_EMERGENCY_NUMBER_DB_FILE_PATH, null).sendToTarget(); 447 } 448 convertEmergencyNumberFromEccInfo(EccInfo eccInfo, String countryIso, int emergencyCallRouting)449 private EmergencyNumber convertEmergencyNumberFromEccInfo(EccInfo eccInfo, String countryIso, 450 int emergencyCallRouting) { 451 String phoneNumber = eccInfo.phoneNumber.trim(); 452 if (phoneNumber.isEmpty()) { 453 loge("EccInfo has empty phone number."); 454 return null; 455 } 456 int emergencyServiceCategoryBitmask = 0; 457 for (int typeData : eccInfo.types) { 458 switch (typeData) { 459 case EccInfo.Type.POLICE: 460 emergencyServiceCategoryBitmask |= 461 EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_POLICE; 462 break; 463 case EccInfo.Type.AMBULANCE: 464 emergencyServiceCategoryBitmask |= 465 EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_AMBULANCE; 466 break; 467 case EccInfo.Type.FIRE: 468 emergencyServiceCategoryBitmask |= 469 EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_FIRE_BRIGADE; 470 break; 471 case EccInfo.Type.MARINE_GUARD: 472 emergencyServiceCategoryBitmask |= 473 EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_MARINE_GUARD; 474 break; 475 case EccInfo.Type.MOUNTAIN_RESCUE: 476 emergencyServiceCategoryBitmask |= 477 EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_MOUNTAIN_RESCUE; 478 break; 479 case EccInfo.Type.MIEC: 480 emergencyServiceCategoryBitmask |= 481 EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_MIEC; 482 break; 483 case EccInfo.Type.AIEC: 484 emergencyServiceCategoryBitmask |= 485 EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_AIEC; 486 break; 487 default: 488 // Ignores unknown types. 489 } 490 } 491 return new EmergencyNumber(phoneNumber, countryIso, "", 492 emergencyServiceCategoryBitmask, new ArrayList<String>(), 493 EmergencyNumber.EMERGENCY_NUMBER_SOURCE_DATABASE, emergencyCallRouting); 494 } 495 496 /** 497 * Get routing type of emergency numbers from DB. Update mnc's list with numbers that are 498 * to supported as normal routing type in the respective mnc's. 499 */ getRoutingInfoFromDB(EccInfo eccInfo, Map<String, Set<String>> normalRoutedNumbers)500 private int getRoutingInfoFromDB(EccInfo eccInfo, 501 Map<String, Set<String>> normalRoutedNumbers) { 502 int emergencyCallRouting; 503 switch(eccInfo.routing) 504 { 505 case EccInfo.Routing.NORMAL : 506 emergencyCallRouting = EmergencyNumber.EMERGENCY_CALL_ROUTING_NORMAL; 507 break; 508 case EccInfo.Routing.EMERGENCY : 509 emergencyCallRouting = EmergencyNumber.EMERGENCY_CALL_ROUTING_EMERGENCY; 510 break; 511 default: 512 emergencyCallRouting = EmergencyNumber.EMERGENCY_CALL_ROUTING_UNKNOWN; 513 } 514 String phoneNumber = eccInfo.phoneNumber.trim(); 515 if (phoneNumber.isEmpty()) { 516 loge("EccInfo has empty phone number."); 517 return emergencyCallRouting; 518 } 519 520 if (eccInfo.routing == EccInfo.Routing.NORMAL) { 521 emergencyCallRouting = EmergencyNumber.EMERGENCY_CALL_ROUTING_NORMAL; 522 523 if (((eccInfo.normalRoutingMncs).length != 0) 524 && (eccInfo.normalRoutingMncs[0].length() > 0)) { 525 emergencyCallRouting = EmergencyNumber.EMERGENCY_CALL_ROUTING_UNKNOWN; 526 527 for (String routingMnc : eccInfo.normalRoutingMncs) { 528 boolean mncExist = normalRoutedNumbers.containsKey(routingMnc); 529 Set phoneNumberList; 530 if (!mncExist) { 531 phoneNumberList = new ArraySet<String>(); 532 phoneNumberList.add(phoneNumber); 533 normalRoutedNumbers.put(routingMnc, phoneNumberList); 534 } else { 535 phoneNumberList = normalRoutedNumbers.get(routingMnc); 536 if (!phoneNumberList.contains(phoneNumber)) { 537 phoneNumberList.add(phoneNumber); 538 } 539 } 540 } 541 logd("Normal routed mncs with phoneNumbers:" + normalRoutedNumbers); 542 } 543 } 544 return emergencyCallRouting; 545 } 546 cacheEmergencyDatabaseByCountry(String countryIso)547 private void cacheEmergencyDatabaseByCountry(String countryIso) { 548 int assetsDatabaseVersion; 549 Map<String, Set<String>> assetNormalRoutedNumbers = new ArrayMap<>(); 550 551 // Read the Asset emergency number database 552 List<EmergencyNumber> updatedAssetEmergencyNumberList = new ArrayList<>(); 553 // try-with-resource. The 2 streams are auto closeable. 554 try (BufferedInputStream inputStream = new BufferedInputStream( 555 mPhone.getContext().getAssets().open(EMERGENCY_NUMBER_DB_ASSETS_FILE)); 556 GZIPInputStream gzipInputStream = new GZIPInputStream(inputStream)) { 557 ProtobufEccData.AllInfo allEccMessages = ProtobufEccData.AllInfo.parseFrom( 558 readInputStreamToByteArray(gzipInputStream)); 559 assetsDatabaseVersion = allEccMessages.revision; 560 logd(countryIso + " asset emergency database is loaded. Ver: " + assetsDatabaseVersion 561 + " Phone Id: " + mPhone.getPhoneId() + " countryIso: " + countryIso); 562 for (ProtobufEccData.CountryInfo countryEccInfo : allEccMessages.countries) { 563 if (countryEccInfo.isoCode.equals(countryIso.toUpperCase(Locale.ROOT))) { 564 for (ProtobufEccData.EccInfo eccInfo : countryEccInfo.eccs) { 565 int emergencyCallRouting = EmergencyNumber.EMERGENCY_CALL_ROUTING_UNKNOWN; 566 if (!shouldEmergencyNumberRoutingFromDbBeIgnored()) { 567 emergencyCallRouting = getRoutingInfoFromDB(eccInfo, 568 assetNormalRoutedNumbers); 569 } 570 updatedAssetEmergencyNumberList.add(convertEmergencyNumberFromEccInfo( 571 eccInfo, countryIso, emergencyCallRouting)); 572 } 573 } 574 } 575 EmergencyNumber.mergeSameNumbersInEmergencyNumberList(updatedAssetEmergencyNumberList); 576 } catch (IOException ex) { 577 logw("Cache asset emergency database failure: " + ex); 578 return; 579 } 580 581 // Cache OTA emergency number database 582 mCurrentOtaDatabaseVersion = cacheOtaEmergencyNumberDatabase(); 583 584 // Use a valid database that has higher version. 585 if (mCurrentOtaDatabaseVersion == INVALID_DATABASE_VERSION 586 && assetsDatabaseVersion == INVALID_DATABASE_VERSION) { 587 loge("No database available. Phone Id: " + mPhone.getPhoneId()); 588 } else if (assetsDatabaseVersion > mCurrentOtaDatabaseVersion) { 589 logd("Using Asset Emergency database. Version: " + assetsDatabaseVersion); 590 mCurrentDatabaseVersion = assetsDatabaseVersion; 591 mEmergencyNumberListFromDatabase = updatedAssetEmergencyNumberList; 592 mNormalRoutedNumbers.clear(); 593 mNormalRoutedNumbers = assetNormalRoutedNumbers; 594 } else { 595 logd("Using Ota Emergency database. Version: " + mCurrentOtaDatabaseVersion); 596 } 597 } 598 cacheOtaEmergencyNumberDatabase()599 private int cacheOtaEmergencyNumberDatabase() { 600 ProtobufEccData.AllInfo allEccMessages = null; 601 int otaDatabaseVersion = INVALID_DATABASE_VERSION; 602 Map<String, Set<String>> otaNormalRoutedNumbers = new ArrayMap<>(); 603 604 // Read the OTA emergency number database 605 List<EmergencyNumber> updatedOtaEmergencyNumberList = new ArrayList<>(); 606 607 File file; 608 // If OTA File partition is not available, try to reload the default one. 609 if (mOverridedOtaDbParcelFileDescriptor == null) { 610 file = new File(Environment.getDataDirectory(), EMERGENCY_NUMBER_DB_OTA_FILE_PATH); 611 } else { 612 try { 613 file = ParcelFileDescriptor.getFile(mOverridedOtaDbParcelFileDescriptor 614 .getFileDescriptor()).getAbsoluteFile(); 615 } catch (IOException ex) { 616 loge("Cache ota emergency database IOException: " + ex); 617 return INVALID_DATABASE_VERSION; 618 } 619 } 620 621 // try-with-resource. Those 3 streams are all auto closeable. 622 try (FileInputStream fileInputStream = new FileInputStream(file); 623 BufferedInputStream inputStream = new BufferedInputStream(fileInputStream); 624 GZIPInputStream gzipInputStream = new GZIPInputStream(inputStream)) { 625 allEccMessages = ProtobufEccData.AllInfo.parseFrom( 626 readInputStreamToByteArray(gzipInputStream)); 627 String countryIso = getLastKnownEmergencyCountryIso(); 628 logd(countryIso + " ota emergency database is loaded. Ver: " + otaDatabaseVersion); 629 otaDatabaseVersion = allEccMessages.revision; 630 for (ProtobufEccData.CountryInfo countryEccInfo : allEccMessages.countries) { 631 if (countryEccInfo.isoCode.equals(countryIso.toUpperCase(Locale.ROOT))) { 632 for (ProtobufEccData.EccInfo eccInfo : countryEccInfo.eccs) { 633 int emergencyCallRouting = EmergencyNumber.EMERGENCY_CALL_ROUTING_UNKNOWN; 634 if (!shouldEmergencyNumberRoutingFromDbBeIgnored()) { 635 emergencyCallRouting = getRoutingInfoFromDB(eccInfo, 636 otaNormalRoutedNumbers); 637 } 638 updatedOtaEmergencyNumberList.add(convertEmergencyNumberFromEccInfo( 639 eccInfo, countryIso, emergencyCallRouting)); 640 } 641 } 642 } 643 EmergencyNumber.mergeSameNumbersInEmergencyNumberList(updatedOtaEmergencyNumberList); 644 } catch (IOException ex) { 645 loge("Cache ota emergency database IOException: " + ex); 646 return INVALID_DATABASE_VERSION; 647 } 648 649 // Use a valid database that has higher version. 650 if (otaDatabaseVersion != INVALID_DATABASE_VERSION 651 && mCurrentDatabaseVersion < otaDatabaseVersion) { 652 mCurrentDatabaseVersion = otaDatabaseVersion; 653 mEmergencyNumberListFromDatabase = updatedOtaEmergencyNumberList; 654 mNormalRoutedNumbers.clear(); 655 mNormalRoutedNumbers = otaNormalRoutedNumbers; 656 } 657 return otaDatabaseVersion; 658 } 659 660 /** 661 * Util function to convert inputStream to byte array before parsing proto data. 662 */ readInputStreamToByteArray(InputStream inputStream)663 private static byte[] readInputStreamToByteArray(InputStream inputStream) throws IOException { 664 ByteArrayOutputStream buffer = new ByteArrayOutputStream(); 665 int nRead; 666 int size = 16 * 1024; // Read 16k chunks 667 byte[] data = new byte[size]; 668 while ((nRead = inputStream.read(data, 0, data.length)) != -1) { 669 buffer.write(data, 0, nRead); 670 } 671 buffer.flush(); 672 return buffer.toByteArray(); 673 } 674 updateRadioEmergencyNumberListAndNotify( List<EmergencyNumber> emergencyNumberListRadio)675 private void updateRadioEmergencyNumberListAndNotify( 676 List<EmergencyNumber> emergencyNumberListRadio) { 677 Collections.sort(emergencyNumberListRadio); 678 logd("updateRadioEmergencyNumberListAndNotify(): receiving " + emergencyNumberListRadio); 679 if (!emergencyNumberListRadio.equals(mEmergencyNumberListFromRadio)) { 680 try { 681 EmergencyNumber.mergeSameNumbersInEmergencyNumberList(emergencyNumberListRadio); 682 writeUpdatedEmergencyNumberListMetrics(emergencyNumberListRadio); 683 mEmergencyNumberListFromRadio = emergencyNumberListRadio; 684 if (!DBG) { 685 mEmergencyNumberListRadioLocalLog.log("updateRadioEmergencyNumberList:" 686 + emergencyNumberListRadio); 687 } 688 updateEmergencyNumberList(); 689 if (!DBG) { 690 mEmergencyNumberListLocalLog.log("updateRadioEmergencyNumberListAndNotify:" 691 + mEmergencyNumberList); 692 } 693 notifyEmergencyNumberList(); 694 } catch (NullPointerException ex) { 695 loge("updateRadioEmergencyNumberListAndNotify() Phone already destroyed: " + ex 696 + " EmergencyNumberList not notified"); 697 } 698 } 699 } 700 updateEmergencyNumberListDatabaseAndNotify(String countryIso)701 private void updateEmergencyNumberListDatabaseAndNotify(String countryIso) { 702 logd("updateEmergencyNumberListDatabaseAndNotify(): receiving countryIso: " 703 + countryIso); 704 updateEmergencyCountryIso(countryIso.toLowerCase(Locale.ROOT)); 705 // Use cached country iso in APM to load emergency number database. 706 if (TextUtils.isEmpty(countryIso)) { 707 countryIso = getCountryIsoForCachingDatabase(); 708 logd("updateEmergencyNumberListDatabaseAndNotify(): using cached APM country " 709 + countryIso); 710 } 711 cacheEmergencyDatabaseByCountry(countryIso); 712 writeUpdatedEmergencyNumberListMetrics(mEmergencyNumberListFromDatabase); 713 if (!DBG) { 714 mEmergencyNumberListDatabaseLocalLog.log( 715 "updateEmergencyNumberListDatabaseAndNotify:" 716 + mEmergencyNumberListFromDatabase); 717 } 718 updateEmergencyNumberList(); 719 if (!DBG) { 720 mEmergencyNumberListLocalLog.log("updateEmergencyNumberListDatabaseAndNotify:" 721 + mEmergencyNumberList); 722 } 723 notifyEmergencyNumberList(); 724 } 725 overrideOtaEmergencyNumberDbFilePath( ParcelFileDescriptor otaParcelableFileDescriptor)726 private void overrideOtaEmergencyNumberDbFilePath( 727 ParcelFileDescriptor otaParcelableFileDescriptor) { 728 logd("overrideOtaEmergencyNumberDbFilePath:" + otaParcelableFileDescriptor); 729 mOverridedOtaDbParcelFileDescriptor = otaParcelableFileDescriptor; 730 } 731 updateOtaEmergencyNumberListDatabaseAndNotify()732 private void updateOtaEmergencyNumberListDatabaseAndNotify() { 733 logd("updateOtaEmergencyNumberListDatabaseAndNotify():" 734 + " receiving Emegency Number database OTA update"); 735 mCurrentOtaDatabaseVersion = cacheOtaEmergencyNumberDatabase(); 736 if (mCurrentOtaDatabaseVersion != INVALID_DATABASE_VERSION) { 737 writeUpdatedEmergencyNumberListMetrics(mEmergencyNumberListFromDatabase); 738 if (!DBG) { 739 mEmergencyNumberListDatabaseLocalLog.log( 740 "updateOtaEmergencyNumberListDatabaseAndNotify:" 741 + mEmergencyNumberListFromDatabase); 742 } 743 updateEmergencyNumberList(); 744 if (!DBG) { 745 mEmergencyNumberListLocalLog.log("updateOtaEmergencyNumberListDatabaseAndNotify:" 746 + mEmergencyNumberList); 747 } 748 notifyEmergencyNumberList(); 749 } 750 } 751 updateEmergencyNumberPrefixAndNotify(String[] emergencyNumberPrefix)752 private void updateEmergencyNumberPrefixAndNotify(String[] emergencyNumberPrefix) { 753 logd("updateEmergencyNumberPrefixAndNotify(): receiving emergencyNumberPrefix: " 754 + Arrays.toString(emergencyNumberPrefix)); 755 mEmergencyNumberPrefix = emergencyNumberPrefix; 756 updateEmergencyNumberList(); 757 if (!DBG) { 758 mEmergencyNumberListLocalLog.log("updateEmergencyNumberPrefixAndNotify:" 759 + mEmergencyNumberList); 760 } 761 notifyEmergencyNumberList(); 762 } 763 notifyEmergencyNumberList()764 private void notifyEmergencyNumberList() { 765 try { 766 if (getEmergencyNumberList() != null) { 767 mPhone.notifyEmergencyNumberList(); 768 logd("notifyEmergencyNumberList(): notified"); 769 } 770 } catch (NullPointerException ex) { 771 loge("notifyEmergencyNumberList(): failure: Phone already destroyed: " + ex); 772 } 773 } 774 775 /** 776 * Update emergency numbers based on the radio, database, and test mode, if they are the same 777 * emergency numbers. 778 */ updateEmergencyNumberList()779 private void updateEmergencyNumberList() { 780 List<EmergencyNumber> mergedEmergencyNumberList = 781 new ArrayList<>(mEmergencyNumberListFromDatabase); 782 mergedEmergencyNumberList.addAll(mEmergencyNumberListFromRadio); 783 // 'updateEmergencyNumberList' is called every time there is a change for emergency numbers 784 // from radio indication, emergency numbers from database, emergency number prefix from 785 // carrier config, or test mode emergency numbers, the emergency number prefix is changed 786 // by carrier config, the emergency number list with prefix needs to be clear, and re-apply 787 // the new prefix for the current emergency numbers. 788 mEmergencyNumberListWithPrefix.clear(); 789 if (mEmergencyNumberPrefix.length != 0) { 790 mEmergencyNumberListWithPrefix.addAll(getEmergencyNumberListWithPrefix( 791 mEmergencyNumberListFromRadio)); 792 mEmergencyNumberListWithPrefix.addAll(getEmergencyNumberListWithPrefix( 793 mEmergencyNumberListFromDatabase)); 794 } 795 if (!DBG) { 796 mEmergencyNumberListPrefixLocalLog.log("updateEmergencyNumberList:" 797 + mEmergencyNumberListWithPrefix); 798 } 799 mergedEmergencyNumberList.addAll(mEmergencyNumberListWithPrefix); 800 mergedEmergencyNumberList.addAll(mEmergencyNumberListFromTestMode); 801 if (shouldDeterminingOfUrnsAndCategoriesWhileMergingIgnored()) { 802 EmergencyNumber.mergeSameNumbersInEmergencyNumberList(mergedEmergencyNumberList); 803 } else { 804 EmergencyNumber.mergeSameNumbersInEmergencyNumberList(mergedEmergencyNumberList, true); 805 } 806 mEmergencyNumberList = mergedEmergencyNumberList; 807 } 808 809 /** 810 * Get the emergency number list. 811 * 812 * @return the emergency number list based on radio indication or ril.ecclist if radio 813 * indication not support from the HAL. 814 */ getEmergencyNumberList()815 public List<EmergencyNumber> getEmergencyNumberList() { 816 List<EmergencyNumber> completeEmergencyNumberList; 817 if (!mEmergencyNumberListFromRadio.isEmpty()) { 818 completeEmergencyNumberList = Collections.unmodifiableList(mEmergencyNumberList); 819 } else { 820 completeEmergencyNumberList = getEmergencyNumberListFromEccListDatabaseAndTest(); 821 } 822 if (shouldAdjustForRouting()) { 823 return adjustRoutingForEmergencyNumbers(completeEmergencyNumberList); 824 } else { 825 return completeEmergencyNumberList; 826 } 827 } 828 829 /** 830 * Util function to check whether routing type and mnc value in emergency number needs 831 * to be adjusted for the current network mnc. 832 */ shouldAdjustForRouting()833 private boolean shouldAdjustForRouting() { 834 if (!shouldEmergencyNumberRoutingFromDbBeIgnored() && !mNormalRoutedNumbers.isEmpty()) { 835 return true; 836 } 837 return false; 838 } 839 840 /** 841 * Adjust emergency numbers with mnc and routing type based on the current network mnc. 842 */ adjustRoutingForEmergencyNumbers( List<EmergencyNumber> emergencyNumbers)843 private List<EmergencyNumber> adjustRoutingForEmergencyNumbers( 844 List<EmergencyNumber> emergencyNumbers) { 845 CellIdentity cellIdentity = mPhone.getCurrentCellIdentity(); 846 if (cellIdentity != null) { 847 String networkMnc = cellIdentity.getMncString(); 848 Set<String> normalRoutedPhoneNumbers = mNormalRoutedNumbers.get(networkMnc); 849 Set<String> normalRoutedPhoneNumbersWithPrefix = new ArraySet<String>(); 850 851 if (normalRoutedPhoneNumbers != null && !normalRoutedPhoneNumbers.isEmpty()) { 852 for (String num : normalRoutedPhoneNumbers) { 853 Set<String> phoneNumbersWithPrefix = addPrefixToEmergencyNumber(num); 854 if (phoneNumbersWithPrefix != null && !phoneNumbersWithPrefix.isEmpty()) { 855 normalRoutedPhoneNumbersWithPrefix.addAll(phoneNumbersWithPrefix); 856 } 857 } 858 } 859 List<EmergencyNumber> adjustedEmergencyNumberList = new ArrayList<>(); 860 int routing; 861 String mnc; 862 for (EmergencyNumber num : emergencyNumbers) { 863 routing = num.getEmergencyCallRouting(); 864 mnc = num.getMnc(); 865 if (num.isFromSources(EmergencyNumber.EMERGENCY_NUMBER_SOURCE_DATABASE)) { 866 if ((normalRoutedPhoneNumbers != null 867 && normalRoutedPhoneNumbers.contains(num.getNumber())) 868 || normalRoutedPhoneNumbersWithPrefix.contains(num.getNumber())) { 869 routing = EmergencyNumber.EMERGENCY_CALL_ROUTING_NORMAL; 870 mnc = networkMnc; 871 logd("adjustRoutingForEmergencyNumbers for number" + num.getNumber()); 872 } else if (routing == EmergencyNumber.EMERGENCY_CALL_ROUTING_UNKNOWN) { 873 routing = EmergencyNumber.EMERGENCY_CALL_ROUTING_EMERGENCY; 874 } 875 } 876 adjustedEmergencyNumberList.add(new EmergencyNumber(num.getNumber(), 877 num.getCountryIso(), mnc, 878 num.getEmergencyServiceCategoryBitmask(), 879 num.getEmergencyUrns(), num.getEmergencyNumberSourceBitmask(), 880 routing)); 881 } 882 return adjustedEmergencyNumberList; 883 } else { 884 return emergencyNumbers; 885 } 886 } 887 888 889 /** 890 * Util function to add prefix to the given emergency number. 891 */ addPrefixToEmergencyNumber(String number)892 private Set<String> addPrefixToEmergencyNumber(String number) { 893 Set<String> phoneNumbersWithPrefix = new ArraySet<String>(); 894 for (String prefix : mEmergencyNumberPrefix) { 895 if (!number.startsWith(prefix)) { 896 phoneNumbersWithPrefix.add(prefix + number); 897 } 898 } 899 return phoneNumbersWithPrefix; 900 } 901 902 /** 903 * Checks if the number is an emergency number in the current Phone. 904 * 905 * @return {@code true} if it is; {@code false} otherwise. 906 */ isEmergencyNumber(String number)907 public boolean isEmergencyNumber(String number) { 908 if (number == null) { 909 return false; 910 } 911 912 // Do not treat SIP address as emergency number 913 if (PhoneNumberUtils.isUriNumber(number)) { 914 return false; 915 } 916 917 // Strip the separators from the number before comparing it 918 // to the list. 919 number = PhoneNumberUtils.extractNetworkPortionAlt(number); 920 921 if (!mEmergencyNumberListFromRadio.isEmpty()) { 922 for (EmergencyNumber num : mEmergencyNumberList) { 923 if (num.getNumber().equals(number)) { 924 logd("Found in mEmergencyNumberList"); 925 return true; 926 } 927 } 928 return false; 929 } else { 930 boolean inEccList = isEmergencyNumberFromEccList(number); 931 boolean inEmergencyNumberDb = isEmergencyNumberFromDatabase(number); 932 boolean inEmergencyNumberTestList = isEmergencyNumberForTest(number); 933 logd("Search results - inRilEccList:" + inEccList 934 + " inEmergencyNumberDb:" + inEmergencyNumberDb + " inEmergencyNumberTestList: " 935 + inEmergencyNumberTestList); 936 return inEccList || inEmergencyNumberDb || inEmergencyNumberTestList; 937 } 938 } 939 940 /** 941 * Get the {@link EmergencyNumber} for the corresponding emergency number address. 942 * 943 * @param emergencyNumber - the supplied emergency number. 944 * @return the {@link EmergencyNumber} for the corresponding emergency number address. 945 */ getEmergencyNumber(String emergencyNumber)946 public EmergencyNumber getEmergencyNumber(String emergencyNumber) { 947 emergencyNumber = PhoneNumberUtils.stripSeparators(emergencyNumber); 948 for (EmergencyNumber num : getEmergencyNumberList()) { 949 if (num.getNumber().equals(emergencyNumber)) { 950 return num; 951 } 952 } 953 return null; 954 } 955 956 /** 957 * Get a list of the {@link EmergencyNumber}s that have the corresponding emergency number. 958 * Note: {@link #getEmergencyNumber(String)} assumes there is ONLY one record for a phone number 959 * when in reality there CAN be multiple instances if the same number is reported by the radio 960 * for a specific mcc and the emergency number database specifies the number without an mcc 961 * specified. 962 * 963 * @param emergencyNumber the emergency number to find. 964 * @return the list of emergency numbers matching. 965 */ getEmergencyNumbers(String emergencyNumber)966 public List<EmergencyNumber> getEmergencyNumbers(String emergencyNumber) { 967 final String toFind = PhoneNumberUtils.stripSeparators(emergencyNumber); 968 return getEmergencyNumberList().stream() 969 .filter(num -> num.getNumber().equals(toFind)) 970 .toList(); 971 } 972 973 /** 974 * Get the emergency service categories for the corresponding emergency number. The only 975 * trusted sources for the categories are the 976 * {@link EmergencyNumber#EMERGENCY_NUMBER_SOURCE_NETWORK_SIGNALING} and 977 * {@link EmergencyNumber#EMERGENCY_NUMBER_SOURCE_SIM}. 978 * 979 * @param emergencyNumber - the supplied emergency number. 980 * @return the emergency service categories for the corresponding emergency number. 981 */ getEmergencyServiceCategories(String emergencyNumber)982 public @EmergencyServiceCategories int getEmergencyServiceCategories(String emergencyNumber) { 983 emergencyNumber = PhoneNumberUtils.stripSeparators(emergencyNumber); 984 for (EmergencyNumber num : getEmergencyNumberList()) { 985 if (num.getNumber().equals(emergencyNumber)) { 986 if (num.isFromSources(EmergencyNumber.EMERGENCY_NUMBER_SOURCE_NETWORK_SIGNALING) 987 || num.isFromSources(EmergencyNumber.EMERGENCY_NUMBER_SOURCE_SIM)) { 988 return num.getEmergencyServiceCategoryBitmask(); 989 } 990 } 991 } 992 return EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_UNSPECIFIED; 993 } 994 995 /** 996 * Get the emergency call routing for the corresponding emergency number. The only trusted 997 * source for the routing is {@link EmergencyNumber#EMERGENCY_NUMBER_SOURCE_DATABASE}. 998 * 999 * @param emergencyNumber - the supplied emergency number. 1000 * @return the emergency call routing for the corresponding emergency number. 1001 */ getEmergencyCallRouting(String emergencyNumber)1002 public @EmergencyCallRouting int getEmergencyCallRouting(String emergencyNumber) { 1003 emergencyNumber = PhoneNumberUtils.stripSeparators(emergencyNumber); 1004 for (EmergencyNumber num : getEmergencyNumberList()) { 1005 if (num.getNumber().equals(emergencyNumber)) { 1006 if (num.isFromSources(EmergencyNumber.EMERGENCY_NUMBER_SOURCE_DATABASE)) { 1007 return num.getEmergencyCallRouting(); 1008 } 1009 } 1010 } 1011 return EmergencyNumber.EMERGENCY_CALL_ROUTING_UNKNOWN; 1012 } 1013 getEmergencyCountryIso()1014 public String getEmergencyCountryIso() { 1015 return mCountryIso; 1016 } 1017 getLastKnownEmergencyCountryIso()1018 public String getLastKnownEmergencyCountryIso() { 1019 return mLastKnownEmergencyCountryIso; 1020 } 1021 getCountryIsoForCachingDatabase()1022 private String getCountryIsoForCachingDatabase() { 1023 ServiceStateTracker sst = mPhone.getServiceStateTracker(); 1024 if (sst != null) { 1025 LocaleTracker lt = sst.getLocaleTracker(); 1026 if (lt != null) { 1027 return lt.getLastKnownCountryIso(); 1028 } 1029 } 1030 return ""; 1031 } 1032 getEmergencyNumberDbVersion()1033 public int getEmergencyNumberDbVersion() { 1034 return mCurrentDatabaseVersion; 1035 } 1036 getEmergencyNumberOtaDbVersion()1037 public int getEmergencyNumberOtaDbVersion() { 1038 return mCurrentOtaDatabaseVersion; 1039 } 1040 updateEmergencyCountryIso(String countryIso)1041 private synchronized void updateEmergencyCountryIso(String countryIso) { 1042 mCountryIso = countryIso; 1043 if (!TextUtils.isEmpty(mCountryIso)) { 1044 mLastKnownEmergencyCountryIso = mCountryIso; 1045 } 1046 mCurrentDatabaseVersion = INVALID_DATABASE_VERSION; 1047 } 1048 1049 /** 1050 * Get Emergency number list based on EccList. This util is used for solving backward 1051 * compatibility if device does not support the 1.4 IRadioIndication HAL that reports 1052 * emergency number list. 1053 */ getEmergencyNumberListFromEccList()1054 private List<EmergencyNumber> getEmergencyNumberListFromEccList() { 1055 List<EmergencyNumber> emergencyNumberList = new ArrayList<>(); 1056 1057 String emergencyNumbers = ((isSimAbsent()) ? "112,911,000,08,110,118,119,999" : "112,911"); 1058 for (String emergencyNum : emergencyNumbers.split(",")) { 1059 emergencyNumberList.add(getLabeledEmergencyNumberForEcclist(emergencyNum)); 1060 } 1061 if (mEmergencyNumberPrefix.length != 0) { 1062 emergencyNumberList.addAll(getEmergencyNumberListWithPrefix(emergencyNumberList)); 1063 } 1064 EmergencyNumber.mergeSameNumbersInEmergencyNumberList(emergencyNumberList); 1065 return emergencyNumberList; 1066 } 1067 getEmergencyNumberListWithPrefix( List<EmergencyNumber> emergencyNumberList)1068 private List<EmergencyNumber> getEmergencyNumberListWithPrefix( 1069 List<EmergencyNumber> emergencyNumberList) { 1070 List<EmergencyNumber> emergencyNumberListWithPrefix = new ArrayList<>(); 1071 if (emergencyNumberList != null) { 1072 for (EmergencyNumber num : emergencyNumberList) { 1073 Set<String> phoneNumbersWithPrefix = addPrefixToEmergencyNumber(num.getNumber()); 1074 if (phoneNumbersWithPrefix != null && !phoneNumbersWithPrefix.isEmpty()) { 1075 for (String numberWithPrefix : phoneNumbersWithPrefix) { 1076 emergencyNumberListWithPrefix.add(new EmergencyNumber( 1077 numberWithPrefix, num.getCountryIso(), 1078 num.getMnc(), num.getEmergencyServiceCategoryBitmask(), 1079 num.getEmergencyUrns(), num.getEmergencyNumberSourceBitmask(), 1080 num.getEmergencyCallRouting())); 1081 } 1082 } 1083 } 1084 } 1085 return emergencyNumberListWithPrefix; 1086 } 1087 isEmergencyNumberForTest(String number)1088 private boolean isEmergencyNumberForTest(String number) { 1089 number = PhoneNumberUtils.stripSeparators(number); 1090 for (EmergencyNumber num : mEmergencyNumberListFromTestMode) { 1091 if (num.getNumber().equals(number)) { 1092 return true; 1093 } 1094 } 1095 return false; 1096 } 1097 isEmergencyNumberFromDatabase(String number)1098 private boolean isEmergencyNumberFromDatabase(String number) { 1099 if (mEmergencyNumberListFromDatabase.isEmpty()) { 1100 return false; 1101 } 1102 number = PhoneNumberUtils.stripSeparators(number); 1103 for (EmergencyNumber num : mEmergencyNumberListFromDatabase) { 1104 if (num.getNumber().equals(number)) { 1105 return true; 1106 } 1107 } 1108 List<EmergencyNumber> emergencyNumberListFromDatabaseWithPrefix = 1109 getEmergencyNumberListWithPrefix(mEmergencyNumberListFromDatabase); 1110 for (EmergencyNumber num : emergencyNumberListFromDatabaseWithPrefix) { 1111 if (num.getNumber().equals(number)) { 1112 return true; 1113 } 1114 } 1115 return false; 1116 } 1117 getLabeledEmergencyNumberForEcclist(String number)1118 private EmergencyNumber getLabeledEmergencyNumberForEcclist(String number) { 1119 number = PhoneNumberUtils.stripSeparators(number); 1120 for (EmergencyNumber num : mEmergencyNumberListFromDatabase) { 1121 if (num.getNumber().equals(number)) { 1122 return new EmergencyNumber(number, getLastKnownEmergencyCountryIso() 1123 .toLowerCase(Locale.ROOT), "", num.getEmergencyServiceCategoryBitmask(), 1124 new ArrayList<String>(), EmergencyNumber.EMERGENCY_NUMBER_SOURCE_DATABASE, 1125 num.getEmergencyCallRouting()); 1126 } 1127 } 1128 return new EmergencyNumber(number, "", "", 1129 EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_UNSPECIFIED, 1130 new ArrayList<String>(), 0, 1131 EmergencyNumber.EMERGENCY_CALL_ROUTING_UNKNOWN); 1132 } 1133 1134 /** 1135 * Back-up old logics for {@link PhoneNumberUtils#isEmergencyNumberInternal} for legacy 1136 * and deprecate purpose. 1137 */ isEmergencyNumberFromEccList(String number)1138 private boolean isEmergencyNumberFromEccList(String number) { 1139 // If the number passed in is null, just return false: 1140 if (number == null) return false; 1141 1142 /// M: preprocess number for emergency check @{ 1143 // Move following logic to isEmergencyNumber() 1144 1145 // If the number passed in is a SIP address, return false, since the 1146 // concept of "emergency numbers" is only meaningful for calls placed 1147 // over the cell network. 1148 // (Be sure to do this check *before* calling extractNetworkPortionAlt(), 1149 // since the whole point of extractNetworkPortionAlt() is to filter out 1150 // any non-dialable characters (which would turn 'abc911def@example.com' 1151 // into '911', for example.)) 1152 //if (PhoneNumberUtils.isUriNumber(number)) { 1153 // return false; 1154 //} 1155 1156 // Strip the separators from the number before comparing it 1157 // to the list. 1158 //number = PhoneNumberUtils.extractNetworkPortionAlt(number); 1159 /// @} 1160 1161 String emergencyNumbers = ""; 1162 String countryIso = getLastKnownEmergencyCountryIso(); 1163 logd("country:" + countryIso); 1164 1165 logd("System property doesn't provide any emergency numbers." 1166 + " Use embedded logic for determining ones."); 1167 1168 // According spec 3GPP TS22.101, the following numbers should be 1169 // ECC numbers when SIM/USIM is not present. 1170 emergencyNumbers = ((isSimAbsent()) ? "112,911,000,08,110,118,119,999" : "112,911"); 1171 1172 for (String emergencyNum : emergencyNumbers.split(",")) { 1173 if (number.equals(emergencyNum)) { 1174 return true; 1175 } else { 1176 for (String prefix : mEmergencyNumberPrefix) { 1177 if (number.equals(prefix + emergencyNum)) { 1178 return true; 1179 } 1180 } 1181 } 1182 } 1183 1184 if (isSimAbsent()) { 1185 // No ecclist system property, so use our own list. 1186 if (countryIso != null) { 1187 ShortNumberInfo info = ShortNumberInfo.getInstance(); 1188 if (info.isEmergencyNumber(number, countryIso.toUpperCase(Locale.ROOT))) { 1189 return true; 1190 } else { 1191 for (String prefix : mEmergencyNumberPrefix) { 1192 if (info.isEmergencyNumber(prefix + number, 1193 countryIso.toUpperCase(Locale.ROOT))) { 1194 return true; 1195 } 1196 } 1197 } 1198 return false; 1199 } 1200 } 1201 1202 return false; 1203 } 1204 1205 /** 1206 * Execute command for updating emergency number for test mode. 1207 */ executeEmergencyNumberTestModeCommand(int action, EmergencyNumber num)1208 public void executeEmergencyNumberTestModeCommand(int action, EmergencyNumber num) { 1209 this.obtainMessage(EVENT_UPDATE_EMERGENCY_NUMBER_TEST_MODE, action, 0, num).sendToTarget(); 1210 } 1211 1212 /** 1213 * Update emergency number list for test mode. 1214 */ updateEmergencyNumberListTestModeAndNotify(int action, EmergencyNumber num)1215 private void updateEmergencyNumberListTestModeAndNotify(int action, EmergencyNumber num) { 1216 if (action == ADD_EMERGENCY_NUMBER_TEST_MODE) { 1217 if (!isEmergencyNumber(num.getNumber())) { 1218 mEmergencyNumberListFromTestMode.add(num); 1219 } 1220 } else if (action == RESET_EMERGENCY_NUMBER_TEST_MODE) { 1221 mEmergencyNumberListFromTestMode.clear(); 1222 } else if (action == REMOVE_EMERGENCY_NUMBER_TEST_MODE) { 1223 mEmergencyNumberListFromTestMode.remove(num); 1224 } else { 1225 loge("updateEmergencyNumberListTestModeAndNotify: Unexpected action in test mode."); 1226 return; 1227 } 1228 if (!DBG) { 1229 mEmergencyNumberListTestModeLocalLog.log( 1230 "updateEmergencyNumberListTestModeAndNotify:" 1231 + mEmergencyNumberListFromTestMode); 1232 } 1233 updateEmergencyNumberList(); 1234 if (!DBG) { 1235 mEmergencyNumberListLocalLog.log( 1236 "updateEmergencyNumberListTestModeAndNotify:" 1237 + mEmergencyNumberList); 1238 } 1239 notifyEmergencyNumberList(); 1240 } 1241 getEmergencyNumberListFromEccListDatabaseAndTest()1242 private List<EmergencyNumber> getEmergencyNumberListFromEccListDatabaseAndTest() { 1243 List<EmergencyNumber> mergedEmergencyNumberList = getEmergencyNumberListFromEccList(); 1244 if (!mEmergencyNumberListFromDatabase.isEmpty()) { 1245 loge("getEmergencyNumberListFromEccListDatabaseAndTest: radio indication is" 1246 + " unavailable in 1.4 HAL."); 1247 mergedEmergencyNumberList.addAll(mEmergencyNumberListFromDatabase); 1248 mergedEmergencyNumberList.addAll(getEmergencyNumberListWithPrefix( 1249 mEmergencyNumberListFromDatabase)); 1250 } 1251 mergedEmergencyNumberList.addAll(getEmergencyNumberListTestMode()); 1252 1253 if (shouldDeterminingOfUrnsAndCategoriesWhileMergingIgnored()) { 1254 EmergencyNumber.mergeSameNumbersInEmergencyNumberList(mergedEmergencyNumberList); 1255 } else { 1256 EmergencyNumber.mergeSameNumbersInEmergencyNumberList(mergedEmergencyNumberList, true); 1257 } 1258 return mergedEmergencyNumberList; 1259 } 1260 1261 /** 1262 * Get emergency number list for test. 1263 */ getEmergencyNumberListTestMode()1264 public List<EmergencyNumber> getEmergencyNumberListTestMode() { 1265 return Collections.unmodifiableList(mEmergencyNumberListFromTestMode); 1266 } 1267 1268 @VisibleForTesting getRadioEmergencyNumberList()1269 public List<EmergencyNumber> getRadioEmergencyNumberList() { 1270 return new ArrayList<>(mEmergencyNumberListFromRadio); 1271 } 1272 logd(String str)1273 private void logd(String str) { 1274 Rlog.d(TAG, "[" + mPhoneId + "]" + str); 1275 } 1276 logw(String str)1277 private void logw(String str) { 1278 Rlog.w(TAG, "[" + mPhoneId + "]" + str); 1279 } 1280 loge(String str)1281 private void loge(String str) { 1282 Rlog.e(TAG, "[" + mPhoneId + "]" + str); 1283 } 1284 writeUpdatedEmergencyNumberListMetrics( List<EmergencyNumber> updatedEmergencyNumberList)1285 private void writeUpdatedEmergencyNumberListMetrics( 1286 List<EmergencyNumber> updatedEmergencyNumberList) { 1287 if (updatedEmergencyNumberList == null) { 1288 return; 1289 } 1290 for (EmergencyNumber num : updatedEmergencyNumberList) { 1291 TelephonyMetrics.getInstance().writeEmergencyNumberUpdateEvent( 1292 mPhone.getPhoneId(), num, getEmergencyNumberDbVersion()); 1293 } 1294 } 1295 1296 /** 1297 * @return {@code true} if emergency numbers sourced from modem/config should be ignored. 1298 * {@code false} if emergency numbers sourced from modem/config should not be ignored. 1299 */ 1300 @VisibleForTesting shouldModemConfigEmergencyNumbersBeIgnored()1301 public boolean shouldModemConfigEmergencyNumbersBeIgnored() { 1302 return mResources.getBoolean(com.android.internal.R.bool 1303 .ignore_modem_config_emergency_numbers); 1304 } 1305 1306 /** 1307 * @return {@code true} if emergency number routing from the android emergency number 1308 * database should be ignored. 1309 * {@code false} if emergency number routing from the android emergency number database 1310 * should not be ignored. 1311 */ 1312 @VisibleForTesting shouldEmergencyNumberRoutingFromDbBeIgnored()1313 public boolean shouldEmergencyNumberRoutingFromDbBeIgnored() { 1314 return mResources.getBoolean(com.android.internal.R.bool 1315 .ignore_emergency_number_routing_from_db); 1316 } 1317 1318 1319 /** 1320 * @return {@code true} if determining of Urns & Service Categories while merging duplicate 1321 * numbers should be ignored. 1322 * {@code false} if determining of Urns & Service Categories while merging duplicate 1323 * numbers should not be ignored. 1324 */ 1325 @VisibleForTesting shouldDeterminingOfUrnsAndCategoriesWhileMergingIgnored()1326 public boolean shouldDeterminingOfUrnsAndCategoriesWhileMergingIgnored() { 1327 // TODO: Device config 1328 return false; 1329 } 1330 1331 /** 1332 * Captures the consolidated emergency numbers list and returns the array of 1333 * {@link PersistAtomsProto.EmergencyNumber}. 1334 */ getEmergencyNumbersProtoArray()1335 public PersistAtomsProto.EmergencyNumbersInfo[] getEmergencyNumbersProtoArray() { 1336 int otaVersion = Math.max(0, getEmergencyNumberOtaDbVersion()); 1337 int assetVersion = Math.max(0, getEmergencyNumberDbVersion()); 1338 boolean isDbRoutingIgnored = shouldEmergencyNumberRoutingFromDbBeIgnored(); 1339 List<EmergencyNumber> emergencyNumberList = getEmergencyNumberList(); 1340 logd("log emergency number list=" + emergencyNumberList + " for otaVersion=" + otaVersion 1341 + ", assetVersion=" + assetVersion + ", isDbRoutingIgnored=" + isDbRoutingIgnored); 1342 return EmergencyNumberStats.getInstance().convertEmergencyNumbersListToProto( 1343 emergencyNumberList, assetVersion, otaVersion, isDbRoutingIgnored); 1344 } 1345 1346 /** 1347 * Dump Emergency Number List info in the tracking 1348 * 1349 * @param fd FileDescriptor 1350 * @param pw PrintWriter 1351 * @param args args 1352 */ dump(FileDescriptor fd, PrintWriter pw, String[] args)1353 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 1354 final IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " "); 1355 ipw.println(" Country Iso:" + getEmergencyCountryIso()); 1356 ipw.println(" ========================================= "); 1357 1358 ipw.println(" Database Version:" + getEmergencyNumberDbVersion()); 1359 ipw.println(" ========================================= "); 1360 1361 ipw.println("mEmergencyNumberListDatabaseLocalLog:"); 1362 ipw.increaseIndent(); 1363 mEmergencyNumberListDatabaseLocalLog.dump(fd, pw, args); 1364 ipw.decreaseIndent(); 1365 ipw.println(" ========================================= "); 1366 1367 ipw.println("mEmergencyNumberListRadioLocalLog:"); 1368 ipw.increaseIndent(); 1369 mEmergencyNumberListRadioLocalLog.dump(fd, pw, args); 1370 ipw.decreaseIndent(); 1371 ipw.println(" ========================================= "); 1372 1373 ipw.println("mEmergencyNumberListPrefixLocalLog:"); 1374 ipw.increaseIndent(); 1375 mEmergencyNumberListPrefixLocalLog.dump(fd, pw, args); 1376 ipw.decreaseIndent(); 1377 ipw.println(" ========================================= "); 1378 1379 ipw.println("mEmergencyNumberListTestModeLocalLog:"); 1380 ipw.increaseIndent(); 1381 mEmergencyNumberListTestModeLocalLog.dump(fd, pw, args); 1382 ipw.decreaseIndent(); 1383 ipw.println(" ========================================= "); 1384 1385 ipw.println("mEmergencyNumberListLocalLog (valid >= 1.4 HAL):"); 1386 ipw.increaseIndent(); 1387 mEmergencyNumberListLocalLog.dump(fd, pw, args); 1388 ipw.decreaseIndent(); 1389 ipw.println(" ========================================= "); 1390 1391 ipw.println("Emergency Number List for Phone" + "(" + mPhone.getPhoneId() + ")"); 1392 ipw.increaseIndent(); 1393 ipw.println(getEmergencyNumberList()); 1394 ipw.decreaseIndent(); 1395 ipw.println(" ========================================= "); 1396 1397 ipw.flush(); 1398 } 1399 } 1400