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