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