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