1 /* 2 * Copyright (C) 2023 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; 18 19 20 import android.annotation.NonNull; 21 import android.annotation.Nullable; 22 import android.content.Context; 23 import android.location.Address; 24 import android.location.Geocoder; 25 import android.location.Location; 26 import android.location.LocationListener; 27 import android.location.LocationManager; 28 import android.net.ConnectivityManager; 29 import android.net.Network; 30 import android.net.NetworkCapabilities; 31 import android.net.NetworkRequest; 32 import android.os.Build; 33 import android.os.Bundle; 34 import android.os.Handler; 35 import android.os.HandlerThread; 36 import android.os.Looper; 37 import android.os.Message; 38 import android.os.SystemClock; 39 import android.os.SystemProperties; 40 import android.telephony.Rlog; 41 import android.util.Pair; 42 43 import com.android.internal.annotations.GuardedBy; 44 import com.android.internal.annotations.VisibleForTesting; 45 46 import java.util.ArrayList; 47 import java.util.HashMap; 48 import java.util.List; 49 import java.util.Locale; 50 import java.util.Map; 51 import java.util.concurrent.TimeUnit; 52 53 /** 54 * This class is used to detect the country where the device is. 55 * 56 * {@link LocaleTracker} also tracks country of a device based on the information provided by 57 * network operators. However, it won't work when a device is out of service. In such cases and if 58 * Wi-Fi is available, {@link Geocoder} can be used to query the country for the current location of 59 * the device. {@link TelephonyCountryDetector} uses both {@link LocaleTracker} and {@link Geocoder} 60 * to track country of a device. 61 */ 62 public class TelephonyCountryDetector extends Handler { 63 private static final String TAG = "TelephonyCountryDetector"; 64 private static final String ALLOW_MOCK_MODEM_PROPERTY = "persist.radio.allow_mock_modem"; 65 private static final String BOOT_ALLOW_MOCK_MODEM_PROPERTY = "ro.boot.radio.allow_mock_modem"; 66 private static final boolean DEBUG = !"user".equals(Build.TYPE); 67 private static final int EVENT_LOCATION_CHANGED = 1; 68 private static final int EVENT_LOCATION_COUNTRY_CODE_CHANGED = 2; 69 private static final int EVENT_NETWORK_COUNTRY_CODE_CHANGED = 3; 70 private static final int EVENT_WIFI_CONNECTIVITY_STATE_CHANGED = 4; 71 private static final int EVENT_LOCATION_UPDATE_REQUEST_QUOTA_RESET = 5; 72 73 // Wait 12 hours between location updates 74 private static final long TIME_BETWEEN_LOCATION_UPDATES_MILLIS = TimeUnit.HOURS.toMillis(12); 75 // Minimum distance before a location update is triggered, in meters. We don't need this to be 76 // too exact because all we care about is in what country the device is. 77 private static final float DISTANCE_BETWEEN_LOCATION_UPDATES_METERS = 2000; 78 protected static final long WAIT_FOR_LOCATION_UPDATE_REQUEST_QUOTA_RESET_TIMEOUT_MILLIS = 79 TimeUnit.MINUTES.toMillis(30); 80 81 private static TelephonyCountryDetector sInstance; 82 83 @NonNull private final Geocoder mGeocoder; 84 @NonNull private final LocationManager mLocationManager; 85 @NonNull private final ConnectivityManager mConnectivityManager; 86 @NonNull private final Object mLock = new Object(); 87 @NonNull 88 @GuardedBy("mLock") 89 private final Map<Integer, NetworkCountryCodeInfo> mNetworkCountryCodeInfoPerPhone = 90 new HashMap<>(); 91 @GuardedBy("mLock") 92 private String mLocationCountryCode = null; 93 /** This should be used by CTS only */ 94 @GuardedBy("mLock") 95 private String mOverriddenLocationCountryCode = null; 96 @GuardedBy("mLock") 97 private boolean mIsLocationUpdateRequested = false; 98 @GuardedBy("mLock") 99 private long mLocationCountryCodeUpdatedTimestampNanos = 0; 100 /** This should be used by CTS only */ 101 @GuardedBy("mLock") 102 private long mOverriddenLocationCountryCodeUpdatedTimestampNanos = 0; 103 @GuardedBy("mLock") 104 private List<String> mOverriddenCurrentNetworkCountryCodes = null; 105 @GuardedBy("mLock") 106 private Map<String, Long> mOverriddenCachedNetworkCountryCodes = new HashMap<>(); 107 @GuardedBy("mLock") 108 private boolean mIsCountryCodesOverridden = false; 109 @NonNull private final LocationListener mLocationListener = new LocationListener() { 110 @Override 111 public void onLocationChanged(Location location) { 112 logd("onLocationChanged: " + (location != null)); 113 if (location != null) { 114 sendRequestAsync(EVENT_LOCATION_CHANGED, location); 115 } 116 } 117 118 @Override 119 public void onProviderDisabled(String provider) { 120 logd("onProviderDisabled: provider=" + provider); 121 } 122 123 @Override 124 public void onProviderEnabled(String provider) { 125 logd("onProviderEnabled: provider=" + provider); 126 } 127 128 @Override 129 public void onStatusChanged(String provider, int status, Bundle extras) { 130 logd("onStatusChanged: provider=" + provider + ", status=" + status 131 + ", extras=" + extras); 132 } 133 }; 134 135 private class TelephonyGeocodeListener implements Geocoder.GeocodeListener { 136 private long mLocationUpdatedTime; TelephonyGeocodeListener(long locationUpdatedTime)137 TelephonyGeocodeListener(long locationUpdatedTime) { 138 mLocationUpdatedTime = locationUpdatedTime; 139 } 140 141 @Override onGeocode(List<Address> addresses)142 public void onGeocode(List<Address> addresses) { 143 if (addresses != null && !addresses.isEmpty()) { 144 logd("onGeocode: addresses is available"); 145 String countryCode = addresses.get(0).getCountryCode(); 146 sendRequestAsync(EVENT_LOCATION_COUNTRY_CODE_CHANGED, 147 new Pair<>(countryCode, mLocationUpdatedTime)); 148 } else { 149 logd("onGeocode: addresses is not available"); 150 } 151 } 152 153 @Override onError(String errorMessage)154 public void onError(String errorMessage) { 155 loge("GeocodeListener.onError=" + errorMessage); 156 } 157 } 158 159 /** 160 * Container class to store country code per Phone. 161 */ 162 private static class NetworkCountryCodeInfo { 163 public int phoneId; 164 public String countryCode; 165 public long timestamp; 166 167 @Override toString()168 public String toString() { 169 return "NetworkCountryCodeInfo[phoneId: " + phoneId 170 + ", countryCode: " + countryCode 171 + ", timestamp: " + timestamp + "]"; 172 } 173 } 174 175 /** 176 * Create the singleton instance of {@link TelephonyCountryDetector}. 177 * 178 * @param looper The looper to run the {@link TelephonyCountryDetector} instance. 179 * @param context The context used by the instance. 180 * @param locationManager The LocationManager instance. 181 * @param connectivityManager The ConnectivityManager instance. 182 */ 183 @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) TelephonyCountryDetector(@onNull Looper looper, @NonNull Context context, @NonNull LocationManager locationManager, @NonNull ConnectivityManager connectivityManager)184 protected TelephonyCountryDetector(@NonNull Looper looper, @NonNull Context context, 185 @NonNull LocationManager locationManager, 186 @NonNull ConnectivityManager connectivityManager) { 187 super(looper); 188 mLocationManager = locationManager; 189 mGeocoder = new Geocoder(context); 190 mConnectivityManager = connectivityManager; 191 initialize(); 192 } 193 194 /** @return the singleton instance of the {@link TelephonyCountryDetector} */ getInstance(@onNull Context context)195 public static synchronized TelephonyCountryDetector getInstance(@NonNull Context context) { 196 if (sInstance == null) { 197 HandlerThread handlerThread = new HandlerThread("TelephonyCountryDetector"); 198 handlerThread.start(); 199 sInstance = new TelephonyCountryDetector(handlerThread.getLooper(), context, 200 context.getSystemService(LocationManager.class), 201 context.getSystemService(ConnectivityManager.class)); 202 } 203 return sInstance; 204 } 205 206 /** 207 * @return The list of current network country ISOs if available, an empty list otherwise. 208 */ getCurrentNetworkCountryIso()209 @NonNull public List<String> getCurrentNetworkCountryIso() { 210 synchronized (mLock) { 211 if (mIsCountryCodesOverridden) { 212 logd("mOverriddenCurrentNetworkCountryCodes=" 213 + String.join(", ", mOverriddenCurrentNetworkCountryCodes)); 214 return mOverriddenCurrentNetworkCountryCodes; 215 } 216 } 217 218 List<String> result = new ArrayList<>(); 219 for (Phone phone : PhoneFactory.getPhones()) { 220 String countryIso = getNetworkCountryIsoForPhone(phone); 221 if (isValid(countryIso)) { 222 String countryIsoInUpperCase = countryIso.toUpperCase(Locale.US); 223 if (!result.contains(countryIsoInUpperCase)) { 224 result.add(countryIsoInUpperCase); 225 } 226 } else { 227 logd("getCurrentNetworkCountryIso: invalid countryIso=" + countryIso 228 + " for phoneId=" + phone.getPhoneId() + ", subId=" + phone.getSubId()); 229 } 230 } 231 return result; 232 } 233 234 /** 235 * @return The cached location country code and its updated timestamp. 236 */ getCachedLocationCountryIsoInfo()237 @NonNull public Pair<String, Long> getCachedLocationCountryIsoInfo() { 238 synchronized (mLock) { 239 if (mIsCountryCodesOverridden) { 240 logd("mOverriddenLocationCountryCode=" + mOverriddenLocationCountryCode 241 + " will be used"); 242 return new Pair<>(mOverriddenLocationCountryCode, 243 mOverriddenLocationCountryCodeUpdatedTimestampNanos); 244 } 245 return new Pair<>(mLocationCountryCode, mLocationCountryCodeUpdatedTimestampNanos); 246 } 247 } 248 249 /** 250 * This API should be used only when {@link #getCurrentNetworkCountryIso()} returns an empty 251 * list. 252 * 253 * @return The list of cached network country codes and their updated timestamps. 254 */ getCachedNetworkCountryIsoInfo()255 @NonNull public Map<String, Long> getCachedNetworkCountryIsoInfo() { 256 synchronized (mLock) { 257 if (mIsCountryCodesOverridden) { 258 logd("mOverriddenCachedNetworkCountryCodes = " 259 + String.join(", ", mOverriddenCachedNetworkCountryCodes.keySet()) 260 + " will be used"); 261 return mOverriddenCachedNetworkCountryCodes; 262 } 263 Map<String, Long> result = new HashMap<>(); 264 for (NetworkCountryCodeInfo countryCodeInfo : 265 mNetworkCountryCodeInfoPerPhone.values()) { 266 boolean alreadyAdded = result.containsKey(countryCodeInfo.countryCode); 267 if (!alreadyAdded || (alreadyAdded 268 && result.get(countryCodeInfo.countryCode) < countryCodeInfo.timestamp)) { 269 result.put(countryCodeInfo.countryCode, countryCodeInfo.timestamp); 270 } 271 } 272 return result; 273 } 274 } 275 276 @Override handleMessage(Message msg)277 public void handleMessage(Message msg) { 278 switch (msg.what) { 279 case EVENT_LOCATION_CHANGED: 280 queryCountryCodeForLocation((Location) msg.obj); 281 break; 282 case EVENT_LOCATION_COUNTRY_CODE_CHANGED: 283 setLocationCountryCode((Pair) msg.obj); 284 break; 285 case EVENT_NETWORK_COUNTRY_CODE_CHANGED: 286 handleNetworkCountryCodeChangedEvent((NetworkCountryCodeInfo) msg.obj); 287 break; 288 case EVENT_WIFI_CONNECTIVITY_STATE_CHANGED: 289 case EVENT_LOCATION_UPDATE_REQUEST_QUOTA_RESET: 290 evaluateRequestingLocationUpdates(); 291 break; 292 default: 293 logw("CountryDetectorHandler: unexpected message code: " + msg.what); 294 break; 295 } 296 } 297 298 /** 299 * This API is called by {@link LocaleTracker} whenever there is a change in network country 300 * code of a phone. 301 */ onNetworkCountryCodeChanged( @onNull Phone phone, @Nullable String currentCountryCode)302 public void onNetworkCountryCodeChanged( 303 @NonNull Phone phone, @Nullable String currentCountryCode) { 304 NetworkCountryCodeInfo networkCountryCodeInfo = new NetworkCountryCodeInfo(); 305 networkCountryCodeInfo.phoneId = phone.getPhoneId(); 306 networkCountryCodeInfo.countryCode = currentCountryCode; 307 sendRequestAsync(EVENT_NETWORK_COUNTRY_CODE_CHANGED, networkCountryCodeInfo); 308 } 309 310 /** 311 * This API should be used by only CTS tests to forcefully set the telephony country codes. 312 */ setCountryCodes(boolean reset, @NonNull List<String> currentNetworkCountryCodes, @NonNull Map<String, Long> cachedNetworkCountryCodes, String locationCountryCode, long locationCountryCodeTimestampNanos)313 public boolean setCountryCodes(boolean reset, @NonNull List<String> currentNetworkCountryCodes, 314 @NonNull Map<String, Long> cachedNetworkCountryCodes, String locationCountryCode, 315 long locationCountryCodeTimestampNanos) { 316 if (!isMockModemAllowed()) { 317 logd("setCountryCodes: mock modem is not allowed"); 318 return false; 319 } 320 logd("setCountryCodes: currentNetworkCountryCodes=" 321 + String.join(", ", currentNetworkCountryCodes) 322 + ", locationCountryCode=" + locationCountryCode 323 + ", locationCountryCodeTimestampNanos" + locationCountryCodeTimestampNanos 324 + ", reset=" + reset + ", cachedNetworkCountryCodes=" 325 + String.join(", ", cachedNetworkCountryCodes.keySet())); 326 327 synchronized (mLock) { 328 if (reset) { 329 mIsCountryCodesOverridden = false; 330 } else { 331 mIsCountryCodesOverridden = true; 332 mOverriddenCachedNetworkCountryCodes = cachedNetworkCountryCodes; 333 mOverriddenCurrentNetworkCountryCodes = currentNetworkCountryCodes; 334 mOverriddenLocationCountryCode = locationCountryCode; 335 mOverriddenLocationCountryCodeUpdatedTimestampNanos = 336 locationCountryCodeTimestampNanos; 337 } 338 } 339 return true; 340 } 341 342 @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) queryCountryCodeForLocation(@onNull Location location)343 protected void queryCountryCodeForLocation(@NonNull Location location) { 344 mGeocoder.getFromLocation(location.getLatitude(), location.getLongitude(), 1, 345 new TelephonyGeocodeListener(location.getElapsedRealtimeNanos())); 346 } 347 348 @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) getElapsedRealtimeNanos()349 protected long getElapsedRealtimeNanos() { 350 return SystemClock.elapsedRealtimeNanos(); 351 } 352 initialize()353 private void initialize() { 354 evaluateRequestingLocationUpdates(); 355 registerForWifiConnectivityStateChanged(); 356 } 357 isGeoCoderImplemented()358 private boolean isGeoCoderImplemented() { 359 return Geocoder.isPresent(); 360 } 361 registerForLocationUpdates()362 private void registerForLocationUpdates() { 363 // If the device does not implement Geocoder, there is no point trying to get location 364 // updates because we cannot retrieve the country based on the location anyway. 365 if (!isGeoCoderImplemented()) { 366 logd("Geocoder is not implemented on the device"); 367 return; 368 } 369 370 synchronized (mLock) { 371 if (mIsLocationUpdateRequested) { 372 logd("Already registered for location updates"); 373 return; 374 } 375 376 logd("Registering for location updates"); 377 /* 378 * PASSIVE_PROVIDER can be used to passively receive location updates when other 379 * applications or services request them without actually requesting the locations 380 * ourselves. This provider will only return locations generated by other providers. 381 * This provider is used to make sure there is no impact on the thermal and battery of 382 * a device. 383 */ 384 mLocationManager.requestLocationUpdates(LocationManager.PASSIVE_PROVIDER, 385 TIME_BETWEEN_LOCATION_UPDATES_MILLIS, DISTANCE_BETWEEN_LOCATION_UPDATES_METERS, 386 mLocationListener); 387 mIsLocationUpdateRequested = true; 388 mLocationListener.onLocationChanged(getLastKnownLocation()); 389 } 390 } 391 392 @Nullable getLastKnownLocation()393 private Location getLastKnownLocation() { 394 Location result = null; 395 for (String provider : mLocationManager.getProviders(true)) { 396 Location location = mLocationManager.getLastKnownLocation(provider); 397 if (location != null && (result == null 398 || result.getElapsedRealtimeNanos() < location.getElapsedRealtimeNanos())) { 399 result = location; 400 } 401 } 402 return result; 403 } 404 unregisterForLocationUpdates()405 private void unregisterForLocationUpdates() { 406 synchronized (mLock) { 407 if (!mIsLocationUpdateRequested) { 408 logd("Location update was not requested yet"); 409 return; 410 } 411 if (isLocationUpdateRequestQuotaExceeded()) { 412 logd("Removing location updates will be re-evaluated after the quota is refilled"); 413 return; 414 } 415 mLocationManager.removeUpdates(mLocationListener); 416 mIsLocationUpdateRequested = false; 417 sendMessageDelayed(obtainMessage(EVENT_LOCATION_UPDATE_REQUEST_QUOTA_RESET), 418 WAIT_FOR_LOCATION_UPDATE_REQUEST_QUOTA_RESET_TIMEOUT_MILLIS); 419 } 420 } 421 isLocationUpdateRequestQuotaExceeded()422 private boolean isLocationUpdateRequestQuotaExceeded() { 423 return hasMessages(EVENT_LOCATION_UPDATE_REQUEST_QUOTA_RESET); 424 } 425 shouldRequestLocationUpdate()426 private boolean shouldRequestLocationUpdate() { 427 return getCurrentNetworkCountryIso().isEmpty() && isWifiNetworkConnected(); 428 } 429 430 /** 431 * Posts the specified command to be executed on the main thread and returns immediately. 432 * 433 * @param command command to be executed on the main thread 434 * @param argument additional parameters required to perform of the operation 435 */ sendRequestAsync(int command, @NonNull Object argument)436 private void sendRequestAsync(int command, @NonNull Object argument) { 437 Message msg = this.obtainMessage(command, argument); 438 msg.sendToTarget(); 439 } 440 handleNetworkCountryCodeChangedEvent( @onNull NetworkCountryCodeInfo currentNetworkCountryCodeInfo)441 private void handleNetworkCountryCodeChangedEvent( 442 @NonNull NetworkCountryCodeInfo currentNetworkCountryCodeInfo) { 443 logd("currentNetworkCountryCodeInfo=" + currentNetworkCountryCodeInfo); 444 if (isValid(currentNetworkCountryCodeInfo.countryCode)) { 445 synchronized (mLock) { 446 NetworkCountryCodeInfo cachedNetworkCountryCodeInfo = 447 mNetworkCountryCodeInfoPerPhone.computeIfAbsent( 448 currentNetworkCountryCodeInfo.phoneId, 449 k -> new NetworkCountryCodeInfo()); 450 cachedNetworkCountryCodeInfo.phoneId = currentNetworkCountryCodeInfo.phoneId; 451 cachedNetworkCountryCodeInfo.timestamp = getElapsedRealtimeNanos(); 452 cachedNetworkCountryCodeInfo.countryCode = 453 currentNetworkCountryCodeInfo.countryCode.toUpperCase(Locale.US); 454 } 455 } else { 456 logd("handleNetworkCountryCodeChangedEvent: Got invalid or empty country code for " 457 + "phoneId=" + currentNetworkCountryCodeInfo.phoneId); 458 synchronized (mLock) { 459 if (mNetworkCountryCodeInfoPerPhone.containsKey( 460 currentNetworkCountryCodeInfo.phoneId)) { 461 // The country code has changed from valid to invalid. Thus, we need to update 462 // the last valid timestamp. 463 NetworkCountryCodeInfo cachedNetworkCountryCodeInfo = 464 mNetworkCountryCodeInfoPerPhone.get( 465 currentNetworkCountryCodeInfo.phoneId); 466 cachedNetworkCountryCodeInfo.timestamp = getElapsedRealtimeNanos(); 467 } 468 } 469 } 470 evaluateRequestingLocationUpdates(); 471 } 472 setLocationCountryCode(@onNull Pair<String, Long> countryCodeInfo)473 private void setLocationCountryCode(@NonNull Pair<String, Long> countryCodeInfo) { 474 logd("Set location country code to: " + countryCodeInfo.first); 475 if (!isValid(countryCodeInfo.first)) { 476 logd("Received invalid location country code"); 477 } else { 478 synchronized (mLock) { 479 mLocationCountryCode = countryCodeInfo.first.toUpperCase(Locale.US); 480 mLocationCountryCodeUpdatedTimestampNanos = countryCodeInfo.second; 481 } 482 } 483 } 484 getNetworkCountryIsoForPhone(@onNull Phone phone)485 private String getNetworkCountryIsoForPhone(@NonNull Phone phone) { 486 ServiceStateTracker serviceStateTracker = phone.getServiceStateTracker(); 487 if (serviceStateTracker == null) { 488 logw("getNetworkCountryIsoForPhone: serviceStateTracker is null"); 489 return null; 490 } 491 492 LocaleTracker localeTracker = serviceStateTracker.getLocaleTracker(); 493 if (localeTracker == null) { 494 logw("getNetworkCountryIsoForPhone: localeTracker is null"); 495 return null; 496 } 497 498 return localeTracker.getCurrentCountry(); 499 } 500 registerForWifiConnectivityStateChanged()501 private void registerForWifiConnectivityStateChanged() { 502 logd("registerForWifiConnectivityStateChanged"); 503 NetworkRequest.Builder builder = new NetworkRequest.Builder(); 504 builder.addTransportType(NetworkCapabilities.TRANSPORT_WIFI); 505 ConnectivityManager.NetworkCallback networkCallback = 506 new ConnectivityManager.NetworkCallback() { 507 @Override 508 public void onAvailable(Network network) { 509 logd("Wifi network available: " + network); 510 sendRequestAsync(EVENT_WIFI_CONNECTIVITY_STATE_CHANGED, null); 511 } 512 513 @Override 514 public void onLost(Network network) { 515 logd("Wifi network lost: " + network); 516 sendRequestAsync(EVENT_WIFI_CONNECTIVITY_STATE_CHANGED, null); 517 } 518 519 @Override 520 public void onUnavailable() { 521 logd("Wifi network unavailable"); 522 sendRequestAsync(EVENT_WIFI_CONNECTIVITY_STATE_CHANGED, null); 523 } 524 }; 525 mConnectivityManager.registerNetworkCallback(builder.build(), networkCallback); 526 } 527 evaluateRequestingLocationUpdates()528 private void evaluateRequestingLocationUpdates() { 529 if (shouldRequestLocationUpdate()) { 530 registerForLocationUpdates(); 531 } else { 532 unregisterForLocationUpdates(); 533 } 534 } 535 isWifiNetworkConnected()536 private boolean isWifiNetworkConnected() { 537 Network activeNetwork = mConnectivityManager.getActiveNetwork(); 538 NetworkCapabilities networkCapabilities = 539 mConnectivityManager.getNetworkCapabilities(activeNetwork); 540 return networkCapabilities != null 541 && networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) 542 && networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET); 543 } 544 545 /** 546 * Check whether this is a valid country code. 547 * 548 * @param countryCode A 2-Character alphanumeric country code. 549 * @return {@code true} if the countryCode is valid, {@code false} otherwise. 550 */ isValid(String countryCode)551 private static boolean isValid(String countryCode) { 552 return countryCode != null && countryCode.length() == 2 553 && countryCode.chars().allMatch(Character::isLetterOrDigit); 554 } 555 isMockModemAllowed()556 private static boolean isMockModemAllowed() { 557 return (DEBUG || SystemProperties.getBoolean(ALLOW_MOCK_MODEM_PROPERTY, false) 558 || SystemProperties.getBoolean(BOOT_ALLOW_MOCK_MODEM_PROPERTY, false)); 559 } 560 logd(@onNull String log)561 private static void logd(@NonNull String log) { 562 Rlog.d(TAG, log); 563 } 564 logw(@onNull String log)565 private static void logw(@NonNull String log) { 566 Rlog.w(TAG, log); 567 } 568 loge(@onNull String log)569 private static void loge(@NonNull String log) { 570 Rlog.e(TAG, log); 571 } 572 } 573