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