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