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