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