1 /*
2  * Copyright (C) 2020 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.settingslib.emergencynumber;
18 
19 import static android.telephony.emergency.EmergencyNumber.EMERGENCY_NUMBER_SOURCE_DATABASE;
20 import static android.telephony.emergency.EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_POLICE;
21 
22 import android.content.ContentResolver;
23 import android.content.Context;
24 import android.content.pm.PackageManager;
25 import android.net.Uri;
26 import android.os.Bundle;
27 import android.os.PersistableBundle;
28 import android.telephony.CarrierConfigManager;
29 import android.telephony.SubscriptionManager;
30 import android.telephony.TelephonyManager;
31 import android.telephony.emergency.EmergencyNumber;
32 import android.text.TextUtils;
33 import android.util.ArrayMap;
34 import android.util.Log;
35 
36 import androidx.annotation.VisibleForTesting;
37 
38 import java.util.ArrayList;
39 import java.util.List;
40 import java.util.Map;
41 import java.util.stream.Collectors;
42 
43 /**
44  * Util class to help manage emergency numbers
45  */
46 public class EmergencyNumberUtils {
47     private static final String TAG = "EmergencyNumberUtils";
48 
49     public static final Uri EMERGENCY_NUMBER_OVERRIDE_AUTHORITY = new Uri.Builder().scheme(
50             ContentResolver.SCHEME_CONTENT)
51             .authority("com.android.emergency.gesture")
52             .build();
53     public static final String METHOD_NAME_GET_EMERGENCY_NUMBER_OVERRIDE =
54             "GET_EMERGENCY_NUMBER_OVERRIDE";
55     public static final String METHOD_NAME_SET_EMERGENCY_NUMBER_OVERRIDE =
56             "SET_EMERGENCY_NUMBER_OVERRIDE";
57     public static final String METHOD_NAME_SET_EMERGENCY_GESTURE = "SET_EMERGENCY_GESTURE";
58     public static final String METHOD_NAME_SET_EMERGENCY_GESTURE_UI_SHOWING =
59             "SET_EMERGENCY_GESTURE_UI_SHOWING";
60     public static final String METHOD_NAME_SET_EMERGENCY_SOUND = "SET_EMERGENCY_SOUND";
61     public static final String METHOD_NAME_GET_EMERGENCY_GESTURE_ENABLED = "GET_EMERGENCY_GESTURE";
62     public static final String METHOD_NAME_GET_EMERGENCY_GESTURE_SOUND_ENABLED =
63             "GET_EMERGENCY_SOUND";
64     public static final String EMERGENCY_GESTURE_CALL_NUMBER = "emergency_gesture_call_number";
65     public static final String EMERGENCY_GESTURE_UI_SHOWING_VALUE =
66             "emergency_gesture_ui_showing_value";
67     public static final String EMERGENCY_SETTING_VALUE = "emergency_setting_value";
68     public static final int EMERGENCY_SETTING_ON = 1;
69     public static final int EMERGENCY_SETTING_OFF = 0;
70 
71     @VisibleForTesting
72     static final String FALL_BACK_NUMBER = "112";
73 
74     private final Context mContext;
75     private final TelephonyManager mTelephonyManager;
76     private final CarrierConfigManager mCarrierConfigManager;
77 
EmergencyNumberUtils(Context context)78     public EmergencyNumberUtils(Context context) {
79         mContext = context;
80         if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
81             mTelephonyManager = context.getSystemService(TelephonyManager.class);
82             mCarrierConfigManager = context.getSystemService(CarrierConfigManager.class);
83         } else {
84             mTelephonyManager = null;
85             mCarrierConfigManager = null;
86         }
87     }
88 
89     /**
90      * Returns the most appropriate number for police.
91      */
getDefaultPoliceNumber()92     public String getDefaultPoliceNumber() {
93         if (mTelephonyManager == null) {
94             return FALL_BACK_NUMBER;
95         }
96         final List<String> promotedPoliceNumber = getPromotedEmergencyNumbers(
97                 EMERGENCY_SERVICE_CATEGORY_POLICE);
98         if (promotedPoliceNumber == null || promotedPoliceNumber.isEmpty()) {
99             return FALL_BACK_NUMBER;
100         }
101         return promotedPoliceNumber.get(0);
102     }
103 
104     /**
105      * Returns the number chosen by user. If user has not provided any number, use default ({@link
106      * #getDefaultPoliceNumber()}).
107      */
getPoliceNumber()108     public String getPoliceNumber() {
109         final String userProvidedNumber = getEmergencyNumberOverride();
110         return TextUtils.isEmpty(userProvidedNumber)
111                 ? getDefaultPoliceNumber() : userProvidedNumber;
112     }
113 
114     /**
115      * Sets device-local emergency number override
116      */
setEmergencyNumberOverride(String number)117     public void setEmergencyNumberOverride(String number) {
118         final Bundle bundle = new Bundle();
119         bundle.putString(EMERGENCY_GESTURE_CALL_NUMBER, number);
120         mContext.getContentResolver().call(EMERGENCY_NUMBER_OVERRIDE_AUTHORITY,
121                 METHOD_NAME_SET_EMERGENCY_NUMBER_OVERRIDE, null /* args */, bundle);
122     }
123 
124     /**
125      * Enable/disable the emergency gesture setting
126      */
setEmergencyGestureEnabled(boolean enabled)127     public void setEmergencyGestureEnabled(boolean enabled) {
128         final Bundle bundle = new Bundle();
129         bundle.putInt(EMERGENCY_SETTING_VALUE,
130                 enabled ? EMERGENCY_SETTING_ON : EMERGENCY_SETTING_OFF);
131         mContext.getContentResolver().call(EMERGENCY_NUMBER_OVERRIDE_AUTHORITY,
132                 METHOD_NAME_SET_EMERGENCY_GESTURE, null /* args */, bundle);
133     }
134 
135     /**
136      * Enable/disable the emergency gesture sound setting
137      */
setEmergencySoundEnabled(boolean enabled)138     public void setEmergencySoundEnabled(boolean enabled) {
139         final Bundle bundle = new Bundle();
140         bundle.putInt(EMERGENCY_SETTING_VALUE,
141                 enabled ? EMERGENCY_SETTING_ON : EMERGENCY_SETTING_OFF);
142         mContext.getContentResolver().call(EMERGENCY_NUMBER_OVERRIDE_AUTHORITY,
143                 METHOD_NAME_SET_EMERGENCY_SOUND, null /* args */, bundle);
144     }
145 
146     /**
147      * Whether or not emergency gesture is enabled.
148      */
getEmergencyGestureEnabled()149     public boolean getEmergencyGestureEnabled() {
150         final Bundle bundle = mContext.getContentResolver().call(
151                 EMERGENCY_NUMBER_OVERRIDE_AUTHORITY,
152                 METHOD_NAME_GET_EMERGENCY_GESTURE_ENABLED, null /* args */, null /* bundle */);
153         return bundle == null ? true : bundle.getInt(EMERGENCY_SETTING_VALUE, EMERGENCY_SETTING_ON)
154                 == EMERGENCY_SETTING_ON;
155     }
156 
157     /**
158      * Whether or not emergency gesture sound is enabled.
159      */
getEmergencyGestureSoundEnabled()160     public boolean getEmergencyGestureSoundEnabled() {
161         final Bundle bundle = mContext.getContentResolver().call(
162                 EMERGENCY_NUMBER_OVERRIDE_AUTHORITY,
163                 METHOD_NAME_GET_EMERGENCY_GESTURE_SOUND_ENABLED, null /* args */,
164                 null /* bundle */);
165         return bundle == null ? true : bundle.getInt(EMERGENCY_SETTING_VALUE, EMERGENCY_SETTING_OFF)
166                 == EMERGENCY_SETTING_ON;
167     }
168 
getEmergencyNumberOverride()169     private String getEmergencyNumberOverride() {
170         final Bundle bundle = mContext.getContentResolver().call(
171                 EMERGENCY_NUMBER_OVERRIDE_AUTHORITY,
172                 METHOD_NAME_GET_EMERGENCY_NUMBER_OVERRIDE, null /* args */, null /* bundle */);
173         return bundle == null ? null : bundle.getString(EMERGENCY_GESTURE_CALL_NUMBER);
174     }
175 
getPromotedEmergencyNumbers(int categories)176     private List<String> getPromotedEmergencyNumbers(int categories) {
177         Map<Integer, List<EmergencyNumber>> allLists = mTelephonyManager.getEmergencyNumberList(
178                 categories);
179         if (allLists == null || allLists.isEmpty()) {
180             Log.w(TAG, "Unable to retrieve emergency number lists!");
181             return new ArrayList<>();
182         }
183         Map<Integer, List<String>> promotedEmergencyNumberLists = new ArrayMap<>();
184         for (Map.Entry<Integer, List<EmergencyNumber>> entry : allLists.entrySet()) {
185             if (entry.getKey() == null || entry.getValue() == null) {
186                 continue;
187             }
188             int subId = entry.getKey();
189             List<EmergencyNumber> emergencyNumberList = entry.getValue();
190             Log.d(TAG, "Emergency numbers for subscription id " + entry.getKey());
191 
192             // The list of promoted emergency numbers which will be visible on shortcut view.
193             List<EmergencyNumber> promotedList = new ArrayList<>();
194             // A temporary list for non-prioritized emergency numbers.
195             List<EmergencyNumber> tempList = new ArrayList<>();
196 
197             for (EmergencyNumber emergencyNumber : emergencyNumberList) {
198                 // Emergency numbers in DATABASE are prioritized since they were well-categorized.
199                 boolean isFromPrioritizedSource =
200                         emergencyNumber.getEmergencyNumberSources().contains(
201                                 EMERGENCY_NUMBER_SOURCE_DATABASE);
202 
203                 Log.d(TAG, String.format("Number %s, isFromPrioritizedSource %b",
204                         emergencyNumber, isFromPrioritizedSource));
205                 if (isFromPrioritizedSource) {
206                     promotedList.add(emergencyNumber);
207                 } else {
208                     tempList.add(emergencyNumber);
209                 }
210             }
211             // Puts numbers in temp list after prioritized numbers.
212             promotedList.addAll(tempList);
213 
214             if (!promotedList.isEmpty()) {
215                 List<String> sanitizedNumbers = sanitizeEmergencyNumbers(promotedList, subId);
216                 promotedEmergencyNumberLists.put(subId, sanitizedNumbers);
217             }
218         }
219 
220         if (promotedEmergencyNumberLists.isEmpty()) {
221             Log.w(TAG, "No promoted emergency number found!");
222         }
223         return promotedEmergencyNumberLists.get(SubscriptionManager.getDefaultSubscriptionId());
224     }
225 
sanitizeEmergencyNumbers( List<EmergencyNumber> input, int subscriptionId)226     private List<String> sanitizeEmergencyNumbers(
227             List<EmergencyNumber> input, int subscriptionId) {
228         // Make a copy of data so we can mutate.
229         List<EmergencyNumber> data = new ArrayList<>(input);
230         String[] carrierPrefixes =
231                 getCarrierEmergencyNumberPrefixes(mCarrierConfigManager, subscriptionId);
232         return data.stream()
233                 .map(d -> removePrefix(d, carrierPrefixes))
234                 .collect(Collectors.toCollection(ArrayList::new));
235     }
236 
removePrefix(EmergencyNumber emergencyNumber, String[] prefixes)237     private String removePrefix(EmergencyNumber emergencyNumber, String[] prefixes) {
238         String number = emergencyNumber.getNumber();
239         if (prefixes == null || prefixes.length == 0) {
240             return number;
241         }
242         for (String prefix : prefixes) {
243             int prefixStartIndex = number.indexOf(prefix);
244             if (prefixStartIndex != 0) {
245                 continue;
246             }
247             Log.d(TAG, "Removing prefix " + prefix + " from " + number);
248             return number.substring(prefix.length());
249         }
250         return number;
251     }
252 
getCarrierEmergencyNumberPrefixes( CarrierConfigManager carrierConfigManager, int subId)253     private static String[] getCarrierEmergencyNumberPrefixes(
254             CarrierConfigManager carrierConfigManager, int subId) {
255         PersistableBundle b = carrierConfigManager.getConfigForSubId(subId);
256         return b == null
257                 ? null
258                 : b.getStringArray(CarrierConfigManager.KEY_EMERGENCY_NUMBER_PREFIX_STRING_ARRAY);
259     }
260 }
261