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.server.location.gnss;
18 
19 import android.content.Context;
20 import android.location.flags.Flags;
21 import android.os.PersistableBundle;
22 import android.os.SystemProperties;
23 import android.telephony.CarrierConfigManager;
24 import android.telephony.SubscriptionManager;
25 import android.telephony.TelephonyManager;
26 import android.text.TextUtils;
27 import android.util.Log;
28 
29 import com.android.internal.util.FrameworkStatsLog;
30 
31 import libcore.io.IoUtils;
32 
33 import java.io.File;
34 import java.io.FileInputStream;
35 import java.io.IOException;
36 import java.util.Arrays;
37 import java.util.Collections;
38 import java.util.HashMap;
39 import java.util.List;
40 import java.util.Locale;
41 import java.util.Map;
42 import java.util.Map.Entry;
43 import java.util.Properties;
44 
45 /**
46  * A utility class to hold GNSS configuration properties.
47  *
48  * The trigger to load/reload the configuration parameters should be managed by the class
49  * that owns an instance of this class.
50  *
51  * Instances of this class are not thread-safe and should either be used from a single thread
52  * or with external synchronization when used by multiple threads.
53  */
54 public class GnssConfiguration {
55     private static final String TAG = "GnssConfiguration";
56 
57     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
58 
59     private static final String DEBUG_PROPERTIES_SYSTEM_FILE = "/etc/gps_debug.conf";
60 
61     private static final String DEBUG_PROPERTIES_VENDOR_FILE = "/vendor/etc/gps_debug.conf";
62 
63     // config.xml properties
64     private static final String CONFIG_SUPL_HOST = "SUPL_HOST";
65     private static final String CONFIG_SUPL_PORT = "SUPL_PORT";
66     private static final String CONFIG_C2K_HOST = "C2K_HOST";
67     private static final String CONFIG_C2K_PORT = "C2K_PORT";
68     private static final String CONFIG_SUPL_VER = "SUPL_VER";
69     private static final String CONFIG_SUPL_MODE = "SUPL_MODE";
70     private static final String CONFIG_SUPL_ES = "SUPL_ES";
71     private static final String CONFIG_LPP_PROFILE = "LPP_PROFILE";
72     private static final String CONFIG_A_GLONASS_POS_PROTOCOL_SELECT =
73             "A_GLONASS_POS_PROTOCOL_SELECT";
74     private static final String CONFIG_USE_EMERGENCY_PDN_FOR_EMERGENCY_SUPL =
75             "USE_EMERGENCY_PDN_FOR_EMERGENCY_SUPL";
76     private static final String CONFIG_GPS_LOCK = "GPS_LOCK";
77     private static final String CONFIG_ES_EXTENSION_SEC = "ES_EXTENSION_SEC";
78     static final String CONFIG_NFW_PROXY_APPS = "NFW_PROXY_APPS";
79     private static final String CONFIG_ENABLE_PSDS_PERIODIC_DOWNLOAD =
80             "ENABLE_PSDS_PERIODIC_DOWNLOAD";
81     private static final String CONFIG_ENABLE_ACTIVE_SIM_EMERGENCY_SUPL =
82             "ENABLE_ACTIVE_SIM_EMERGENCY_SUPL";
83     private static final String CONFIG_ENABLE_NI_SUPL_MESSAGE_INJECTION =
84             "ENABLE_NI_SUPL_MESSAGE_INJECTION";
85     static final String CONFIG_LONGTERM_PSDS_SERVER_1 = "LONGTERM_PSDS_SERVER_1";
86     static final String CONFIG_LONGTERM_PSDS_SERVER_2 = "LONGTERM_PSDS_SERVER_2";
87     static final String CONFIG_LONGTERM_PSDS_SERVER_3 = "LONGTERM_PSDS_SERVER_3";
88     static final String CONFIG_NORMAL_PSDS_SERVER = "NORMAL_PSDS_SERVER";
89     static final String CONFIG_REALTIME_PSDS_SERVER = "REALTIME_PSDS_SERVER";
90     // Limit on NI emergency mode time extension after emergency sessions ends
91     private static final int MAX_EMERGENCY_MODE_EXTENSION_SECONDS = 300;  // 5 minute maximum
92 
93     // Persist property for LPP_PROFILE
94     static final String LPP_PROFILE = "persist.sys.gps.lpp";
95 
96     // Represents an HAL interface version. Instances of this class are created in the JNI layer
97     // and returned through native methods.
98     static class HalInterfaceVersion {
99         // mMajor being this value denotes AIDL HAL. In this case, mMinor denotes the AIDL version.
100         static final int AIDL_INTERFACE = 3;
101         final int mMajor;
102         final int mMinor;
103 
HalInterfaceVersion(int major, int minor)104         HalInterfaceVersion(int major, int minor) {
105             mMajor = major;
106             mMinor = minor;
107         }
108     }
109 
110     /**
111      * Properties loaded from PROPERTIES_FILE.
112      */
113     private final Properties mProperties;
114 
115     private int mEsExtensionSec = 0;
116 
117     private final Context mContext;
118 
GnssConfiguration(Context context)119     public GnssConfiguration(Context context) {
120         mContext = context;
121         mProperties = new Properties();
122     }
123 
124     /**
125      * Returns the full set of properties loaded.
126      */
getProperties()127     Properties getProperties() {
128         return mProperties;
129     }
130 
131     /**
132      * Returns the value of config parameter ES_EXTENSION_SEC. The value is range checked
133      * and constrained to min/max limits.
134      */
getEsExtensionSec()135     public int getEsExtensionSec() {
136         return mEsExtensionSec;
137     }
138 
139     /**
140      * Returns the value of config parameter SUPL_HOST or {@code null} if no value is
141      * provided.
142      */
getSuplHost()143     String getSuplHost() {
144         return mProperties.getProperty(CONFIG_SUPL_HOST);
145     }
146 
147     /**
148      * Returns the value of config parameter SUPL_PORT or {@code defaultPort} if no value is
149      * provided or if there is an error parsing the configured value.
150      */
getSuplPort(int defaultPort)151     int getSuplPort(int defaultPort) {
152         return getIntConfig(CONFIG_SUPL_PORT, defaultPort);
153     }
154 
155     /**
156      * Returns the value of config parameter C2K_HOST or {@code null} if no value is
157      * provided.
158      */
getC2KHost()159     String getC2KHost() {
160         return mProperties.getProperty(CONFIG_C2K_HOST);
161     }
162 
163     /**
164      * Returns the value of config parameter C2K_PORT or {@code defaultPort} if no value is
165      * provided or if there is an error parsing the configured value.
166      */
getC2KPort(int defaultPort)167     int getC2KPort(int defaultPort) {
168         return getIntConfig(CONFIG_C2K_PORT, defaultPort);
169     }
170 
171     /**
172      * Returns the value of config parameter SUPL_MODE or {@code defaultMode} if no value is
173      * provided or if there is an error parsing the configured value.
174      */
getSuplMode(int defaultMode)175     int getSuplMode(int defaultMode) {
176         return getIntConfig(CONFIG_SUPL_MODE, defaultMode);
177     }
178 
179     /**
180      * Returns the value of config parameter SUPL_ES or {@code defaultSuplEs} if no value is
181      * provided or if there is an error parsing the configured value.
182      */
getSuplEs(int defaultSuplEs)183     public int getSuplEs(int defaultSuplEs) {
184         return getIntConfig(CONFIG_SUPL_ES, defaultSuplEs);
185     }
186 
187     /**
188      * Returns the value of config parameter LPP_PROFILE or {@code null} if no value is
189      * provided.
190      */
getLppProfile()191     String getLppProfile() {
192         return mProperties.getProperty(CONFIG_LPP_PROFILE);
193     }
194 
195     /**
196      * Returns the list of proxy apps from the value of config parameter NFW_PROXY_APPS.
197      */
getProxyApps()198     List<String> getProxyApps() {
199         // Space separated list of Android proxy app package names.
200         String proxyAppsStr = mProperties.getProperty(CONFIG_NFW_PROXY_APPS);
201         if (TextUtils.isEmpty(proxyAppsStr)) {
202             return Collections.emptyList();
203         }
204 
205         String[] proxyAppsArray = proxyAppsStr.trim().split("\\s+");
206         if (proxyAppsArray.length == 0) {
207             return Collections.emptyList();
208         }
209 
210         return Arrays.asList(proxyAppsArray);
211     }
212 
213     /**
214      * Returns true if PSDS periodic download is enabled, false otherwise.
215      */
isPsdsPeriodicDownloadEnabled()216     boolean isPsdsPeriodicDownloadEnabled() {
217         return getBooleanConfig(CONFIG_ENABLE_PSDS_PERIODIC_DOWNLOAD, false);
218     }
219 
220     /**
221      * Returns true if during an emergency call, the GnssConfiguration of the activeSubId will be
222      * injected for the emergency SUPL flow; Returns false otherwise. Default false if not set.
223      */
isActiveSimEmergencySuplEnabled()224     boolean isActiveSimEmergencySuplEnabled() {
225         return getBooleanConfig(CONFIG_ENABLE_ACTIVE_SIM_EMERGENCY_SUPL, false);
226     }
227 
228     /**
229      * Returns true if NI SUPL message injection is enabled; Returns false otherwise.
230      * Default false if not set.
231      */
isNiSuplMessageInjectionEnabled()232     boolean isNiSuplMessageInjectionEnabled() {
233         return getBooleanConfig(CONFIG_ENABLE_NI_SUPL_MESSAGE_INJECTION, false);
234     }
235 
236     /**
237      * Returns true if a long-term PSDS server is configured.
238      */
isLongTermPsdsServerConfigured()239     boolean isLongTermPsdsServerConfigured() {
240         return (mProperties.getProperty(CONFIG_LONGTERM_PSDS_SERVER_1) != null
241                 || mProperties.getProperty(CONFIG_LONGTERM_PSDS_SERVER_2) != null
242                 || mProperties.getProperty(CONFIG_LONGTERM_PSDS_SERVER_3) != null);
243     }
244 
245     /**
246      * Updates the GNSS HAL satellite denylist.
247      */
setSatelliteBlocklist(int[] constellations, int[] svids)248     void setSatelliteBlocklist(int[] constellations, int[] svids) {
249         native_set_satellite_blocklist(constellations, svids);
250     }
251 
getHalInterfaceVersion()252     HalInterfaceVersion getHalInterfaceVersion() {
253         return native_get_gnss_configuration_version();
254     }
255 
256     interface SetCarrierProperty {
set(int value)257         boolean set(int value);
258     }
259 
260     /**
261      * Loads the GNSS properties from carrier config file followed by the properties from
262      * gps debug config file, and injects the GNSS properties into the HAL.
263      */
reloadGpsProperties()264     void reloadGpsProperties() {
265         reloadGpsProperties(/* inEmergency= */ false, /* activeSubId= */ -1);
266     }
267 
268     /**
269      * Loads the GNSS properties from carrier config file followed by the properties from
270      * gps debug config file, and injects the GNSS properties into the HAL.
271      */
reloadGpsProperties(boolean inEmergency, int activeSubId)272     void reloadGpsProperties(boolean inEmergency, int activeSubId) {
273         if (DEBUG) {
274             Log.d(TAG,
275                     "Reset GPS properties, previous size = " + mProperties.size() + ", inEmergency:"
276                             + inEmergency + ", activeSubId=" + activeSubId);
277         }
278         loadPropertiesFromCarrierConfig(inEmergency, activeSubId);
279 
280         if (Flags.gnssConfigurationFromResource()) {
281             // Overlay carrier properties from resources.
282             loadPropertiesFromResource(mContext, mProperties);
283         }
284 
285         if (isSimAbsent(mContext)) {
286             // Use the default SIM's LPP profile when SIM is absent.
287             String lpp_prof = SystemProperties.get(LPP_PROFILE);
288             if (!TextUtils.isEmpty(lpp_prof)) {
289                 // override default value of this if lpp_prof is not empty
290                 mProperties.setProperty(CONFIG_LPP_PROFILE, lpp_prof);
291             }
292         }
293 
294         /*
295          * Overlay carrier properties from a debug configuration file.
296          */
297         loadPropertiesFromGpsDebugConfig(mProperties, DEBUG_PROPERTIES_VENDOR_FILE);
298         loadPropertiesFromGpsDebugConfig(mProperties, DEBUG_PROPERTIES_SYSTEM_FILE);
299         mEsExtensionSec = getRangeCheckedConfigEsExtensionSec();
300 
301         logConfigurations();
302 
303         final HalInterfaceVersion gnssConfigurationIfaceVersion = getHalInterfaceVersion();
304         if (gnssConfigurationIfaceVersion != null) {
305             // Set to a range checked value.
306             if (isConfigEsExtensionSecSupported(gnssConfigurationIfaceVersion)
307                     && !native_set_es_extension_sec(mEsExtensionSec)) {
308                 Log.e(TAG, "Unable to set " + CONFIG_ES_EXTENSION_SEC + ": " + mEsExtensionSec);
309             }
310 
311             Map<String, SetCarrierProperty> map = new HashMap<String, SetCarrierProperty>();
312 
313             map.put(CONFIG_SUPL_VER, GnssConfiguration::native_set_supl_version);
314             map.put(CONFIG_SUPL_MODE, GnssConfiguration::native_set_supl_mode);
315 
316             if (isConfigSuplEsSupported(gnssConfigurationIfaceVersion)) {
317                 map.put(CONFIG_SUPL_ES, GnssConfiguration::native_set_supl_es);
318             }
319 
320             map.put(CONFIG_LPP_PROFILE, GnssConfiguration::native_set_lpp_profile);
321             map.put(CONFIG_A_GLONASS_POS_PROTOCOL_SELECT,
322                     GnssConfiguration::native_set_gnss_pos_protocol_select);
323             map.put(CONFIG_USE_EMERGENCY_PDN_FOR_EMERGENCY_SUPL,
324                     GnssConfiguration::native_set_emergency_supl_pdn);
325 
326             if (isConfigGpsLockSupported(gnssConfigurationIfaceVersion)) {
327                 map.put(CONFIG_GPS_LOCK, GnssConfiguration::native_set_gps_lock);
328             }
329 
330             for (Entry<String, SetCarrierProperty> entry : map.entrySet()) {
331                 String propertyName = entry.getKey();
332                 String propertyValueString = mProperties.getProperty(propertyName);
333                 if (propertyValueString != null) {
334                     try {
335                         int propertyValueInt = Integer.decode(propertyValueString);
336                         boolean result = entry.getValue().set(propertyValueInt);
337                         if (!result) {
338                             Log.e(TAG, "Unable to set " + propertyName);
339                         }
340                     } catch (NumberFormatException e) {
341                         Log.e(TAG, "Unable to parse propertyName: " + propertyValueString);
342                     }
343                 }
344             }
345         } else if (DEBUG) {
346             Log.d(TAG, "Skipped configuration update because GNSS configuration in GPS HAL is not"
347                     + " supported");
348         }
349     }
350 
logConfigurations()351     private void logConfigurations() {
352         FrameworkStatsLog.write(FrameworkStatsLog.GNSS_CONFIGURATION_REPORTED,
353                 getSuplHost(),
354                 getSuplPort(0),
355                 getC2KHost(),
356                 getC2KPort(0),
357                 getIntConfig(CONFIG_SUPL_VER, 0),
358                 getSuplMode(0),
359                 getSuplEs(0) == 1,
360                 getIntConfig(CONFIG_LPP_PROFILE, 0),
361                 getIntConfig(CONFIG_A_GLONASS_POS_PROTOCOL_SELECT, 0),
362                 getIntConfig(CONFIG_USE_EMERGENCY_PDN_FOR_EMERGENCY_SUPL, 0) == 1,
363                 getIntConfig(CONFIG_GPS_LOCK, 0),
364                 getEsExtensionSec(),
365                 mProperties.getProperty(CONFIG_NFW_PROXY_APPS));
366     }
367 
368     /**
369      * Loads GNSS properties from carrier config file.
370      */
loadPropertiesFromCarrierConfig(boolean inEmergency, int activeSubId)371     void loadPropertiesFromCarrierConfig(boolean inEmergency, int activeSubId) {
372         CarrierConfigManager configManager = (CarrierConfigManager)
373                 mContext.getSystemService(Context.CARRIER_CONFIG_SERVICE);
374         if (configManager == null) {
375             return;
376         }
377 
378         int subId = SubscriptionManager.getDefaultDataSubscriptionId();
379         if (inEmergency && activeSubId >= 0) {
380             subId = activeSubId;
381         }
382         PersistableBundle configs = SubscriptionManager.isValidSubscriptionId(subId)
383                 ? configManager.getConfigForSubId(subId) : configManager.getConfig();
384         if (configs == null) {
385             if (DEBUG) Log.d(TAG, "SIM not ready, use default carrier config.");
386             configs = CarrierConfigManager.getDefaultConfig();
387         }
388         for (String configKey : configs.keySet()) {
389             if (configKey != null && configKey.startsWith(CarrierConfigManager.Gps.KEY_PREFIX)) {
390                 String key = configKey
391                         .substring(CarrierConfigManager.Gps.KEY_PREFIX.length())
392                         .toUpperCase(Locale.ROOT);
393                 Object value = configs.get(configKey);
394                 if (DEBUG) Log.d(TAG, "Gps config: " + key + " = " + value);
395                 if (value instanceof String) {
396                     // Most GPS properties are of String type; convert so.
397                     mProperties.setProperty(key, (String) value);
398                 } else if (value != null) {
399                     mProperties.setProperty(key, value.toString());
400                 }
401             }
402         }
403     }
404 
loadPropertiesFromGpsDebugConfig(Properties properties, String filePath)405     private void loadPropertiesFromGpsDebugConfig(Properties properties, String filePath) {
406         try {
407             File file = new File(filePath);
408             FileInputStream stream = null;
409             try {
410                 stream = new FileInputStream(file);
411                 properties.load(stream);
412             } finally {
413                 IoUtils.closeQuietly(stream);
414             }
415         } catch (IOException e) {
416             if (DEBUG) Log.d(TAG, "Could not open GPS configuration file " + filePath);
417         }
418     }
419 
loadPropertiesFromResource(Context context, Properties properties)420     private void loadPropertiesFromResource(Context context,
421             Properties properties) {
422         String[] configValues = context.getResources().getStringArray(
423                 com.android.internal.R.array.config_gnssParameters);
424         for (String item : configValues) {
425             if (DEBUG) Log.d(TAG, "GnssParamsResource: " + item);
426             // We need to support "KEY =", but not "=VALUE".
427             int index = item.indexOf("=");
428             if (index > 0 && index + 1 < item.length()) {
429                 String key = item.substring(0, index);
430                 String value = item.substring(index + 1);
431                 properties.setProperty(key.trim().toUpperCase(Locale.ROOT), value);
432             } else {
433                 Log.w(TAG, "malformed contents: " + item);
434             }
435         }
436     }
437 
getRangeCheckedConfigEsExtensionSec()438     private int getRangeCheckedConfigEsExtensionSec() {
439         int emergencyExtensionSeconds = getIntConfig(CONFIG_ES_EXTENSION_SEC, 0);
440         if (emergencyExtensionSeconds > MAX_EMERGENCY_MODE_EXTENSION_SECONDS) {
441             Log.w(TAG, CONFIG_ES_EXTENSION_SEC + ": " + emergencyExtensionSeconds
442                     + " too high, reset to " + MAX_EMERGENCY_MODE_EXTENSION_SECONDS);
443             emergencyExtensionSeconds = MAX_EMERGENCY_MODE_EXTENSION_SECONDS;
444         } else if (emergencyExtensionSeconds < 0) {
445             Log.w(TAG, CONFIG_ES_EXTENSION_SEC + ": " + emergencyExtensionSeconds
446                     + " is negative, reset to zero.");
447             emergencyExtensionSeconds = 0;
448         }
449         return emergencyExtensionSeconds;
450     }
451 
getIntConfig(String configParameter, int defaultValue)452     private int getIntConfig(String configParameter, int defaultValue) {
453         String valueString = mProperties.getProperty(configParameter);
454         if (TextUtils.isEmpty(valueString)) {
455             return defaultValue;
456         }
457         try {
458             return Integer.decode(valueString);
459         } catch (NumberFormatException e) {
460             Log.e(TAG, "Unable to parse config parameter " + configParameter + " value: "
461                     + valueString + ". Using default value: " + defaultValue);
462             return defaultValue;
463         }
464     }
465 
getBooleanConfig(String configParameter, boolean defaultValue)466     private boolean getBooleanConfig(String configParameter, boolean defaultValue) {
467         String valueString = mProperties.getProperty(configParameter);
468         if (TextUtils.isEmpty(valueString)) {
469             return defaultValue;
470         }
471         return Boolean.parseBoolean(valueString);
472     }
473 
isConfigEsExtensionSecSupported( HalInterfaceVersion gnssConfiguartionIfaceVersion)474     private static boolean isConfigEsExtensionSecSupported(
475             HalInterfaceVersion gnssConfiguartionIfaceVersion) {
476         // ES_EXTENSION_SEC is introduced in @2.0::IGnssConfiguration.hal
477         return gnssConfiguartionIfaceVersion.mMajor >= 2;
478     }
479 
isConfigSuplEsSupported( HalInterfaceVersion gnssConfiguartionIfaceVersion)480     private static boolean isConfigSuplEsSupported(
481             HalInterfaceVersion gnssConfiguartionIfaceVersion) {
482         // SUPL_ES is deprecated in @2.0::IGnssConfiguration.hal
483         return gnssConfiguartionIfaceVersion.mMajor < 2;
484     }
485 
isConfigGpsLockSupported( HalInterfaceVersion gnssConfiguartionIfaceVersion)486     private static boolean isConfigGpsLockSupported(
487             HalInterfaceVersion gnssConfiguartionIfaceVersion) {
488         // GPS_LOCK is deprecated in @2.0::IGnssConfiguration.hal
489         return gnssConfiguartionIfaceVersion.mMajor < 2;
490     }
491 
isSimAbsent(Context context)492     private static boolean isSimAbsent(Context context) {
493         TelephonyManager phone = (TelephonyManager) context.getSystemService(
494                 Context.TELEPHONY_SERVICE);
495         return phone.getSimState() == TelephonyManager.SIM_STATE_ABSENT;
496     }
497 
native_get_gnss_configuration_version()498     private static native HalInterfaceVersion native_get_gnss_configuration_version();
499 
native_set_supl_version(int version)500     private static native boolean native_set_supl_version(int version);
501 
native_set_supl_mode(int mode)502     private static native boolean native_set_supl_mode(int mode);
503 
native_set_supl_es(int es)504     private static native boolean native_set_supl_es(int es);
505 
native_set_lpp_profile(int lppProfile)506     private static native boolean native_set_lpp_profile(int lppProfile);
507 
native_set_gnss_pos_protocol_select(int gnssPosProtocolSelect)508     private static native boolean native_set_gnss_pos_protocol_select(int gnssPosProtocolSelect);
509 
native_set_gps_lock(int gpsLock)510     private static native boolean native_set_gps_lock(int gpsLock);
511 
native_set_emergency_supl_pdn(int emergencySuplPdn)512     private static native boolean native_set_emergency_supl_pdn(int emergencySuplPdn);
513 
native_set_satellite_blocklist(int[] constellations, int[] svIds)514     private static native boolean native_set_satellite_blocklist(int[] constellations, int[] svIds);
515 
native_set_es_extension_sec(int emergencyExtensionSeconds)516     private static native boolean native_set_es_extension_sec(int emergencyExtensionSeconds);
517 }
518