1 /* 2 * Copyright (C) 2016 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.wifi; 18 19 import static com.android.server.wifi.WifiSettingsConfigStore.WIFI_DEFAULT_COUNTRY_CODE; 20 21 import android.annotation.NonNull; 22 import android.annotation.Nullable; 23 import android.content.Context; 24 import android.net.wifi.WifiContext; 25 import android.net.wifi.WifiInfo; 26 import android.net.wifi.util.WifiResourceCache; 27 import android.os.SystemProperties; 28 import android.telephony.TelephonyManager; 29 import android.text.TextUtils; 30 import android.util.ArrayMap; 31 import android.util.Log; 32 33 import androidx.annotation.Keep; 34 35 import com.android.modules.utils.build.SdkLevel; 36 import com.android.server.wifi.hotspot2.NetworkDetail; 37 import com.android.server.wifi.p2p.WifiP2pMetrics; 38 import com.android.server.wifi.util.ApConfigUtil; 39 import com.android.server.wifi.util.WifiPermissionsUtil; 40 import com.android.wifi.resources.R; 41 42 import java.io.FileDescriptor; 43 import java.io.PrintWriter; 44 import java.text.SimpleDateFormat; 45 import java.util.ArrayList; 46 import java.util.Date; 47 import java.util.List; 48 import java.util.Locale; 49 import java.util.Map; 50 import java.util.Set; 51 52 /** 53 * Provide functions for making changes to WiFi country code. 54 * This Country Code is from MCC or phone default setting. This class sends Country Code 55 * to driver through wpa_supplicant when ClientModeImpl marks current state as ready 56 * using setReadyForChange(true). 57 */ 58 public class WifiCountryCode { 59 private static final String TAG = "WifiCountryCode"; 60 private static final String BOOT_DEFAULT_WIFI_COUNTRY_CODE = "ro.boot.wificountrycode"; 61 private static final int PKT_COUNT_HIGH_PKT_PER_SEC = 16; 62 private static final int DISCONNECT_WIFI_COUNT_MAX = 1; 63 /* TODO: replace with PackageManager.FEATURE_TELEPHONY_CALLING once 64 * wifi-module-sdk-version-defaults min_sdk_version bumps to API 33. */ 65 private static final String FEATURE_TELEPHONY_CALLING = "android.hardware.telephony.calling"; 66 static final int MIN_COUNTRY_CODE_COUNT_US = 3; 67 static final int MIN_COUNTRY_CODE_COUNT_OTHER = 2; 68 static final String COUNTRY_CODE_US = "US"; 69 static final int MAX_DURATION_SINCE_LAST_UPDATE_TIME_MS = 500_000; 70 static final int MIN_SCAN_RSSI_DBM = -85; 71 private final String mWorldModeCountryCode; 72 private final WifiContext mContext; 73 private final TelephonyManager mTelephonyManager; 74 private final ActiveModeWarden mActiveModeWarden; 75 private final WifiP2pMetrics mWifiP2pMetrics; 76 private final WifiNative mWifiNative; 77 private final WifiSettingsConfigStore mSettingsConfigStore; 78 private final Clock mClock; 79 private final WifiPermissionsUtil mWifiPermissionsUtil; 80 private final WifiCarrierInfoManager mWifiCarrierInfoManager; 81 private final WifiResourceCache mResourceCache; 82 private List<ChangeListener> mListeners = new ArrayList<>(); 83 private boolean mVerboseLoggingEnabled = false; 84 private boolean mIsCountryCodePendingToUpdateToCmm = true; // default to true for first update. 85 /** 86 * Map of active ClientModeManager instance to whether it is ready for country code change. 87 * 88 * - When a new ClientModeManager instance is created, it is added to this map and starts out 89 * ready for any country code changes (value = true). 90 * - When the ClientModeManager instance starts a connection attempt, it is marked not ready for 91 * country code changes (value = false). 92 * - When the ClientModeManager instance ends the connection, it is again marked ready for 93 * country code changes (value = true). 94 * - When the ClientModeManager instance is destroyed, it is removed from this map. 95 */ 96 private final Map<ActiveModeManager, Boolean> mAmmToReadyForChangeMap = 97 new ArrayMap<>(); 98 private static final SimpleDateFormat FORMATTER = new SimpleDateFormat("MM-dd HH:mm:ss.SSS"); 99 100 private String mTelephonyCountryCode = null; 101 private String mOverrideCountryCode = null; 102 private String mDriverCountryCode = null; 103 private String mFrameworkCountryCode = null; 104 private String mLastReceivedActiveDriverCountryCode = null; 105 private long mDriverCountryCodeUpdatedTimestamp = 0; 106 private String mTelephonyCountryTimestamp = null; 107 private long mFrameworkCountryCodeUpdatedTimestamp = 0; 108 private String mAllCmmReadyTimestamp = null; 109 private int mDisconnectWifiToForceUpdateCount = 0; 110 111 private class ModeChangeCallbackInternal implements ActiveModeWarden.ModeChangeCallback { 112 @Override onActiveModeManagerAdded(@onNull ActiveModeManager activeModeManager)113 public void onActiveModeManagerAdded(@NonNull ActiveModeManager activeModeManager) { 114 if (activeModeManager.getRole() instanceof ActiveModeManager.ClientRole) { 115 // Add this CMM for tracking. Interface is up and HAL is initialized at this point. 116 // If this device runs the 1.5 HAL version, use the IWifiChip.setCountryCode() 117 // to set the country code. 118 mAmmToReadyForChangeMap.put(activeModeManager, true); 119 evaluateAllCmmStateAndApplyIfAllReady(); 120 } else if (activeModeManager instanceof SoftApManager) { 121 // Put SoftApManager ready for consistence behavior in mAmmToReadyForChangeMap. 122 // No need to trigger CC change because SoftApManager takes CC when starting up. 123 mAmmToReadyForChangeMap.put(activeModeManager, true); 124 } 125 } 126 127 @Override onActiveModeManagerRemoved(@onNull ActiveModeManager activeModeManager)128 public void onActiveModeManagerRemoved(@NonNull ActiveModeManager activeModeManager) { 129 if (mAmmToReadyForChangeMap.remove(activeModeManager) != null) { 130 if (activeModeManager instanceof ActiveModeManager.ClientRole) { 131 // Remove this CMM from tracking. 132 evaluateAllCmmStateAndApplyIfAllReady(); 133 } 134 } 135 if (mAmmToReadyForChangeMap.size() == 0) { 136 handleCountryCodeChanged(null); 137 Log.i(TAG, "No active mode, call onDriverCountryCodeChanged with Null"); 138 } 139 } 140 141 @Override onActiveModeManagerRoleChanged(@onNull ActiveModeManager activeModeManager)142 public void onActiveModeManagerRoleChanged(@NonNull ActiveModeManager activeModeManager) { 143 if (activeModeManager.getRole() == ActiveModeManager.ROLE_CLIENT_PRIMARY) { 144 // Set this CMM ready for change. This is needed to handle the transition from 145 // ROLE_CLIENT_SCAN_ONLY to ROLE_CLIENT_PRIMARY on devices running older HAL 146 // versions (since the IWifiChip.setCountryCode() was only added in the 1.5 HAL 147 // version, before that we need to wait till supplicant is up for country code 148 // change. 149 mAmmToReadyForChangeMap.put(activeModeManager, true); 150 evaluateAllCmmStateAndApplyIfAllReady(); 151 } 152 } 153 } 154 155 private class ClientModeListenerInternal implements ClientModeImplListener { 156 @Override onConnectionStart(@onNull ConcreteClientModeManager clientModeManager)157 public void onConnectionStart(@NonNull ConcreteClientModeManager clientModeManager) { 158 if (mAmmToReadyForChangeMap.get(clientModeManager) == null) { 159 Log.wtf(TAG, "Connection start received from unknown client mode manager"); 160 } 161 // connection start. CMM not ready for country code change. 162 mAmmToReadyForChangeMap.put(clientModeManager, false); 163 evaluateAllCmmStateAndApplyIfAllReady(); 164 } 165 166 @Override onConnectionEnd(@onNull ConcreteClientModeManager clientModeManager)167 public void onConnectionEnd(@NonNull ConcreteClientModeManager clientModeManager) { 168 if (mAmmToReadyForChangeMap.get(clientModeManager) == null) { 169 Log.wtf(TAG, "Connection end received from unknown client mode manager"); 170 } 171 // connection end. CMM ready for country code change. 172 mAmmToReadyForChangeMap.put(clientModeManager, true); 173 evaluateAllCmmStateAndApplyIfAllReady(); 174 } 175 176 } 177 178 private class CountryChangeListenerInternal implements ChangeListener { 179 @Override onDriverCountryCodeChanged(String country)180 public void onDriverCountryCodeChanged(String country) { 181 Log.i(TAG, "Receive onDriverCountryCodeChanged " + country); 182 mLastReceivedActiveDriverCountryCode = country; 183 // Before T build, always handle country code changed. 184 if (!SdkLevel.isAtLeastT() || isDriverSupportedRegChangedEvent()) { 185 // CC doesn't notify listener after sending to the driver, notify the listener 186 // after we received CC changed event. 187 handleCountryCodeChanged(country); 188 } 189 } 190 191 @Override onSetCountryCodeSucceeded(String country)192 public void onSetCountryCodeSucceeded(String country) { 193 Log.i(TAG, "Receive onSetCountryCodeSucceeded " + country); 194 // The country code callback might not be triggered even if the driver supports reg 195 // changed event when the maintained country code in the driver is same as last one. 196 // So notify the country code changed event to listener when the set one is same as 197 // last received one. 198 if (!SdkLevel.isAtLeastT() || !isDriverSupportedRegChangedEvent() 199 || TextUtils.equals(country, mLastReceivedActiveDriverCountryCode)) { 200 mWifiNative.countryCodeChanged(country); 201 handleCountryCodeChanged(country); 202 } 203 } 204 } 205 WifiCountryCode( WifiContext context, ActiveModeWarden activeModeWarden, WifiP2pMetrics wifiP2pMetrics, ClientModeImplMonitor clientModeImplMonitor, WifiNative wifiNative, @NonNull WifiSettingsConfigStore settingsConfigStore, Clock clock, WifiPermissionsUtil wifiPermissionsUtil, @NonNull WifiCarrierInfoManager wifiCarrierInfoManager)206 public WifiCountryCode( 207 WifiContext context, 208 ActiveModeWarden activeModeWarden, 209 WifiP2pMetrics wifiP2pMetrics, 210 ClientModeImplMonitor clientModeImplMonitor, 211 WifiNative wifiNative, 212 @NonNull WifiSettingsConfigStore settingsConfigStore, 213 Clock clock, 214 WifiPermissionsUtil wifiPermissionsUtil, 215 @NonNull WifiCarrierInfoManager wifiCarrierInfoManager) { 216 mContext = context; 217 mTelephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); 218 mActiveModeWarden = activeModeWarden; 219 mWifiP2pMetrics = wifiP2pMetrics; 220 mWifiNative = wifiNative; 221 mSettingsConfigStore = settingsConfigStore; 222 mClock = clock; 223 mWifiPermissionsUtil = wifiPermissionsUtil; 224 mWifiCarrierInfoManager = wifiCarrierInfoManager; 225 mResourceCache = mContext.getResourceCache(); 226 227 mActiveModeWarden.registerModeChangeCallback(new ModeChangeCallbackInternal()); 228 clientModeImplMonitor.registerListener(new ClientModeListenerInternal()); 229 mWifiNative.registerCountryCodeEventListener(new CountryChangeListenerInternal()); 230 231 mWorldModeCountryCode = mResourceCache 232 .getString(R.string.config_wifiDriverWorldModeCountryCode); 233 234 Log.d(TAG, "Default country code from system property " 235 + BOOT_DEFAULT_WIFI_COUNTRY_CODE + " is " + getOemDefaultCountryCode()); 236 } 237 238 /** 239 * Default country code stored in system property 240 * @return Country code if available, null otherwise. 241 */ getOemDefaultCountryCode()242 public static String getOemDefaultCountryCode() { 243 String country = SystemProperties.get(BOOT_DEFAULT_WIFI_COUNTRY_CODE); 244 return WifiCountryCode.isValid(country) ? country.toUpperCase(Locale.US) : null; 245 } 246 247 /** 248 * Is this a valid country code 249 * @param countryCode A 2-Character alphanumeric country code. 250 * @return true if the countryCode is valid, false otherwise. 251 */ isValid(String countryCode)252 public static boolean isValid(String countryCode) { 253 return countryCode != null && countryCode.length() == 2 254 && countryCode.chars().allMatch(Character::isLetterOrDigit); 255 } 256 257 /** 258 * The class for country code related change listener 259 */ 260 public interface ChangeListener { 261 /** 262 * Called when receiving new country code change pending. 263 */ onCountryCodeChangePending(@onNull String countryCode)264 default void onCountryCodeChangePending(@NonNull String countryCode) {}; 265 266 /** 267 * Called when receiving country code changed from driver. 268 */ onDriverCountryCodeChanged(String countryCode)269 void onDriverCountryCodeChanged(String countryCode); 270 271 /** 272 * Called when country code set to native layer successful, framework sends event to 273 * force country code changed. 274 * 275 * Reason: The country code change listener from wificond rely on driver supported 276 * NL80211_CMD_REG_CHANGE/NL80211_CMD_WIPHY_REG_CHANGE. Trigger update country code 277 * to listener here for non-supported platform. 278 */ onSetCountryCodeSucceeded(String country)279 default void onSetCountryCodeSucceeded(String country) {} 280 } 281 282 283 /** 284 * Register Country code changed listener. 285 */ registerListener(@onNull ChangeListener listener)286 public void registerListener(@NonNull ChangeListener listener) { 287 mListeners.add(listener); 288 /** 289 * Always called with mDriverCountryCode even if the SDK version is lower than T. 290 * Reason: Before android S, the purpose of the internal listener is updating the supported 291 * channels, it always depends on mDriverCountryCode. 292 */ 293 if (mDriverCountryCode != null) { 294 listener.onDriverCountryCodeChanged(mDriverCountryCode); 295 } 296 } 297 298 /** 299 * Unregister Country code changed listener. 300 */ unregisterListener(@onNull ChangeListener listener)301 public void unregisterListener(@NonNull ChangeListener listener) { 302 mListeners.remove(listener); 303 } 304 305 /** 306 * Enable verbose logging for WifiCountryCode. 307 */ enableVerboseLogging(boolean verbose)308 public void enableVerboseLogging(boolean verbose) { 309 mVerboseLoggingEnabled = verbose; 310 } 311 hasCalling()312 private boolean hasCalling() { 313 return mContext.getPackageManager().hasSystemFeature(FEATURE_TELEPHONY_CALLING); 314 } 315 initializeTelephonyCountryCodeIfNeeded()316 private void initializeTelephonyCountryCodeIfNeeded() { 317 // If we don't have telephony country code set yet, poll it. 318 if (mTelephonyCountryCode == null) { 319 Log.d(TAG, "Reading country code from telephony"); 320 setTelephonyCountryCode(mTelephonyManager.getNetworkCountryIso()); 321 } 322 } 323 324 /** 325 * We call native code to request country code changes only if all {@link ClientModeManager} 326 * instances are ready for country code change. Country code is a chip level configuration and 327 * results in all the connections on the chip being disrupted. 328 * 329 * @return true if there are active CMM's and all are ready for country code change. 330 */ isAllCmmReady()331 private boolean isAllCmmReady() { 332 boolean isAnyCmmExist = false; 333 for (ActiveModeManager am : mAmmToReadyForChangeMap.keySet()) { 334 if (am instanceof ConcreteClientModeManager) { 335 isAnyCmmExist = true; 336 if (!mAmmToReadyForChangeMap.get(am)) { 337 return false; 338 } 339 } 340 } 341 return isAnyCmmExist; 342 } 343 344 /** 345 * Check all active CMM instances and apply country code change if ready. 346 */ evaluateAllCmmStateAndApplyIfAllReady()347 private void evaluateAllCmmStateAndApplyIfAllReady() { 348 Log.d(TAG, "evaluateAllCmmStateAndApplyIfAllReady: " + mAmmToReadyForChangeMap); 349 if (isAllCmmReady() && mIsCountryCodePendingToUpdateToCmm) { 350 mAllCmmReadyTimestamp = FORMATTER.format(new Date(mClock.getWallClockMillis())); 351 // We are ready to set country code now. 352 // We need to post pending country code request. 353 initializeTelephonyCountryCodeIfNeeded(); 354 updateCountryCode(true); 355 } 356 } 357 358 /** 359 * This call will override any existing country code. 360 * This is for test purpose only and we should disallow any update from 361 * telephony in this mode. 362 * @param countryCode A 2-Character alphanumeric country code. 363 */ setOverrideCountryCode(String countryCode)364 public synchronized void setOverrideCountryCode(String countryCode) { 365 if (TextUtils.isEmpty(countryCode)) { 366 Log.d(TAG, "Fail to override country code because" 367 + "the received country code is empty"); 368 return; 369 } 370 // Support 00 map to device world mode country code 371 if (TextUtils.equals("00", countryCode)) { 372 countryCode = mWorldModeCountryCode; 373 } 374 mOverrideCountryCode = countryCode.toUpperCase(Locale.US); 375 updateCountryCode(false); 376 } 377 378 /** 379 * This is for clearing the country code previously set through #setOverrideCountryCode() method 380 */ clearOverrideCountryCode()381 public synchronized void clearOverrideCountryCode() { 382 mOverrideCountryCode = null; 383 updateCountryCode(false); 384 } 385 setTelephonyCountryCode(String countryCode)386 private void setTelephonyCountryCode(String countryCode) { 387 Log.d(TAG, "Set telephony country code to: " + countryCode); 388 mTelephonyCountryTimestamp = FORMATTER.format(new Date(mClock.getWallClockMillis())); 389 390 // Empty country code. 391 if (TextUtils.isEmpty(countryCode)) { 392 if (mResourceCache 393 .getBoolean(R.bool.config_wifi_revert_country_code_on_cellular_loss)) { 394 Log.d(TAG, "Received empty country code, reset to default country code"); 395 mTelephonyCountryCode = null; 396 } 397 } else { 398 mTelephonyCountryCode = countryCode.toUpperCase(Locale.US); 399 } 400 } 401 402 /** 403 * Handle telephony country code change request. 404 * @param countryCode The country code intended to set. 405 * This is supposed to be from Telephony service. 406 * otherwise we think it is from other applications. 407 * @return Returns true if the country code passed in is acceptable and passed to the driver. 408 */ setTelephonyCountryCodeAndUpdate(String countryCode)409 public boolean setTelephonyCountryCodeAndUpdate(String countryCode) { 410 if (TextUtils.isEmpty(countryCode) 411 && !TextUtils.isEmpty(mTelephonyManager.getNetworkCountryIso())) { 412 Log.i(TAG, "Skip Telephony CC update to empty because there is " 413 + "an available CC from default active SIM"); 414 return false; 415 } 416 // We do not check if the country code (CC) equals the current one because 417 // 1. Wpa supplicant may silently modify the country code. 418 // 2. If Wifi restarted therefore wpa_supplicant also restarted, 419 setTelephonyCountryCode(countryCode); 420 if (mOverrideCountryCode != null) { 421 Log.d(TAG, "Skip Telephony CC update due to override country code set"); 422 return false; 423 } 424 425 updateCountryCode(false); 426 return true; 427 } 428 429 /** 430 * Update country code from scan results 431 * Note the derived country code is used only if all following conditions are met 432 * 1) There is no telephony country code 433 * 2) The current driver country code is empty or equal to the worldwide code 434 * 3) Currently the device is disconnected 435 * @param scanDetails Wifi scan results 436 */ updateCountryCodeFromScanResults(@onNull List<ScanDetail> scanDetails)437 public void updateCountryCodeFromScanResults(@NonNull List<ScanDetail> scanDetails) { 438 if (mTelephonyCountryCode != null) { 439 return; 440 } 441 442 if (!isCcUpdateGenericEnabled()) { 443 return; 444 } 445 446 String countryCode = findCountryCodeFromScanResults(scanDetails); 447 if (countryCode == null) { 448 Log.i(TAG, "Skip framework CC update because it is empty"); 449 return; 450 } 451 if (countryCode.equalsIgnoreCase(mFrameworkCountryCode)) { 452 return; 453 } 454 455 mFrameworkCountryCodeUpdatedTimestamp = mClock.getWallClockMillis(); 456 mFrameworkCountryCode = countryCode; 457 if (mOverrideCountryCode != null) { 458 Log.d(TAG, "Skip framework CC update due to override country code set"); 459 return; 460 } 461 462 updateCountryCode(false); 463 } 464 isCcUpdateGenericEnabled()465 private boolean isCcUpdateGenericEnabled() { 466 return mResourceCache.getBoolean( 467 R.bool.config_wifiUpdateCountryCodeFromScanResultGeneric); 468 } 469 findCountryCodeFromScanResults(List<ScanDetail> scanDetails)470 private String findCountryCodeFromScanResults(List<ScanDetail> scanDetails) { 471 String selectedCountryCode = null; 472 int count = 0; 473 for (ScanDetail scanDetail : scanDetails) { 474 NetworkDetail networkDetail = scanDetail.getNetworkDetail(); 475 String countryCode = networkDetail.getCountryCode(); 476 if (scanDetail.getScanResult().level < MIN_SCAN_RSSI_DBM) { 477 continue; 478 } 479 if (countryCode == null || TextUtils.isEmpty(countryCode)) { 480 continue; 481 } 482 if (selectedCountryCode == null) { 483 selectedCountryCode = countryCode; 484 } 485 if (!selectedCountryCode.equalsIgnoreCase(countryCode)) { 486 if (mVerboseLoggingEnabled) { 487 Log.d(TAG, "CC doesn't match"); 488 } 489 return null; 490 } 491 count++; 492 } 493 if (mVerboseLoggingEnabled) { 494 Log.d(TAG, selectedCountryCode + " " + count); 495 } 496 if (count == 0) { 497 return null; 498 } 499 int min_count = selectedCountryCode.equalsIgnoreCase(COUNTRY_CODE_US) 500 ? MIN_COUNTRY_CODE_COUNT_US : MIN_COUNTRY_CODE_COUNT_OTHER; 501 return (count >= min_count) ? selectedCountryCode : null; 502 } 503 disconnectWifiToForceUpdateIfNeeded()504 private void disconnectWifiToForceUpdateIfNeeded() { 505 if (shouldDisconnectWifiToForceUpdate()) { 506 Log.d(TAG, "Disconnect wifi to force update"); 507 for (ClientModeManager cmm : 508 mActiveModeWarden.getInternetConnectivityClientModeManagers()) { 509 if (!cmm.isConnected()) { 510 continue; 511 } 512 cmm.disconnect(); 513 } 514 mDisconnectWifiToForceUpdateCount++; 515 } 516 } 517 shouldDisconnectWifiToForceUpdate()518 private boolean shouldDisconnectWifiToForceUpdate() { 519 if (hasCalling() && mWifiCarrierInfoManager.isWifiCallingAvailable()) { 520 return false; 521 } 522 523 if (mTelephonyCountryCode == null 524 || mTelephonyCountryCode.equals(mDriverCountryCode)) { 525 return false; 526 } 527 528 if (mDisconnectWifiToForceUpdateCount >= DISCONNECT_WIFI_COUNT_MAX) { 529 return false; 530 } 531 532 if (mDriverCountryCode != null 533 && !mDriverCountryCode.equalsIgnoreCase(mWorldModeCountryCode)) { 534 return false; 535 } 536 537 for (ClientModeManager cmm : 538 mActiveModeWarden.getInternetConnectivityClientModeManagers()) { 539 if (!cmm.isConnected()) { 540 continue; 541 } 542 WifiInfo wifiInfo = cmm.getConnectionInfo(); 543 if (wifiInfo.getSuccessfulTxPacketsPerSecond() < PKT_COUNT_HIGH_PKT_PER_SEC 544 && wifiInfo.getSuccessfulRxPacketsPerSecond() < PKT_COUNT_HIGH_PKT_PER_SEC) { 545 return true; 546 } 547 } 548 return false; 549 } 550 551 /** 552 * Method to get the received driver Country Code that being used in driver. 553 * 554 * @return Returns the local copy of the received driver Country Code or null if 555 * there is no Country Code was received from driver or no any active mode. 556 */ 557 @Nullable getCurrentDriverCountryCode()558 public synchronized String getCurrentDriverCountryCode() { 559 return mDriverCountryCode; 560 } 561 562 /** 563 * Method to return the currently reported Country Code resolved from various sources: 564 * e.g. default country code, cellular network country code, country code override, etc. 565 * 566 * @return The current Wifi Country Code resolved from various sources. Returns null when there 567 * is no Country Code available. 568 */ 569 @Keep 570 @Nullable getCountryCode()571 public synchronized String getCountryCode() { 572 initializeTelephonyCountryCodeIfNeeded(); 573 return pickCountryCode(true); 574 } 575 576 /** 577 * set default country code 578 * @param countryCode A 2-Character alphanumeric country code. 579 */ setDefaultCountryCode(String countryCode)580 public synchronized void setDefaultCountryCode(String countryCode) { 581 if (TextUtils.isEmpty(countryCode)) { 582 Log.d(TAG, "Fail to set default country code because the country code is empty"); 583 return; 584 } 585 586 mSettingsConfigStore.put(WIFI_DEFAULT_COUNTRY_CODE, 587 countryCode.toUpperCase(Locale.US)); 588 Log.i(TAG, "Default country code updated in config store: " + countryCode); 589 updateCountryCode(false); 590 } 591 592 /** 593 * Method to dump the current state of this WifiCountryCode object. 594 */ dump(FileDescriptor fd, PrintWriter pw, String[] args)595 public synchronized void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 596 pw.println("mRevertCountryCodeOnCellularLoss: " 597 + mResourceCache.getBoolean( 598 R.bool.config_wifi_revert_country_code_on_cellular_loss)); 599 pw.println("DefaultCountryCode(system property): " + getOemDefaultCountryCode()); 600 pw.println("DefaultCountryCode(config store): " 601 + mSettingsConfigStore.get(WIFI_DEFAULT_COUNTRY_CODE)); 602 pw.println("mTelephonyCountryCode: " + mTelephonyCountryCode); 603 pw.println("mTelephonyCountryTimestamp: " + mTelephonyCountryTimestamp); 604 pw.println("mOverrideCountryCode: " + mOverrideCountryCode); 605 pw.println("mAllCmmReadyTimestamp: " + mAllCmmReadyTimestamp); 606 pw.println("isAllCmmReady: " + isAllCmmReady()); 607 pw.println("mAmmToReadyForChangeMap: " + mAmmToReadyForChangeMap); 608 pw.println("mDisconnectWifiToForceUpdateCount: " + mDisconnectWifiToForceUpdateCount); 609 pw.println("mDriverCountryCode: " + mDriverCountryCode); 610 pw.println("mDriverCountryCodeUpdatedTimestamp: " 611 + (mDriverCountryCodeUpdatedTimestamp != 0 612 ? FORMATTER.format(new Date(mDriverCountryCodeUpdatedTimestamp)) : "N/A")); 613 pw.println("mFrameworkCountryCode: " + mFrameworkCountryCode); 614 pw.println("mFrameworkCountryCodeUpdatedTimestamp: " 615 + (mFrameworkCountryCodeUpdatedTimestamp != 0 616 ? FORMATTER.format(new Date(mFrameworkCountryCodeUpdatedTimestamp)) : "N/A")); 617 pw.println("isDriverSupportedRegChangedEvent: " 618 + isDriverSupportedRegChangedEvent()); 619 } 620 isDriverSupportedRegChangedEvent()621 private boolean isDriverSupportedRegChangedEvent() { 622 return mResourceCache.getBoolean( 623 R.bool.config_wifiDriverSupportedNl80211RegChangedEvent); 624 } 625 updateCountryCode(boolean isClientModeOnly)626 private void updateCountryCode(boolean isClientModeOnly) { 627 // The mDriverCountryCode is the country code which is being used by driver now. 628 // It should not be a candidate for writing use case. 629 String country = pickCountryCode(false); 630 Log.d(TAG, "updateCountryCode to " + country); 631 632 // We do not check if the country code equals the current one. 633 // There are two reasons: 634 // 1. Wpa supplicant may silently modify the country code. 635 // 2. If Wifi restarted therefore wpa_supplicant also restarted, 636 // the country code could be reset to '00' by wpa_supplicant. 637 if (country != null) { 638 setCountryCodeNative(country, isClientModeOnly); 639 } 640 // We do not set country code if there is no candidate. This is reasonable 641 // because wpa_supplicant usually starts with an international safe country 642 // code setting: '00'. 643 } 644 645 /** 646 * Pick up country code base on country code we have. 647 * 648 * @param useDriverCountryCodeIfAvailable whether or not to use driver country code 649 * if available, and it is only for reporting purpose. 650 * @return country code base on the use case and current country code we have. 651 */ pickCountryCode(boolean useDriverCountryCodeIfAvailable)652 private String pickCountryCode(boolean useDriverCountryCodeIfAvailable) { 653 if (mOverrideCountryCode != null) { 654 return mOverrideCountryCode; 655 } 656 if (mTelephonyCountryCode != null) { 657 return mTelephonyCountryCode; 658 } 659 if (useDriverCountryCodeIfAvailable && mDriverCountryCode != null) { 660 // Returns driver country code since it may be different to WIFI_DEFAULT_COUNTRY_CODE 661 // when driver supported 802.11d. 662 return mDriverCountryCode; 663 } 664 if (mFrameworkCountryCode != null && isCcUpdateGenericEnabled()) { 665 return mFrameworkCountryCode; 666 } 667 return mSettingsConfigStore.get(WIFI_DEFAULT_COUNTRY_CODE); 668 } 669 setCountryCodeNative(String country, boolean isClientModeOnly)670 private boolean setCountryCodeNative(String country, boolean isClientModeOnly) { 671 Set<ActiveModeManager> amms = mAmmToReadyForChangeMap.keySet(); 672 boolean isConcreteClientModeManagerUpdated = false; 673 boolean anyAmmConfigured = false; 674 final boolean isNeedToUpdateCCToSta = mResourceCache 675 .getBoolean(R.bool.config_wifiStaDynamicCountryCodeUpdateSupported) 676 || isAllCmmReady(); 677 if (!isNeedToUpdateCCToSta) { 678 Log.d(TAG, "skip update supplicant not ready yet"); 679 disconnectWifiToForceUpdateIfNeeded(); 680 } 681 boolean isCountryCodeChanged = !TextUtils.equals(mDriverCountryCode, country); 682 Log.d(TAG, "setCountryCodeNative: " + country + ", isClientModeOnly: " + isClientModeOnly 683 + " mDriverCountryCode: " + mDriverCountryCode); 684 // We intend to change Country code, assume to pending to update for Cmm first. 685 mIsCountryCodePendingToUpdateToCmm = true; 686 for (ActiveModeManager am : amms) { 687 if (isNeedToUpdateCCToSta && !isConcreteClientModeManagerUpdated 688 && am instanceof ConcreteClientModeManager) { 689 // Set the country code using one of the active mode managers. Since 690 // country code is a chip level global setting, it can be set as long 691 // as there is at least one active interface to communicate to Wifi chip 692 ConcreteClientModeManager cm = (ConcreteClientModeManager) am; 693 if (!cm.setCountryCode(country)) { 694 Log.d(TAG, "Failed to set country code (ConcreteClientModeManager) to " 695 + country); 696 } else { 697 isConcreteClientModeManagerUpdated = true; 698 anyAmmConfigured = true; 699 // Start from S, frameworks support country code callback from wificond, 700 // move "notify the lister" to CountryChangeListenerInternal. 701 if (!SdkLevel.isAtLeastS() && !isDriverSupportedRegChangedEvent()) { 702 handleCountryCodeChanged(country); 703 } 704 // Country code was updated to cmmm succeeded, change pending to false. 705 mIsCountryCodePendingToUpdateToCmm = false; 706 } 707 } else if (!isClientModeOnly && am instanceof SoftApManager) { 708 SoftApManager sm = (SoftApManager) am; 709 if (mDriverCountryCode == null || !isCountryCodeChanged) { 710 // Ignore SoftApManager init country code case or country code didn't be 711 // changed case. 712 continue; 713 } 714 // Restart SAP if the overlay is enabled. 715 if (ApConfigUtil.isSoftApRestartRequiredWhenCountryCodeChanged(mContext)) { 716 Log.i(TAG, "restart SoftAp required because country code changed to " 717 + country); 718 SoftApModeConfiguration modeConfig = sm.getSoftApModeConfiguration(); 719 SoftApModeConfiguration newModeConfig = new SoftApModeConfiguration( 720 modeConfig.getTargetMode(), modeConfig.getSoftApConfiguration(), 721 modeConfig.getCapability(), country, modeConfig.getTetheringRequest()); 722 mActiveModeWarden.stopSoftAp(modeConfig.getTargetMode()); 723 mActiveModeWarden.startSoftAp(newModeConfig, sm.getRequestorWs()); 724 } else { 725 // The API:updateCountryCode in SoftApManager is asynchronous, it requires a 726 // new callback support in S to trigger "notifyListener" for 727 // the new S API: SoftApCapability#getSupportedChannelList(band). 728 // It requires: 729 // 1. a new overlay configuration which is introduced from S. 730 // 2. wificond support in S for S API: SoftApCapability#getSupportedChannelList 731 // Any case if device supported to set country code in R, 732 // the new S API: SoftApCapability#getSupportedChannelList(band) still doesn't 733 // work normally in R build when wifi disabled. 734 if (!sm.updateCountryCode(country)) { 735 Log.d(TAG, "Can't set country code (SoftApManager) to " 736 + country + " when SAP on (Device doesn't support runtime update)"); 737 } else { 738 anyAmmConfigured = true; 739 } 740 } 741 } 742 } 743 if (!anyAmmConfigured) { 744 for (ChangeListener listener : mListeners) { 745 if (country != null) { 746 listener.onCountryCodeChangePending(country); 747 } 748 } 749 } 750 return anyAmmConfigured; 751 } 752 handleCountryCodeChanged(String country)753 private void handleCountryCodeChanged(String country) { 754 mDriverCountryCodeUpdatedTimestamp = mClock.getWallClockMillis(); 755 mDriverCountryCode = country; 756 mWifiP2pMetrics.setIsCountryCodeWorldMode(isDriverCountryCodeWorldMode()); 757 notifyListener(country); 758 if (country == null) { 759 mIsCountryCodePendingToUpdateToCmm = true; 760 } 761 } 762 763 /** 764 * Method to check if current driver Country Code is in the world mode 765 */ isDriverCountryCodeWorldMode()766 public boolean isDriverCountryCodeWorldMode() { 767 if (mDriverCountryCode == null) { 768 return true; 769 } 770 return mDriverCountryCode.equalsIgnoreCase(mWorldModeCountryCode); 771 } 772 773 /** 774 * Notify the listeners. There are two kind of listeners 775 * 1. external listener, they only care what is country code which driver is using now. 776 * 2. internal listener, frameworks also only care what is country code which driver is using 777 * now because it requires to update supported channels with new country code. 778 * 779 * Note: Call this API only after confirming the CC is used in driver. 780 * 781 * @param country the country code is used in driver or null when driver is non-active. 782 */ notifyListener(@ullable String country)783 private void notifyListener(@Nullable String country) { 784 mActiveModeWarden.updateClientScanModeAfterCountryCodeUpdate(country); 785 for (ChangeListener listener : mListeners) { 786 listener.onDriverCountryCodeChanged(country); 787 } 788 } 789 } 790