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.os.AsyncResult; 24 import android.os.Handler; 25 import android.os.Message; 26 import android.os.PersistableBundle; 27 import android.os.SystemProperties; 28 import android.telephony.CarrierConfigManager; 29 import android.telephony.PhoneNumberUtils; 30 import android.telephony.Rlog; 31 import android.telephony.TelephonyManager; 32 import android.telephony.emergency.EmergencyNumber; 33 import android.telephony.emergency.EmergencyNumber.EmergencyCallRouting; 34 import android.telephony.emergency.EmergencyNumber.EmergencyServiceCategories; 35 import android.text.TextUtils; 36 import android.util.LocalLog; 37 38 import com.android.i18n.phonenumbers.ShortNumberInfo; 39 import com.android.internal.annotations.VisibleForTesting; 40 import com.android.internal.telephony.CommandsInterface; 41 import com.android.internal.telephony.LocaleTracker; 42 import com.android.internal.telephony.Phone; 43 import com.android.internal.telephony.PhoneConstants; 44 import com.android.internal.telephony.ServiceStateTracker; 45 import com.android.internal.telephony.SubscriptionController; 46 import com.android.internal.telephony.metrics.TelephonyMetrics; 47 import com.android.internal.util.IndentingPrintWriter; 48 import com.android.phone.ecc.nano.ProtobufEccData; 49 import com.android.phone.ecc.nano.ProtobufEccData.EccInfo; 50 51 import libcore.io.IoUtils; 52 53 import java.io.BufferedInputStream; 54 import java.io.ByteArrayOutputStream; 55 import java.io.FileDescriptor; 56 import java.io.IOException; 57 import java.io.InputStream; 58 import java.io.PrintWriter; 59 import java.util.ArrayList; 60 import java.util.Collections; 61 import java.util.List; 62 import java.util.zip.GZIPInputStream; 63 64 /** 65 * Emergency Number Tracker that handles update of emergency number list from RIL and emergency 66 * number database. This is multi-sim based and each Phone has a EmergencyNumberTracker. 67 */ 68 public class EmergencyNumberTracker extends Handler { 69 private static final String TAG = EmergencyNumberTracker.class.getSimpleName(); 70 71 /** @hide */ 72 public static boolean DBG = false; 73 /** @hide */ 74 public static final int ADD_EMERGENCY_NUMBER_TEST_MODE = 1; 75 /** @hide */ 76 public static final int REMOVE_EMERGENCY_NUMBER_TEST_MODE = 2; 77 /** @hide */ 78 public static final int RESET_EMERGENCY_NUMBER_TEST_MODE = 3; 79 80 private final CommandsInterface mCi; 81 private final Phone mPhone; 82 private String mCountryIso; 83 private String[] mEmergencyNumberPrefix = new String[0]; 84 85 private static final String EMERGENCY_NUMBER_DB_ASSETS_FILE = "eccdata"; 86 87 private List<EmergencyNumber> mEmergencyNumberListFromDatabase = new ArrayList<>(); 88 private List<EmergencyNumber> mEmergencyNumberListFromRadio = new ArrayList<>(); 89 private List<EmergencyNumber> mEmergencyNumberListWithPrefix = new ArrayList<>(); 90 private List<EmergencyNumber> mEmergencyNumberListFromTestMode = new ArrayList<>(); 91 private List<EmergencyNumber> mEmergencyNumberList = new ArrayList<>(); 92 93 private final LocalLog mEmergencyNumberListDatabaseLocalLog = new LocalLog(20); 94 private final LocalLog mEmergencyNumberListRadioLocalLog = new LocalLog(20); 95 private final LocalLog mEmergencyNumberListPrefixLocalLog = new LocalLog(20); 96 private final LocalLog mEmergencyNumberListTestModeLocalLog = new LocalLog(20); 97 private final LocalLog mEmergencyNumberListLocalLog = new LocalLog(20); 98 99 /** Event indicating the update for the emergency number list from the radio. */ 100 private static final int EVENT_UNSOL_EMERGENCY_NUMBER_LIST = 1; 101 /** 102 * Event indicating the update for the emergency number list from the database due to the 103 * change of country code. 104 **/ 105 private static final int EVENT_UPDATE_DB_COUNTRY_ISO_CHANGED = 2; 106 /** Event indicating the update for the emergency number list in the testing mode. */ 107 private static final int EVENT_UPDATE_EMERGENCY_NUMBER_TEST_MODE = 3; 108 /** Event indicating the update for the emergency number prefix from carrier config. */ 109 private static final int EVENT_UPDATE_EMERGENCY_NUMBER_PREFIX = 4; 110 111 private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() { 112 @Override 113 public void onReceive(Context context, Intent intent) { 114 if (intent.getAction().equals( 115 CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED)) { 116 onCarrierConfigChanged(); 117 return; 118 } else if (intent.getAction().equals( 119 TelephonyManager.ACTION_NETWORK_COUNTRY_CHANGED)) { 120 int phoneId = intent.getIntExtra(PhoneConstants.PHONE_KEY, -1); 121 if (phoneId == mPhone.getPhoneId()) { 122 String countryIso = intent.getStringExtra( 123 TelephonyManager.EXTRA_NETWORK_COUNTRY); 124 logd("ACTION_NETWORK_COUNTRY_CHANGED: PhoneId: " + phoneId + " CountryIso: " 125 + countryIso); 126 // Sometimes the country is updated as an empty string when the network signal 127 // is lost; though we may not call emergency when there is no signal, we want 128 // to keep the old country iso to provide country-related emergency numbers, 129 // because they think they are still in that country. So we do need to update 130 // country change in this case. 131 if (TextUtils.isEmpty(countryIso)) { 132 return; 133 } 134 updateEmergencyNumberDatabaseCountryChange(countryIso); 135 } 136 return; 137 } 138 } 139 }; 140 EmergencyNumberTracker(Phone phone, CommandsInterface ci)141 public EmergencyNumberTracker(Phone phone, CommandsInterface ci) { 142 mPhone = phone; 143 mCi = ci; 144 if (mPhone != null) { 145 CarrierConfigManager configMgr = (CarrierConfigManager) 146 mPhone.getContext().getSystemService(Context.CARRIER_CONFIG_SERVICE); 147 if (configMgr != null) { 148 PersistableBundle b = configMgr.getConfigForSubId(mPhone.getSubId()); 149 if (b != null) { 150 mEmergencyNumberPrefix = b.getStringArray( 151 CarrierConfigManager.KEY_EMERGENCY_NUMBER_PREFIX_STRING_ARRAY); 152 } 153 } else { 154 loge("CarrierConfigManager is null."); 155 } 156 157 // Receive Carrier Config Changes 158 IntentFilter filter = new IntentFilter( 159 CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED); 160 // Receive Telephony Network Country Changes 161 filter.addAction(TelephonyManager.ACTION_NETWORK_COUNTRY_CHANGED); 162 163 mPhone.getContext().registerReceiver(mIntentReceiver, filter); 164 } else { 165 loge("mPhone is null."); 166 } 167 168 initializeDatabaseEmergencyNumberList(); 169 mCi.registerForEmergencyNumberList(this, EVENT_UNSOL_EMERGENCY_NUMBER_LIST, null); 170 } 171 172 /** 173 * Message handler for updating emergency number list from RIL, updating emergency number list 174 * from database if the country ISO is changed, and notifying the change of emergency number 175 * list. 176 * 177 * @param msg The message 178 */ 179 @Override handleMessage(Message msg)180 public void handleMessage(Message msg) { 181 switch (msg.what) { 182 case EVENT_UNSOL_EMERGENCY_NUMBER_LIST: 183 AsyncResult ar = (AsyncResult) msg.obj; 184 if (ar.result == null) { 185 loge("EVENT_UNSOL_EMERGENCY_NUMBER_LIST: Result from RIL is null."); 186 } else if ((ar.result != null) && (ar.exception == null)) { 187 updateRadioEmergencyNumberListAndNotify((List<EmergencyNumber>) ar.result); 188 } else { 189 loge("EVENT_UNSOL_EMERGENCY_NUMBER_LIST: Exception from RIL : " 190 + ar.exception); 191 } 192 break; 193 case EVENT_UPDATE_DB_COUNTRY_ISO_CHANGED: 194 if (msg.obj == null) { 195 loge("EVENT_UPDATE_DB_COUNTRY_ISO_CHANGED: Result from UpdateCountryIso is" 196 + " null."); 197 } else { 198 updateEmergencyNumberListDatabaseAndNotify((String) msg.obj); 199 } 200 break; 201 case EVENT_UPDATE_EMERGENCY_NUMBER_TEST_MODE: 202 if (msg.obj == null) { 203 loge("EVENT_UPDATE_EMERGENCY_NUMBER_TEST_MODE: Result from" 204 + " executeEmergencyNumberTestModeCommand is null."); 205 } else { 206 updateEmergencyNumberListTestModeAndNotify( 207 msg.arg1, (EmergencyNumber) msg.obj); 208 } 209 break; 210 case EVENT_UPDATE_EMERGENCY_NUMBER_PREFIX: 211 if (msg.obj == null) { 212 loge("EVENT_UPDATE_EMERGENCY_NUMBER_PREFIX: Result from" 213 + " onCarrierConfigChanged is null."); 214 } else { 215 updateEmergencyNumberPrefixAndNotify((String[]) msg.obj); 216 } 217 218 } 219 } 220 initializeDatabaseEmergencyNumberList()221 private void initializeDatabaseEmergencyNumberList() { 222 // If country iso has been cached when listener is set, don't need to cache the initial 223 // country iso and initial database. 224 if (mCountryIso == null) { 225 mCountryIso = getInitialCountryIso().toLowerCase(); 226 cacheEmergencyDatabaseByCountry(mCountryIso); 227 } 228 } 229 onCarrierConfigChanged()230 private void onCarrierConfigChanged() { 231 if (mPhone != null) { 232 CarrierConfigManager configMgr = (CarrierConfigManager) 233 mPhone.getContext().getSystemService(Context.CARRIER_CONFIG_SERVICE); 234 if (configMgr != null) { 235 PersistableBundle b = configMgr.getConfigForSubId(mPhone.getSubId()); 236 if (b != null) { 237 String[] emergencyNumberPrefix = b.getStringArray( 238 CarrierConfigManager.KEY_EMERGENCY_NUMBER_PREFIX_STRING_ARRAY); 239 if (!mEmergencyNumberPrefix.equals(emergencyNumberPrefix)) { 240 this.obtainMessage(EVENT_UPDATE_EMERGENCY_NUMBER_PREFIX, 241 emergencyNumberPrefix).sendToTarget(); 242 } 243 } 244 } 245 } else { 246 loge("onCarrierConfigChanged mPhone is null."); 247 } 248 } 249 getInitialCountryIso()250 private String getInitialCountryIso() { 251 if (mPhone != null) { 252 ServiceStateTracker sst = mPhone.getServiceStateTracker(); 253 if (sst != null) { 254 LocaleTracker lt = sst.getLocaleTracker(); 255 if (lt != null) { 256 return lt.getCurrentCountry(); 257 } 258 } 259 } else { 260 loge("getInitialCountryIso mPhone is null."); 261 262 } 263 return ""; 264 } 265 266 /** 267 * Update Emergency Number database based on changed Country ISO. 268 * 269 * @param countryIso 270 * 271 * @hide 272 */ updateEmergencyNumberDatabaseCountryChange(String countryIso)273 public void updateEmergencyNumberDatabaseCountryChange(String countryIso) { 274 this.obtainMessage(EVENT_UPDATE_DB_COUNTRY_ISO_CHANGED, countryIso).sendToTarget(); 275 } 276 convertEmergencyNumberFromEccInfo(EccInfo eccInfo, String countryIso)277 private EmergencyNumber convertEmergencyNumberFromEccInfo(EccInfo eccInfo, String countryIso) { 278 String phoneNumber = eccInfo.phoneNumber.trim(); 279 if (phoneNumber.isEmpty()) { 280 loge("EccInfo has empty phone number."); 281 return null; 282 } 283 int emergencyServiceCategoryBitmask = 0; 284 for (int typeData : eccInfo.types) { 285 switch (typeData) { 286 case EccInfo.Type.POLICE: 287 emergencyServiceCategoryBitmask = emergencyServiceCategoryBitmask == 0 288 ? EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_POLICE 289 : emergencyServiceCategoryBitmask 290 | EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_POLICE; 291 break; 292 case EccInfo.Type.AMBULANCE: 293 emergencyServiceCategoryBitmask = emergencyServiceCategoryBitmask == 0 294 ? EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_AMBULANCE 295 : emergencyServiceCategoryBitmask 296 | EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_AMBULANCE; 297 break; 298 case EccInfo.Type.FIRE: 299 emergencyServiceCategoryBitmask = emergencyServiceCategoryBitmask == 0 300 ? EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_FIRE_BRIGADE 301 : emergencyServiceCategoryBitmask 302 | EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_FIRE_BRIGADE; 303 break; 304 default: 305 // Ignores unknown types. 306 } 307 } 308 return new EmergencyNumber(phoneNumber, countryIso, "", emergencyServiceCategoryBitmask, 309 new ArrayList<String>(), EmergencyNumber.EMERGENCY_NUMBER_SOURCE_DATABASE, 310 EmergencyNumber.EMERGENCY_CALL_ROUTING_UNKNOWN); 311 } 312 cacheEmergencyDatabaseByCountry(String countryIso)313 private void cacheEmergencyDatabaseByCountry(String countryIso) { 314 BufferedInputStream inputStream = null; 315 ProtobufEccData.AllInfo allEccMessages = null; 316 List<EmergencyNumber> updatedEmergencyNumberList = new ArrayList<>(); 317 try { 318 inputStream = new BufferedInputStream( 319 mPhone.getContext().getAssets().open(EMERGENCY_NUMBER_DB_ASSETS_FILE)); 320 allEccMessages = ProtobufEccData.AllInfo.parseFrom(readInputStreamToByteArray( 321 new GZIPInputStream(inputStream))); 322 logd(countryIso + " emergency database is loaded. "); 323 for (ProtobufEccData.CountryInfo countryEccInfo : allEccMessages.countries) { 324 if (countryEccInfo.isoCode.equals(countryIso.toUpperCase())) { 325 for (ProtobufEccData.EccInfo eccInfo : countryEccInfo.eccs) { 326 updatedEmergencyNumberList.add(convertEmergencyNumberFromEccInfo( 327 eccInfo, countryIso)); 328 } 329 } 330 } 331 EmergencyNumber.mergeSameNumbersInEmergencyNumberList(updatedEmergencyNumberList); 332 mEmergencyNumberListFromDatabase = updatedEmergencyNumberList; 333 } catch (IOException ex) { 334 loge("Cache emergency database failure: " + ex); 335 } finally { 336 IoUtils.closeQuietly(inputStream); 337 } 338 } 339 340 /** 341 * Util function to convert inputStream to byte array before parsing proto data. 342 */ readInputStreamToByteArray(InputStream inputStream)343 private static byte[] readInputStreamToByteArray(InputStream inputStream) throws IOException { 344 ByteArrayOutputStream buffer = new ByteArrayOutputStream(); 345 int nRead; 346 int size = 16 * 1024; // Read 16k chunks 347 byte[] data = new byte[size]; 348 while ((nRead = inputStream.read(data, 0, data.length)) != -1) { 349 buffer.write(data, 0, nRead); 350 } 351 buffer.flush(); 352 return buffer.toByteArray(); 353 } 354 updateRadioEmergencyNumberListAndNotify( List<EmergencyNumber> emergencyNumberListRadio)355 private void updateRadioEmergencyNumberListAndNotify( 356 List<EmergencyNumber> emergencyNumberListRadio) { 357 Collections.sort(emergencyNumberListRadio); 358 logd("updateRadioEmergencyNumberListAndNotify(): receiving " + emergencyNumberListRadio); 359 if (!emergencyNumberListRadio.equals(mEmergencyNumberListFromRadio)) { 360 try { 361 EmergencyNumber.mergeSameNumbersInEmergencyNumberList(emergencyNumberListRadio); 362 writeUpdatedEmergencyNumberListMetrics(emergencyNumberListRadio); 363 mEmergencyNumberListFromRadio = emergencyNumberListRadio; 364 if (!DBG) { 365 mEmergencyNumberListRadioLocalLog.log("updateRadioEmergencyNumberList:" 366 + emergencyNumberListRadio); 367 } 368 updateEmergencyNumberList(); 369 if (!DBG) { 370 mEmergencyNumberListLocalLog.log("updateRadioEmergencyNumberListAndNotify:" 371 + mEmergencyNumberList); 372 } 373 notifyEmergencyNumberList(); 374 } catch (NullPointerException ex) { 375 loge("updateRadioEmergencyNumberListAndNotify() Phone already destroyed: " + ex 376 + " EmergencyNumberList not notified"); 377 } 378 } 379 } 380 updateEmergencyNumberListDatabaseAndNotify(String countryIso)381 private void updateEmergencyNumberListDatabaseAndNotify(String countryIso) { 382 logd("updateEmergencyNumberListDatabaseAndNotify(): receiving countryIso: " 383 + countryIso); 384 385 mCountryIso = countryIso.toLowerCase(); 386 cacheEmergencyDatabaseByCountry(countryIso); 387 writeUpdatedEmergencyNumberListMetrics(mEmergencyNumberListFromDatabase); 388 if (!DBG) { 389 mEmergencyNumberListDatabaseLocalLog.log( 390 "updateEmergencyNumberListDatabaseAndNotify:" 391 + mEmergencyNumberListFromDatabase); 392 } 393 updateEmergencyNumberList(); 394 if (!DBG) { 395 mEmergencyNumberListLocalLog.log("updateEmergencyNumberListDatabaseAndNotify:" 396 + mEmergencyNumberList); 397 } 398 notifyEmergencyNumberList(); 399 } 400 updateEmergencyNumberPrefixAndNotify(String[] emergencyNumberPrefix)401 private void updateEmergencyNumberPrefixAndNotify(String[] emergencyNumberPrefix) { 402 logd("updateEmergencyNumberPrefixAndNotify(): receiving emergencyNumberPrefix: " 403 + emergencyNumberPrefix.toString()); 404 mEmergencyNumberPrefix = emergencyNumberPrefix; 405 updateEmergencyNumberList(); 406 if (!DBG) { 407 mEmergencyNumberListLocalLog.log("updateEmergencyNumberPrefixAndNotify:" 408 + mEmergencyNumberList); 409 } 410 notifyEmergencyNumberList(); 411 } 412 notifyEmergencyNumberList()413 private void notifyEmergencyNumberList() { 414 try { 415 if (getEmergencyNumberList() != null) { 416 mPhone.notifyEmergencyNumberList(); 417 logd("notifyEmergencyNumberList(): notified"); 418 } 419 } catch (NullPointerException ex) { 420 loge("notifyEmergencyNumberList(): failure: Phone already destroyed: " + ex); 421 } 422 } 423 424 /** 425 * Update emergency numbers based on the radio, database, and test mode, if they are the same 426 * emergency numbers. 427 */ updateEmergencyNumberList()428 private void updateEmergencyNumberList() { 429 List<EmergencyNumber> mergedEmergencyNumberList = 430 new ArrayList<>(mEmergencyNumberListFromDatabase); 431 mergedEmergencyNumberList.addAll(mEmergencyNumberListFromRadio); 432 // 'updateEmergencyNumberList' is called every time there is a change for emergency numbers 433 // from radio indication, emergency numbers from database, emergency number prefix from 434 // carrier config, or test mode emergency numbers, the emergency number prefix is changed 435 // by carrier config, the emergency number list with prefix needs to be clear, and re-apply 436 // the new prefix for the current emergency numbers. 437 mEmergencyNumberListWithPrefix.clear(); 438 if (mEmergencyNumberPrefix.length != 0) { 439 mEmergencyNumberListWithPrefix.addAll(getEmergencyNumberListWithPrefix( 440 mEmergencyNumberListFromRadio)); 441 mEmergencyNumberListWithPrefix.addAll(getEmergencyNumberListWithPrefix( 442 mEmergencyNumberListFromDatabase)); 443 } 444 if (!DBG) { 445 mEmergencyNumberListPrefixLocalLog.log("updateEmergencyNumberList:" 446 + mEmergencyNumberListWithPrefix); 447 } 448 mergedEmergencyNumberList.addAll(mEmergencyNumberListWithPrefix); 449 mergedEmergencyNumberList.addAll(mEmergencyNumberListFromTestMode); 450 EmergencyNumber.mergeSameNumbersInEmergencyNumberList(mergedEmergencyNumberList); 451 mEmergencyNumberList = mergedEmergencyNumberList; 452 } 453 454 /** 455 * Get the emergency number list. 456 * 457 * @return the emergency number list based on radio indication or ril.ecclist if radio 458 * indication not support from the HAL. 459 */ getEmergencyNumberList()460 public List<EmergencyNumber> getEmergencyNumberList() { 461 if (!mEmergencyNumberListFromRadio.isEmpty()) { 462 return Collections.unmodifiableList(mEmergencyNumberList); 463 } else { 464 return getEmergencyNumberListFromEccListAndTest(); 465 } 466 } 467 468 /** 469 * Checks if the number is an emergency number in the current Phone. 470 * 471 * @return {@code true} if it is; {@code false} otherwise. 472 */ isEmergencyNumber(String number, boolean exactMatch)473 public boolean isEmergencyNumber(String number, boolean exactMatch) { 474 if (number == null) { 475 return false; 476 } 477 number = PhoneNumberUtils.stripSeparators(number); 478 if (!mEmergencyNumberListFromRadio.isEmpty()) { 479 for (EmergencyNumber num : mEmergencyNumberList) { 480 // According to com.android.i18n.phonenumbers.ShortNumberInfo, in 481 // these countries, if extra digits are added to an emergency number, 482 // it no longer connects to the emergency service. 483 if (mCountryIso.equals("br") || mCountryIso.equals("cl") 484 || mCountryIso.equals("ni")) { 485 exactMatch = true; 486 } else { 487 exactMatch = false || exactMatch; 488 } 489 if (exactMatch) { 490 if (num.getNumber().equals(number)) { 491 return true; 492 } 493 } else { 494 if (number.startsWith(num.getNumber())) { 495 return true; 496 } 497 } 498 } 499 return false; 500 } else { 501 return isEmergencyNumberFromEccList(number, exactMatch) 502 || isEmergencyNumberForTest(number); 503 } 504 } 505 506 /** 507 * Get the {@link EmergencyNumber} for the corresponding emergency number address. 508 * 509 * @param emergencyNumber - the supplied emergency number. 510 * @return the {@link EmergencyNumber} for the corresponding emergency number address. 511 */ getEmergencyNumber(String emergencyNumber)512 public EmergencyNumber getEmergencyNumber(String emergencyNumber) { 513 emergencyNumber = PhoneNumberUtils.stripSeparators(emergencyNumber); 514 for (EmergencyNumber num : getEmergencyNumberList()) { 515 if (num.getNumber().equals(emergencyNumber)) { 516 return num; 517 } 518 } 519 return null; 520 } 521 522 /** 523 * Get the emergency service categories for the corresponding emergency number. The only 524 * trusted sources for the categories are the 525 * {@link EmergencyNumber#EMERGENCY_NUMBER_SOURCE_NETWORK_SIGNALING} and 526 * {@link EmergencyNumber#EMERGENCY_NUMBER_SOURCE_SIM}. 527 * 528 * @param emergencyNumber - the supplied emergency number. 529 * @return the emergency service categories for the corresponding emergency number. 530 */ getEmergencyServiceCategories(String emergencyNumber)531 public @EmergencyServiceCategories int getEmergencyServiceCategories(String emergencyNumber) { 532 emergencyNumber = PhoneNumberUtils.stripSeparators(emergencyNumber); 533 for (EmergencyNumber num : getEmergencyNumberList()) { 534 if (num.getNumber().equals(emergencyNumber)) { 535 if (num.isFromSources(EmergencyNumber.EMERGENCY_NUMBER_SOURCE_NETWORK_SIGNALING) 536 || num.isFromSources(EmergencyNumber.EMERGENCY_NUMBER_SOURCE_SIM)) { 537 return num.getEmergencyServiceCategoryBitmask(); 538 } 539 } 540 } 541 return EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_UNSPECIFIED; 542 } 543 544 /** 545 * Get the emergency call routing for the corresponding emergency number. The only trusted 546 * source for the routing is {@link EmergencyNumber#EMERGENCY_NUMBER_SOURCE_DATABASE}. 547 * 548 * @param emergencyNumber - the supplied emergency number. 549 * @return the emergency call routing for the corresponding emergency number. 550 */ getEmergencyCallRouting(String emergencyNumber)551 public @EmergencyCallRouting int getEmergencyCallRouting(String emergencyNumber) { 552 emergencyNumber = PhoneNumberUtils.stripSeparators(emergencyNumber); 553 for (EmergencyNumber num : getEmergencyNumberList()) { 554 if (num.getNumber().equals(emergencyNumber)) { 555 if (num.isFromSources(EmergencyNumber.EMERGENCY_NUMBER_SOURCE_DATABASE)) { 556 return num.getEmergencyCallRouting(); 557 } 558 } 559 } 560 return EmergencyNumber.EMERGENCY_CALL_ROUTING_UNKNOWN; 561 } 562 563 /** 564 * Get Emergency number list based on EccList. This util is used for solving backward 565 * compatibility if device does not support the 1.4 IRadioIndication HAL that reports 566 * emergency number list. 567 */ getEmergencyNumberListFromEccList()568 private List<EmergencyNumber> getEmergencyNumberListFromEccList() { 569 List<EmergencyNumber> emergencyNumberList = new ArrayList<>(); 570 int slotId = SubscriptionController.getInstance().getSlotIndex(mPhone.getSubId()); 571 572 String ecclist = (slotId <= 0) ? "ril.ecclist" : ("ril.ecclist" + slotId); 573 String emergencyNumbers = SystemProperties.get(ecclist, ""); 574 if (TextUtils.isEmpty(emergencyNumbers)) { 575 // then read-only ecclist property since old RIL only uses this 576 emergencyNumbers = SystemProperties.get("ro.ril.ecclist"); 577 } 578 if (!TextUtils.isEmpty(emergencyNumbers)) { 579 // searches through the comma-separated list for a match, 580 // return true if one is found. 581 for (String emergencyNum : emergencyNumbers.split(",")) { 582 emergencyNumberList.add(getLabeledEmergencyNumberForEcclist(emergencyNum)); 583 } 584 } 585 emergencyNumbers = ((slotId < 0) ? "112,911,000,08,110,118,119,999" : "112,911"); 586 for (String emergencyNum : emergencyNumbers.split(",")) { 587 emergencyNumberList.add(getLabeledEmergencyNumberForEcclist(emergencyNum)); 588 } 589 if (mEmergencyNumberPrefix.length != 0) { 590 emergencyNumberList.addAll(getEmergencyNumberListWithPrefix(emergencyNumberList)); 591 } 592 EmergencyNumber.mergeSameNumbersInEmergencyNumberList(emergencyNumberList); 593 return emergencyNumberList; 594 } 595 getEmergencyNumberListWithPrefix( List<EmergencyNumber> emergencyNumberList)596 private List<EmergencyNumber> getEmergencyNumberListWithPrefix( 597 List<EmergencyNumber> emergencyNumberList) { 598 List<EmergencyNumber> emergencyNumberListWithPrefix = new ArrayList<>(); 599 for (EmergencyNumber num : emergencyNumberList) { 600 for (String prefix : mEmergencyNumberPrefix) { 601 // If an emergency number has started with the prefix, no need to apply the prefix. 602 if (!num.getNumber().startsWith(prefix)) { 603 emergencyNumberListWithPrefix.add(new EmergencyNumber( 604 prefix + num.getNumber(), num.getCountryIso(), 605 num.getMnc(), num.getEmergencyServiceCategoryBitmask(), 606 num.getEmergencyUrns(), num.getEmergencyNumberSourceBitmask(), 607 num.getEmergencyCallRouting())); 608 } 609 } 610 } 611 return emergencyNumberListWithPrefix; 612 } 613 isEmergencyNumberForTest(String number)614 private boolean isEmergencyNumberForTest(String number) { 615 number = PhoneNumberUtils.stripSeparators(number); 616 for (EmergencyNumber num : mEmergencyNumberListFromTestMode) { 617 if (num.getNumber().equals(number)) { 618 return true; 619 } 620 } 621 return false; 622 } 623 getLabeledEmergencyNumberForEcclist(String number)624 private EmergencyNumber getLabeledEmergencyNumberForEcclist(String number) { 625 number = PhoneNumberUtils.stripSeparators(number); 626 for (EmergencyNumber num : mEmergencyNumberListFromDatabase) { 627 if (num.getNumber().equals(number)) { 628 return new EmergencyNumber(number, mCountryIso.toLowerCase(), "", 629 num.getEmergencyServiceCategoryBitmask(), 630 new ArrayList<String>(), EmergencyNumber.EMERGENCY_NUMBER_SOURCE_DATABASE, 631 EmergencyNumber.EMERGENCY_CALL_ROUTING_UNKNOWN); 632 } 633 } 634 return new EmergencyNumber(number, "", "", 635 EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_UNSPECIFIED, 636 new ArrayList<String>(), 0, 637 EmergencyNumber.EMERGENCY_CALL_ROUTING_UNKNOWN); 638 } 639 640 /** 641 * Back-up old logics for {@link PhoneNumberUtils#isEmergencyNumberInternal} for legacy 642 * and deprecate purpose. 643 */ isEmergencyNumberFromEccList(String number, boolean useExactMatch)644 private boolean isEmergencyNumberFromEccList(String number, boolean useExactMatch) { 645 // If the number passed in is null, just return false: 646 if (number == null) return false; 647 648 // If the number passed in is a SIP address, return false, since the 649 // concept of "emergency numbers" is only meaningful for calls placed 650 // over the cell network. 651 // (Be sure to do this check *before* calling extractNetworkPortionAlt(), 652 // since the whole point of extractNetworkPortionAlt() is to filter out 653 // any non-dialable characters (which would turn 'abc911def@example.com' 654 // into '911', for example.)) 655 if (PhoneNumberUtils.isUriNumber(number)) { 656 return false; 657 } 658 659 // Strip the separators from the number before comparing it 660 // to the list. 661 number = PhoneNumberUtils.extractNetworkPortionAlt(number); 662 663 String emergencyNumbers = ""; 664 int slotId = SubscriptionController.getInstance().getSlotIndex(mPhone.getSubId()); 665 666 // retrieve the list of emergency numbers 667 // check read-write ecclist property first 668 String ecclist = (slotId <= 0) ? "ril.ecclist" : ("ril.ecclist" + slotId); 669 670 emergencyNumbers = SystemProperties.get(ecclist, ""); 671 672 logd("slotId:" + slotId + " country:" + mCountryIso + " emergencyNumbers: " 673 + emergencyNumbers); 674 675 if (TextUtils.isEmpty(emergencyNumbers)) { 676 // then read-only ecclist property since old RIL only uses this 677 emergencyNumbers = SystemProperties.get("ro.ril.ecclist"); 678 } 679 680 if (!TextUtils.isEmpty(emergencyNumbers)) { 681 // searches through the comma-separated list for a match, 682 // return true if one is found. 683 for (String emergencyNum : emergencyNumbers.split(",")) { 684 // According to com.android.i18n.phonenumbers.ShortNumberInfo, in 685 // these countries, if extra digits are added to an emergency number, 686 // it no longer connects to the emergency service. 687 if (useExactMatch || mCountryIso.equals("br") || mCountryIso.equals("cl") 688 || mCountryIso.equals("ni")) { 689 if (number.equals(emergencyNum)) { 690 return true; 691 } else { 692 for (String prefix : mEmergencyNumberPrefix) { 693 if (number.equals(prefix + emergencyNum)) { 694 return true; 695 } 696 } 697 } 698 } else { 699 if (number.startsWith(emergencyNum)) { 700 return true; 701 } else { 702 for (String prefix : mEmergencyNumberPrefix) { 703 if (number.startsWith(prefix + emergencyNum)) { 704 return true; 705 } 706 } 707 } 708 } 709 } 710 // no matches found against the list! 711 return false; 712 } 713 714 logd("System property doesn't provide any emergency numbers." 715 + " Use embedded logic for determining ones."); 716 717 // If slot id is invalid, means that there is no sim card. 718 // According spec 3GPP TS22.101, the following numbers should be 719 // ECC numbers when SIM/USIM is not present. 720 emergencyNumbers = ((slotId < 0) ? "112,911,000,08,110,118,119,999" : "112,911"); 721 722 for (String emergencyNum : emergencyNumbers.split(",")) { 723 if (useExactMatch) { 724 if (number.equals(emergencyNum)) { 725 return true; 726 } else { 727 for (String prefix : mEmergencyNumberPrefix) { 728 if (number.equals(prefix + emergencyNum)) { 729 return true; 730 } 731 } 732 } 733 } else { 734 if (number.startsWith(emergencyNum)) { 735 return true; 736 } else { 737 for (String prefix : mEmergencyNumberPrefix) { 738 if (number.equals(prefix + emergencyNum)) { 739 return true; 740 } 741 } 742 } 743 } 744 } 745 746 // No ecclist system property, so use our own list. 747 if (mCountryIso != null) { 748 ShortNumberInfo info = ShortNumberInfo.getInstance(); 749 if (useExactMatch) { 750 if (info.isEmergencyNumber(number, mCountryIso.toUpperCase())) { 751 return true; 752 } else { 753 for (String prefix : mEmergencyNumberPrefix) { 754 if (info.isEmergencyNumber(prefix + number, mCountryIso.toUpperCase())) { 755 return true; 756 } 757 } 758 } 759 return false; 760 } else { 761 if (info.connectsToEmergencyNumber(number, mCountryIso.toUpperCase())) { 762 return true; 763 } else { 764 for (String prefix : mEmergencyNumberPrefix) { 765 if (info.connectsToEmergencyNumber(prefix + number, 766 mCountryIso.toUpperCase())) { 767 return true; 768 } 769 } 770 } 771 return false; 772 } 773 } 774 775 return false; 776 } 777 778 /** 779 * Execute command for updating emergency number for test mode. 780 */ executeEmergencyNumberTestModeCommand(int action, EmergencyNumber num)781 public void executeEmergencyNumberTestModeCommand(int action, EmergencyNumber num) { 782 this.obtainMessage(EVENT_UPDATE_EMERGENCY_NUMBER_TEST_MODE, action, 0, num).sendToTarget(); 783 } 784 785 /** 786 * Update emergency number list for test mode. 787 */ updateEmergencyNumberListTestModeAndNotify(int action, EmergencyNumber num)788 private void updateEmergencyNumberListTestModeAndNotify(int action, EmergencyNumber num) { 789 if (action == ADD_EMERGENCY_NUMBER_TEST_MODE) { 790 if (!isEmergencyNumber(num.getNumber(), true)) { 791 mEmergencyNumberListFromTestMode.add(num); 792 } 793 } else if (action == RESET_EMERGENCY_NUMBER_TEST_MODE) { 794 mEmergencyNumberListFromTestMode.clear(); 795 } else if (action == REMOVE_EMERGENCY_NUMBER_TEST_MODE) { 796 mEmergencyNumberListFromTestMode.remove(num); 797 } else { 798 loge("updateEmergencyNumberListTestModeAndNotify: Unexpected action in test mode."); 799 return; 800 } 801 if (!DBG) { 802 mEmergencyNumberListTestModeLocalLog.log( 803 "updateEmergencyNumberListTestModeAndNotify:" 804 + mEmergencyNumberListFromTestMode); 805 } 806 updateEmergencyNumberList(); 807 if (!DBG) { 808 mEmergencyNumberListLocalLog.log( 809 "updateEmergencyNumberListTestModeAndNotify:" 810 + mEmergencyNumberList); 811 } 812 notifyEmergencyNumberList(); 813 } 814 getEmergencyNumberListFromEccListAndTest()815 private List<EmergencyNumber> getEmergencyNumberListFromEccListAndTest() { 816 List<EmergencyNumber> mergedEmergencyNumberList = getEmergencyNumberListFromEccList(); 817 mergedEmergencyNumberList.addAll(getEmergencyNumberListTestMode()); 818 return mergedEmergencyNumberList; 819 } 820 821 /** 822 * Get emergency number list for test. 823 */ getEmergencyNumberListTestMode()824 public List<EmergencyNumber> getEmergencyNumberListTestMode() { 825 return Collections.unmodifiableList(mEmergencyNumberListFromTestMode); 826 } 827 828 @VisibleForTesting getRadioEmergencyNumberList()829 public List<EmergencyNumber> getRadioEmergencyNumberList() { 830 return new ArrayList<>(mEmergencyNumberListFromRadio); 831 } 832 logd(String str)833 private static void logd(String str) { 834 Rlog.d(TAG, str); 835 } 836 loge(String str)837 private static void loge(String str) { 838 Rlog.e(TAG, str); 839 } 840 writeUpdatedEmergencyNumberListMetrics( List<EmergencyNumber> updatedEmergencyNumberList)841 private void writeUpdatedEmergencyNumberListMetrics( 842 List<EmergencyNumber> updatedEmergencyNumberList) { 843 if (updatedEmergencyNumberList == null) { 844 return; 845 } 846 for (EmergencyNumber num : updatedEmergencyNumberList) { 847 TelephonyMetrics.getInstance().writeEmergencyNumberUpdateEvent( 848 mPhone.getPhoneId(), num); 849 } 850 } 851 852 /** 853 * Dump Emergency Number List info in the tracking 854 * 855 * @param fd FileDescriptor 856 * @param pw PrintWriter 857 * @param args args 858 */ dump(FileDescriptor fd, PrintWriter pw, String[] args)859 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 860 final IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " "); 861 ipw.println(" Hal Version:" + mPhone.getHalVersion()); 862 ipw.println(" ========================================= "); 863 864 ipw.println("mEmergencyNumberListDatabaseLocalLog:"); 865 ipw.increaseIndent(); 866 mEmergencyNumberListDatabaseLocalLog.dump(fd, pw, args); 867 ipw.decreaseIndent(); 868 ipw.println(" ========================================= "); 869 870 ipw.println("mEmergencyNumberListRadioLocalLog:"); 871 ipw.increaseIndent(); 872 mEmergencyNumberListRadioLocalLog.dump(fd, pw, args); 873 ipw.decreaseIndent(); 874 ipw.println(" ========================================= "); 875 876 ipw.println("mEmergencyNumberListPrefixLocalLog:"); 877 ipw.increaseIndent(); 878 mEmergencyNumberListPrefixLocalLog.dump(fd, pw, args); 879 ipw.decreaseIndent(); 880 ipw.println(" ========================================= "); 881 882 ipw.println("mEmergencyNumberListTestModeLocalLog:"); 883 ipw.increaseIndent(); 884 mEmergencyNumberListTestModeLocalLog.dump(fd, pw, args); 885 ipw.decreaseIndent(); 886 ipw.println(" ========================================= "); 887 888 ipw.println("mEmergencyNumberListLocalLog (valid >= 1.4 HAL):"); 889 ipw.increaseIndent(); 890 mEmergencyNumberListLocalLog.dump(fd, pw, args); 891 ipw.decreaseIndent(); 892 ipw.println(" ========================================= "); 893 894 int slotId = SubscriptionController.getInstance().getSlotIndex(mPhone.getSubId()); 895 String ecclist = (slotId <= 0) ? "ril.ecclist" : ("ril.ecclist" + slotId); 896 ipw.println(" ril.ecclist: " + SystemProperties.get(ecclist, "")); 897 ipw.println(" ========================================= "); 898 899 ipw.println("Emergency Number List for Phone" + "(" + mPhone.getPhoneId() + ")"); 900 ipw.increaseIndent(); 901 ipw.println(getEmergencyNumberList()); 902 ipw.decreaseIndent(); 903 ipw.println(" ========================================= "); 904 905 ipw.flush(); 906 } 907 } 908