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