1 /* 2 * Copyright (C) 2021 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.uwb; 18 19 import static com.android.server.uwb.data.UwbUciConstants.STATUS_CODE_OK; 20 21 import android.annotation.NonNull; 22 import android.app.AlarmManager; 23 import android.app.PendingIntent; 24 import android.content.BroadcastReceiver; 25 import android.content.Context; 26 import android.content.ContextParams; 27 import android.content.Intent; 28 import android.content.IntentFilter; 29 import android.content.pm.PackageManager; 30 import android.location.Address; 31 import android.location.Geocoder; 32 import android.location.Location; 33 import android.location.LocationListener; 34 import android.location.LocationManager; 35 import android.net.wifi.WifiManager; 36 import android.net.wifi.WifiManager.ActiveCountryCodeChangedCallback; 37 import android.os.Handler; 38 import android.provider.Settings; 39 import android.telephony.SubscriptionInfo; 40 import android.telephony.SubscriptionManager; 41 import android.telephony.TelephonyManager; 42 import android.text.TextUtils; 43 import android.util.ArraySet; 44 import android.util.Log; 45 import android.util.Pair; 46 47 import androidx.annotation.Nullable; 48 49 import com.android.internal.annotations.Keep; 50 import com.android.internal.annotations.VisibleForTesting; 51 import com.android.modules.utils.HandlerExecutor; 52 import com.android.server.uwb.jni.NativeUwbManager; 53 54 import java.io.FileDescriptor; 55 import java.io.PrintWriter; 56 import java.nio.charset.StandardCharsets; 57 import java.time.LocalDateTime; 58 import java.time.format.DateTimeFormatter; 59 import java.util.ArrayList; 60 import java.util.List; 61 import java.util.Locale; 62 import java.util.Map; 63 import java.util.Objects; 64 import java.util.Optional; 65 import java.util.Set; 66 import java.util.concurrent.ConcurrentSkipListMap; 67 import java.util.stream.Collectors; 68 69 /** 70 * Provide functions for making changes to UWB country code. 71 * This Country Code is from MCC or phone default setting. This class sends Country Code 72 * to UWB vendor via the HAL. 73 */ 74 public class UwbCountryCode { 75 private static final String TAG = "UwbCountryCode"; 76 // To be used when there is no country code available. 77 @VisibleForTesting 78 public static final String DEFAULT_COUNTRY_CODE = "00"; 79 private static final DateTimeFormatter FORMATTER = 80 DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS"); 81 /** 82 * Copied from {@link TelephonyManager} because it's @hide. 83 * TODO (b/242326831): Use @SystemApi. 84 */ 85 @VisibleForTesting 86 public static final String EXTRA_LAST_KNOWN_NETWORK_COUNTRY = 87 "android.telephony.extra.LAST_KNOWN_NETWORK_COUNTRY"; 88 89 public static final String GEOCODER_RETRY_TIMEOUT_INTENT = 90 "com.android.uwb.uwbcountrycode.GEOCODE_RETRY"; 91 92 // Wait 1 hour between updates 93 private static final long TIME_BETWEEN_UPDATES_MS = 1000L * 60 * 60 * 1; 94 // Minimum distance before an update is triggered, in meters. We don't need this to be too 95 // exact because all we care about is what country the user is in. 96 private static final float DISTANCE_BETWEEN_UPDATES_METERS = 5_000.0f; 97 98 // The last SIM slot index, used when the slot is not known, so that the corresponding 99 // country code has the lowest priority (in the sorted mTelephonyCountryCodeInfoPerSlot map). 100 private static final int LAST_SIM_SLOT_INDEX = Integer.MAX_VALUE; 101 102 // Wait between Fused updates 103 public static final long FUSED_TIME_BETWEEN_UPDATES_MS = 1000L * 60 * 1; 104 105 // Geocode Resolver timer timeout 106 public static final long GEOCODE_RESOLVER_FIRST_TIMEOUT_MS = 1000L * 5 * 1; 107 public static final long GEOCODE_RESOLVER_RETRY_TIMEOUT_MS = 1000L * 60 * 1; 108 109 private final Context mContext; 110 private final Handler mHandler; 111 private final TelephonyManager mTelephonyManager; 112 private final SubscriptionManager mSubscriptionManager; 113 private final LocationManager mLocationManager; 114 private final Geocoder mGeocoder; 115 private final NativeUwbManager mNativeUwbManager; 116 private final UwbInjector mUwbInjector; 117 private final Set<CountryCodeChangedListener> mListeners = new ArraySet<>(); 118 119 private AlarmManager mGeocodeRetryTimer = null; 120 private Intent mRetryTimerIntent = new Intent(GEOCODER_RETRY_TIMEOUT_INTENT); 121 private BroadcastReceiver mRetryTimeoutReceiver; 122 private boolean mGeocoderRetryTimerActive = false; 123 private boolean mFusedLocationProviderActive = false; 124 private Map<Integer, TelephonyCountryCodeSlotInfo> mTelephonyCountryCodeInfoPerSlot = 125 new ConcurrentSkipListMap(); 126 private String mWifiCountryCode = null; 127 private String mLocationCountryCode = null; 128 private String mCachedCountryCode = null; 129 private String mOverrideCountryCode = null; 130 private String mCountryCode = null; 131 private Optional<Integer> mCountryCodeStatus = Optional.empty(); 132 private String mCountryCodeUpdatedTimestamp = null; 133 private String mWifiCountryTimestamp = null; 134 private String mLocationCountryTimestamp = null; 135 private boolean mIsMccMncOemOverrideEnabled = false; 136 private final List<MccMnc> mMccMncOemOverrideList = new ArrayList<>(); 137 138 /** 139 * Container class to store country code per sim slot. 140 */ 141 public static class TelephonyCountryCodeSlotInfo { 142 public int slotIdx; 143 public String countryCode; 144 public String lastKnownCountryCode; 145 public String timestamp; 146 147 @Override toString()148 public String toString() { 149 return "TelephonyCountryCodeSlotInfo[ slotIdx: " + slotIdx 150 + ", countryCode: " + countryCode 151 + ", lastKnownCountryCode: " + lastKnownCountryCode 152 + ", timestamp: " + timestamp + "]"; 153 } 154 } 155 156 public interface CountryCodeChangedListener { 157 /** 158 * Notify listeners about a country code change. 159 * @param statusCode - Status of the UWBS controller configuring the {@code newCountryCode}: 160 * - STATUS_CODE_OK: The country code was successfully configured by UWBS. 161 * - STATUS_CODE_ANDROID_REGULATION_UWB_OFF: UWB is not supported in the configured 162 * country code. 163 * - Other status codes returned by the UWBS controller. 164 * @param newCountryCode - The new UWB country code configured in the UWBS controller. 165 */ onCountryCodeChanged(int statusCode, @Nullable String newCountryCode)166 void onCountryCodeChanged(int statusCode, @Nullable String newCountryCode); 167 } 168 UwbCountryCode( Context context, NativeUwbManager nativeUwbManager, Handler handler, UwbInjector uwbInjector)169 public UwbCountryCode( 170 Context context, NativeUwbManager nativeUwbManager, Handler handler, 171 UwbInjector uwbInjector) { 172 mContext = context.createContext( 173 new ContextParams.Builder().setAttributionTag(TAG).build()); 174 mTelephonyManager = mContext.getSystemService(TelephonyManager.class); 175 mSubscriptionManager = mContext.getSystemService(SubscriptionManager.class); 176 mLocationManager = mContext.getSystemService(LocationManager.class); 177 mGeocoder = uwbInjector.makeGeocoder(); 178 mNativeUwbManager = nativeUwbManager; 179 mHandler = handler; 180 mUwbInjector = uwbInjector; 181 mGeocodeRetryTimer = mContext.getSystemService(AlarmManager.class); 182 } 183 184 @Keep 185 private class WifiCountryCodeCallback implements ActiveCountryCodeChangedCallback { onActiveCountryCodeChanged(@onNull String countryCode)186 public void onActiveCountryCodeChanged(@NonNull String countryCode) { 187 setWifiCountryCode(countryCode); 188 } 189 onCountryCodeInactive()190 public void onCountryCodeInactive() { 191 setWifiCountryCode(""); 192 } 193 } 194 195 private class MccMnc { 196 private final int mMcc; 197 private final int mMnc; MccMnc(int mcc, int mnc)198 MccMnc(int mcc, int mnc) { 199 mMcc = mcc; 200 mMnc = mnc; 201 } getMcc()202 public int getMcc() { 203 return mMcc; 204 } getMnc()205 public int getMnc() { 206 return mMnc; 207 } 208 } 209 generateOemOverrideMccMncList()210 private void generateOemOverrideMccMncList() { 211 String[] mccMncOemOverrideList = 212 mUwbInjector.getDeviceConfigFacade().getMccMncOemOverrideList(); 213 if (mccMncOemOverrideList == null) return; 214 for (String mccMnc : mccMncOemOverrideList) { 215 int mcc = -1; 216 int mnc = -1; 217 try { 218 mcc = Integer.valueOf(mccMnc.substring(0, 3)); 219 } catch (Exception e) { 220 Log.e(TAG, "No mcc set", e); 221 continue; 222 } 223 try { 224 mnc = Integer.valueOf(mccMnc.substring(3)); 225 } catch (Exception e) { 226 Log.d(TAG, "No mnc set", e); 227 } 228 mMccMncOemOverrideList.add(new MccMnc(mcc, mnc)); 229 } 230 } 231 shouldOverrideCountryCodeForMccMncs()232 private boolean shouldOverrideCountryCodeForMccMncs() { 233 List<SubscriptionInfo> subscriptionInfoList = 234 mSubscriptionManager.getCompleteActiveSubscriptionInfoList(); 235 if (subscriptionInfoList != null && !subscriptionInfoList.isEmpty()) { 236 for (SubscriptionInfo subscriptionInfo : subscriptionInfoList) { 237 if (shouldOverrideCountryCodeForMccMnc( 238 subscriptionInfo.getMccString(), subscriptionInfo.getMncString())) { 239 return true; 240 } 241 } 242 } 243 return false; 244 } 245 shouldOverrideCountryCodeForMccMnc(String mccString, String mncString)246 private boolean shouldOverrideCountryCodeForMccMnc(String mccString, String mncString) { 247 if (TextUtils.isEmpty(mccString) || TextUtils.isEmpty(mncString)) return false; 248 try { 249 int mcc = Integer.valueOf(mccString); 250 int mnc = Integer.valueOf(mncString); 251 for (MccMnc mccMnc: mMccMncOemOverrideList) { 252 if (mccMnc.getMcc() == mcc) { 253 if (mccMnc.getMnc() == -1) { 254 Log.i(TAG, "Override MCC meets " + mccMnc.getMcc()); 255 return true; 256 } else if (mccMnc.getMnc() == mnc) { 257 Log.i(TAG, "Override MCC MNC meets " 258 + mccMnc.getMcc() + ":" + mccMnc.getMnc()); 259 return true; 260 } 261 } 262 } 263 } catch (Exception e) { 264 Log.e(TAG, "failed in shouldOverrideCountryCodeForMccMnc", e); 265 } 266 return false; 267 } 268 setCountryCodeFromGeocodingLocation(@ullable Location location)269 private void setCountryCodeFromGeocodingLocation(@Nullable Location location) { 270 if (location == null) return; 271 Geocoder.GeocodeListener geocodeListener = (List<Address> addresses) -> { 272 if (addresses != null && !addresses.isEmpty()) { 273 String countryCode = addresses.get(0).getCountryCode(); 274 mHandler.post(() -> setLocationCountryCode(countryCode)); 275 } 276 }; 277 try { 278 mGeocoder.getFromLocation( 279 location.getLatitude(), location.getLongitude(), 1, geocodeListener); 280 } catch (IllegalArgumentException e) { 281 // Wrong Type of Latitude and Longitude return from getFromLocation. 282 Log.e(TAG, "Exception while fetching latitude/longitude from location", e); 283 284 // Call setCountryCode() to update country code from other sources. 285 mLocationCountryCode = null; 286 setCountryCode(false); 287 } 288 } 289 290 /** 291 * Initialize the module. 292 */ initialize()293 public void initialize() { 294 // Read the cached country code first (if caching is enabled on the device) 295 if (mUwbInjector.getDeviceConfigFacade().isPersistentCacheUseForCountryCodeEnabled()) { 296 String cachedCountryCode = mUwbInjector.getUwbSettingsStore().get( 297 UwbSettingsStore.SETTINGS_CACHED_COUNTRY_CODE); 298 if (isValid(cachedCountryCode)) mCachedCountryCode = cachedCountryCode; 299 } 300 IntentFilter filter = new IntentFilter(TelephonyManager.ACTION_NETWORK_COUNTRY_CHANGED); 301 generateOemOverrideMccMncList(); 302 if (!mMccMncOemOverrideList.isEmpty()) { 303 filter.addAction(TelephonyManager.ACTION_SIM_CARD_STATE_CHANGED); 304 } 305 mContext.registerReceiver( 306 new BroadcastReceiver() { 307 @Override 308 public void onReceive(Context context, Intent intent) { 309 if (intent.getAction() 310 .equals(TelephonyManager.ACTION_NETWORK_COUNTRY_CHANGED)) { 311 int slotIdx = intent.getIntExtra( 312 SubscriptionManager.EXTRA_SLOT_INDEX, 313 LAST_SIM_SLOT_INDEX); 314 String countryCode = intent.getStringExtra( 315 TelephonyManager.EXTRA_NETWORK_COUNTRY); 316 String lastKnownCountryCode = intent.getStringExtra( 317 EXTRA_LAST_KNOWN_NETWORK_COUNTRY); 318 Log.d(TAG, "Telephony Country code changed to: " + countryCode); 319 setTelephonyCountryCodeAndLastKnownCountryCode( 320 slotIdx, countryCode, lastKnownCountryCode); 321 } else if (intent.getAction() 322 .equals(TelephonyManager.ACTION_SIM_CARD_STATE_CHANGED)) { 323 if (!mMccMncOemOverrideList.isEmpty()) { 324 boolean shouldOverrideCountryCodeForMccMnc = 325 shouldOverrideCountryCodeForMccMncs(); 326 if (mIsMccMncOemOverrideEnabled 327 != shouldOverrideCountryCodeForMccMnc) { 328 Log.i(TAG, "OEM override for mcc mnc changed"); 329 mIsMccMncOemOverrideEnabled = 330 shouldOverrideCountryCodeForMccMnc; 331 setCountryCode(true); 332 } 333 } 334 } 335 } 336 }, 337 filter, null, mHandler); 338 try { 339 if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WIFI)) { 340 mContext.getSystemService(WifiManager.class) 341 .registerActiveCountryCodeChangedCallback( 342 new HandlerExecutor(mHandler), new WifiCountryCodeCallback()); 343 } 344 } catch (SecurityException e) { 345 // failed to register WifiCountryCodeCallback 346 Log.e(TAG, "failed to register WifiCountryCodeCallback", e); 347 mWifiCountryCode = null; 348 } 349 if (mUwbInjector.getDeviceConfigFacade().isLocationUseForCountryCodeEnabled() && 350 mUwbInjector.isGeocoderPresent()) { 351 mLocationManager.requestLocationUpdates( 352 LocationManager.PASSIVE_PROVIDER, 353 TIME_BETWEEN_UPDATES_MS, 354 DISTANCE_BETWEEN_UPDATES_METERS, 355 location -> setCountryCodeFromGeocodingLocation(location)); 356 357 } 358 Log.d(TAG, "Default country code from system property is " 359 + mUwbInjector.getOemDefaultCountryCode()); 360 List<SubscriptionInfo> subscriptionInfoList = 361 mSubscriptionManager.getActiveSubscriptionInfoList(); 362 if (subscriptionInfoList != null && !subscriptionInfoList.isEmpty()) { 363 Set<Integer> slotIdxs = subscriptionInfoList 364 .stream() 365 .map(SubscriptionInfo::getSimSlotIndex) 366 .collect(Collectors.toSet()); 367 for (Integer slotIdx : slotIdxs) { 368 String countryCode; 369 try { 370 countryCode = mTelephonyManager.getNetworkCountryIso(slotIdx); 371 } catch (IllegalArgumentException e) { 372 Log.e(TAG, "Failed to get country code for slot id:" + slotIdx, e); 373 continue; 374 } 375 setTelephonyCountryCodeAndLastKnownCountryCode(slotIdx, countryCode, null); 376 } 377 } else { 378 // Fetch and configure the networkCountryIso() when the subscriptionInfoList is either 379 // null or empty. This is done only when the country code is valid. 380 String countryCode = mTelephonyManager.getNetworkCountryIso(); 381 if (isValid(countryCode)) { 382 setTelephonyCountryCodeAndLastKnownCountryCode( 383 LAST_SIM_SLOT_INDEX, countryCode, null); 384 } 385 } 386 if (mUwbInjector.getDeviceConfigFacade().isLocationUseForCountryCodeEnabled() && 387 mUwbInjector.isGeocoderPresent()) { 388 setCountryCodeFromGeocodingLocation( 389 mLocationManager.getLastKnownLocation(LocationManager.FUSED_PROVIDER)); 390 } 391 if (mUwbInjector.getDeviceConfigFacade().isLocationUseForCountryCodeEnabled() 392 && mUwbInjector.isGeocoderPresent() && !isValid(mCachedCountryCode) 393 && mUwbInjector.getDeviceConfigFacade().isPersistentCacheUseForCountryCodeEnabled() 394 && (mUwbInjector.getGlobalSettingsInt( 395 Settings.Global.AIRPLANE_MODE_ON, 0) == 0)) { 396 startFusedLocationManager(); 397 } 398 // Current Wifi country code update is sent immediately on registration. 399 } 400 addListener(@onNull CountryCodeChangedListener listener)401 public void addListener(@NonNull CountryCodeChangedListener listener) { 402 mListeners.add(listener); 403 } 404 405 /** Start Fused Provider Country Code Resolver */ startFusedLocationManager()406 private void startFusedLocationManager() { 407 if (mFusedLocationProviderActive || !mUwbInjector 408 .getDeviceConfigFacade().isFusedCountryCodeProviderEnabled()) { 409 return; 410 } 411 Log.d(TAG, "Start Fused Country Code Resolver"); 412 mLocationManager.requestLocationUpdates(LocationManager.FUSED_PROVIDER, 413 FUSED_TIME_BETWEEN_UPDATES_MS, DISTANCE_BETWEEN_UPDATES_METERS, 414 mFusedLocationListener, mUwbInjector.getUwbServiceLooper()); 415 mFusedLocationProviderActive = true; 416 } 417 418 /** Stop Fused Provider Country Code Resolver */ stopFusedLocationManager()419 private void stopFusedLocationManager() { 420 if (mFusedLocationProviderActive) { 421 Log.d(TAG, "Stopping Fused Country Code Resolver"); 422 mLocationManager.removeUpdates(mFusedLocationListener); 423 mFusedLocationProviderActive = false; 424 } 425 } 426 427 private final LocationListener mFusedLocationListener = new LocationListener() { 428 @Override 429 public void onLocationChanged(Location location) { 430 synchronized (UwbCountryCode.this) { 431 Log.d(TAG, "Fused Provider onLocationChanged: " + location); 432 if (location.isComplete()) { 433 setCountryCodeFromGeocodingLocation(location); 434 startRetryRequest(); 435 stopFusedLocationManager(); 436 } 437 } 438 } 439 }; 440 441 /** Start retry timer in case Geocode Resolver fails */ startRetryRequest()442 private void startRetryRequest() { 443 if (mGeocoderRetryTimerActive) return; 444 445 Log.d(TAG, "Starting Geocode Resolver Timer"); 446 mRetryTimeoutReceiver = new BroadcastReceiver() { 447 @Override public void onReceive(Context context, Intent intent) { 448 Log.d(TAG, "Geocode Resolver Retry Timeout onReceive"); 449 setCountryCodeFromGeocodingLocation( 450 mLocationManager.getLastKnownLocation(LocationManager.FUSED_PROVIDER)); 451 } 452 }; 453 mContext.registerReceiver(mRetryTimeoutReceiver, 454 new IntentFilter(GEOCODER_RETRY_TIMEOUT_INTENT)); 455 mGeocodeRetryTimer.setInexactRepeating(AlarmManager.ELAPSED_REALTIME, 456 mUwbInjector.getElapsedSinceBootMillis() + GEOCODE_RESOLVER_FIRST_TIMEOUT_MS, 457 GEOCODE_RESOLVER_RETRY_TIMEOUT_MS, getRetryTimerBroadcast()); 458 mGeocoderRetryTimerActive = true; 459 } 460 461 /** Stop retry timer in case Geocode Resolver fails */ stopRetryRequest()462 private void stopRetryRequest() { 463 if (mGeocoderRetryTimerActive) { 464 Log.d(TAG, "Stop Geocode Resolver timer"); 465 mGeocodeRetryTimer.cancel(getRetryTimerBroadcast()); 466 mContext.unregisterReceiver(mRetryTimeoutReceiver); 467 mGeocoderRetryTimerActive = false; 468 } 469 } 470 getRetryTimerBroadcast()471 private PendingIntent getRetryTimerBroadcast() { 472 return PendingIntent.getBroadcast(mContext, 0, mRetryTimerIntent, 473 PendingIntent.FLAG_IMMUTABLE); 474 } 475 setTelephonyCountryCodeAndLastKnownCountryCode(int slotIdx, String countryCode, String lastKnownCountryCode)476 private void setTelephonyCountryCodeAndLastKnownCountryCode(int slotIdx, String countryCode, 477 String lastKnownCountryCode) { 478 Log.d(TAG, "Set telephony country code to: " + countryCode 479 + ", last country code to: " + lastKnownCountryCode + " for slotIdx: " + slotIdx); 480 TelephonyCountryCodeSlotInfo telephonyCountryCodeInfoSlot = 481 mTelephonyCountryCodeInfoPerSlot.computeIfAbsent( 482 slotIdx, k -> new TelephonyCountryCodeSlotInfo()); 483 telephonyCountryCodeInfoSlot.slotIdx = slotIdx; 484 telephonyCountryCodeInfoSlot.timestamp = LocalDateTime.now().format(FORMATTER); 485 // Empty country code. 486 if (!isValid(countryCode)) { 487 Log.d(TAG, "Received empty telephony country code"); 488 telephonyCountryCodeInfoSlot.countryCode = null; 489 } else { 490 telephonyCountryCodeInfoSlot.countryCode = countryCode.toUpperCase(Locale.US); 491 } 492 if (!isValid(lastKnownCountryCode)) { 493 Log.d(TAG, "Received empty telephony last known country code"); 494 telephonyCountryCodeInfoSlot.lastKnownCountryCode = null; 495 } else { 496 telephonyCountryCodeInfoSlot.lastKnownCountryCode = 497 lastKnownCountryCode.toUpperCase(Locale.US); 498 } 499 setCountryCode(false); 500 } 501 setWifiCountryCode(String countryCode)502 private void setWifiCountryCode(String countryCode) { 503 Log.d(TAG, "Set wifi country code to: " + countryCode); 504 mWifiCountryTimestamp = LocalDateTime.now().format(FORMATTER); 505 // Empty country code. 506 if (!isValid(countryCode)) { 507 Log.d(TAG, "Received empty wifi country code"); 508 mWifiCountryCode = null; 509 } else { 510 mWifiCountryCode = countryCode.toUpperCase(Locale.US); 511 } 512 setCountryCode(false); 513 } 514 setLocationCountryCode(String countryCode)515 private void setLocationCountryCode(String countryCode) { 516 Log.d(TAG, "Set location country code to: " + countryCode); 517 mLocationCountryTimestamp = LocalDateTime.now().format(FORMATTER); 518 // Empty country code. 519 if (!isValid(countryCode)) { 520 Log.d(TAG, "Received empty location country code"); 521 mLocationCountryCode = null; 522 } else { 523 mLocationCountryCode = countryCode.toUpperCase(Locale.US); 524 } 525 setCountryCode(false); 526 } 527 528 /** 529 * Priority order of country code sources (we stop at the first known country code source): 530 * 1. Override country code - Country code forced via shell command (local/automated testing) 531 * 2. Telephony country code - Current country code retrieved via cellular. If there are 532 * multiple SIM's, the country code chosen is non-deterministic if they return different codes. 533 * 3. Wifi country code - Current country code retrieved via wifi (via 80211.ad). 534 * 4. Last known telephony country code - Last known country code retrieved via cellular. If 535 * there are multiple SIM's, the country code chosen is non-deterministic if they return 536 * different codes. 537 * 5. Location Country code - If enabled, country code retrieved from LocationManager Fused 538 * location provider. 539 * 6. Cached Country code - If enabled (only enabled on non-phone form factors), uses last 540 * valid country code retrieved from any of the sources above (cache cleared on APM mode 541 * toggle). 542 * 7. OEM default country code - If set by the OEM, then we default to this country code. 543 * @return 544 */ pickCountryCode()545 private String pickCountryCode() { 546 if (mOverrideCountryCode != null) { 547 return mOverrideCountryCode; 548 } 549 if (mIsMccMncOemOverrideEnabled) { 550 return mUwbInjector.getOemDefaultCountryCode(); 551 } 552 if (mTelephonyCountryCodeInfoPerSlot != null) { 553 for (TelephonyCountryCodeSlotInfo telephonyCountryCodeInfoSlot : 554 mTelephonyCountryCodeInfoPerSlot.values()) { 555 if (telephonyCountryCodeInfoSlot != null 556 && telephonyCountryCodeInfoSlot.countryCode != null) { 557 return telephonyCountryCodeInfoSlot.countryCode; 558 } 559 } 560 } 561 if (mWifiCountryCode != null) { 562 return mWifiCountryCode; 563 } 564 if (mTelephonyCountryCodeInfoPerSlot != null) { 565 for (TelephonyCountryCodeSlotInfo telephonyCountryCodeInfoSlot : 566 mTelephonyCountryCodeInfoPerSlot.values()) { 567 if (telephonyCountryCodeInfoSlot != null 568 && telephonyCountryCodeInfoSlot.lastKnownCountryCode != null) { 569 return telephonyCountryCodeInfoSlot.lastKnownCountryCode; 570 } 571 } 572 } 573 if (mLocationCountryCode != null) { 574 return mLocationCountryCode; 575 } 576 if (mUwbInjector.getDeviceConfigFacade().isPersistentCacheUseForCountryCodeEnabled() 577 && mCachedCountryCode != null) { 578 Log.d(TAG, "Using cached country code"); 579 return mCachedCountryCode; 580 } 581 return mUwbInjector.getOemDefaultCountryCode(); 582 } 583 584 /** 585 * Set country code 586 * 587 * @param forceUpdate Force update the country code even if it was the same as previously cached 588 * value. 589 * @return Pair<UWBS StatusCode from setting the country code, 590 * Country code that was attempted to be set in UWBS> 591 */ setCountryCode(boolean forceUpdate)592 public Pair<Integer, String> setCountryCode(boolean forceUpdate) { 593 String country = pickCountryCode(); 594 if (country == null) { 595 Log.i(TAG, "No valid country code, reset to " + DEFAULT_COUNTRY_CODE); 596 country = DEFAULT_COUNTRY_CODE; 597 } 598 if (isValid(country)) { 599 stopFusedLocationManager(); 600 stopRetryRequest(); 601 } 602 if (!forceUpdate && Objects.equals(country, mCountryCode)) { 603 Log.i(TAG, "Ignoring already set country code: " + country); 604 return new Pair<>(STATUS_CODE_OK, mCountryCode); 605 } 606 Log.d(TAG, "setCountryCode to " + country); 607 int status = mNativeUwbManager.setCountryCode(country.getBytes(StandardCharsets.UTF_8)); 608 if (status != STATUS_CODE_OK) { 609 Log.i(TAG, "Failed to set country code, with status code: " + status); 610 } 611 mCountryCode = country; 612 mCountryCodeUpdatedTimestamp = LocalDateTime.now().format(FORMATTER); 613 mCountryCodeStatus = Optional.of(status); 614 // Cache the country code (if caching is enabled on the device) 615 if (mUwbInjector.getDeviceConfigFacade().isPersistentCacheUseForCountryCodeEnabled() 616 && isValid(country)) { 617 mCachedCountryCode = country; 618 mUwbInjector.getUwbSettingsStore().put( 619 UwbSettingsStore.SETTINGS_CACHED_COUNTRY_CODE, country); 620 } 621 622 for (CountryCodeChangedListener listener : mListeners) { 623 listener.onCountryCodeChanged(status, country); 624 } 625 return new Pair<>(status, country); 626 } 627 628 /** 629 * Get country code. 630 * 631 * @return the country code that was last configured in the UWBS. 632 */ getCountryCode()633 public String getCountryCode() { 634 return mCountryCode; 635 } 636 637 /** 638 * Get country code configuration status. 639 * 640 * @return Status of the last attempt to configure a country code in the UWBS. 641 */ getCountryCodeStatus()642 public Optional<Integer> getCountryCodeStatus() { 643 return mCountryCodeStatus; 644 } 645 646 /** 647 * Is this a valid country code 648 * @param countryCode A 2-Character alphanumeric country code. 649 * @return true if the countryCode is valid, false otherwise. 650 */ isValid(String countryCode)651 public static boolean isValid(String countryCode) { 652 return countryCode != null && countryCode.length() == 2 653 && !countryCode.equals(DEFAULT_COUNTRY_CODE) 654 && countryCode.chars().allMatch(Character::isLetter); 655 } 656 657 /** 658 * This call will override any existing country code. 659 * This is for test purpose only and we should disallow any update from 660 * telephony in this mode. 661 * @param countryCode A 2-Character alphanumeric country code. 662 */ setOverrideCountryCode(String countryCode)663 public synchronized void setOverrideCountryCode(String countryCode) { 664 if (TextUtils.isEmpty(countryCode)) { 665 Log.d(TAG, "Fail to override country code because" 666 + "the received country code is empty"); 667 return; 668 } 669 mOverrideCountryCode = countryCode.toUpperCase(Locale.US); 670 setCountryCode(true); 671 } 672 673 /** 674 * This is for clearing the country code previously set through #setOverrideCountryCode() method 675 */ clearOverrideCountryCode()676 public synchronized void clearOverrideCountryCode() { 677 mOverrideCountryCode = null; 678 setCountryCode(true); 679 } 680 681 /** 682 * This is for clearing the cached country code when Airplane mode is toggled 683 */ clearCachedCountryCode()684 public synchronized void clearCachedCountryCode() { 685 if (mUwbInjector.getDeviceConfigFacade().isPersistentCacheUseForCountryCodeEnabled()) { 686 Log.d(TAG, "Clearing cached country code"); 687 mCachedCountryCode = null; 688 mUwbInjector.getUwbSettingsStore().put( 689 UwbSettingsStore.SETTINGS_CACHED_COUNTRY_CODE, ""); 690 } 691 if (mUwbInjector.getGlobalSettingsInt(Settings.Global.AIRPLANE_MODE_ON, 0) == 1) { 692 stopFusedLocationManager(); 693 } else { 694 startFusedLocationManager(); 695 } 696 } 697 698 /** 699 * Method to dump the current state of this UwbCountryCode object. 700 */ dump(FileDescriptor fd, PrintWriter pw, String[] args)701 public synchronized void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 702 pw.println("---- Dump of UwbCountryCode ----"); 703 pw.println("DefaultCountryCode(system property): " 704 + mUwbInjector.getOemDefaultCountryCode()); 705 pw.println("mOverrideCountryCode: " + mOverrideCountryCode); 706 pw.println("mTelephonyCountryCodeInfoSlot: " + mTelephonyCountryCodeInfoPerSlot); 707 pw.println("mWifiCountryCode: " + mWifiCountryCode); 708 pw.println("mWifiCountryTimestamp: " + mWifiCountryTimestamp); 709 pw.println("mLocationCountryCode: " + mLocationCountryCode); 710 pw.println("mLocationCountryTimestamp: " + mLocationCountryTimestamp); 711 pw.println("mCountryCode: " + mCountryCode); 712 pw.println("mCountryCodeStatus: " 713 + (mCountryCodeStatus.isEmpty() ? "none" : mCountryCodeStatus.get())); 714 pw.println("mCountryCodeUpdatedTimestamp: " + mCountryCodeUpdatedTimestamp); 715 pw.println("mCachedCountryCode: " + mCachedCountryCode); 716 pw.println("---- Dump of UwbCountryCode ----"); 717 } 718 } 719