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.os.SystemProperties; 25 import android.telephony.TelephonyManager; 26 import android.text.TextUtils; 27 import android.util.ArrayMap; 28 import android.util.Log; 29 30 import com.android.internal.annotations.VisibleForTesting; 31 import com.android.modules.utils.build.SdkLevel; 32 import com.android.wifi.resources.R; 33 34 import java.io.FileDescriptor; 35 import java.io.PrintWriter; 36 import java.text.SimpleDateFormat; 37 import java.util.ArrayList; 38 import java.util.Date; 39 import java.util.List; 40 import java.util.Locale; 41 import java.util.Map; 42 import java.util.Set; 43 44 /** 45 * Provide functions for making changes to WiFi country code. 46 * This Country Code is from MCC or phone default setting. This class sends Country Code 47 * to driver through wpa_supplicant when ClientModeImpl marks current state as ready 48 * using setReadyForChange(true). 49 */ 50 public class WifiCountryCode { 51 private static final String TAG = "WifiCountryCode"; 52 private static final String BOOT_DEFAULT_WIFI_COUNTRY_CODE = "ro.boot.wificountrycode"; 53 private final Context mContext; 54 private final TelephonyManager mTelephonyManager; 55 private final ActiveModeWarden mActiveModeWarden; 56 private final WifiNative mWifiNative; 57 private final WifiSettingsConfigStore mSettingsConfigStore; 58 private List<ChangeListener> mListeners = new ArrayList<>(); 59 private boolean DBG = false; 60 /** 61 * Map of active ClientModeManager instance to whether it is ready for country code change. 62 * 63 * - When a new ClientModeManager instance is created, it is added to this map and starts out 64 * ready for any country code changes (value = true). 65 * - When the ClientModeManager instance starts a connection attempt, it is marked not ready for 66 * country code changes (value = false). 67 * - When the ClientModeManager instance ends the connection, it is again marked ready for 68 * country code changes (value = true). 69 * - When the ClientModeManager instance is destroyed, it is removed from this map. 70 */ 71 private final Map<ActiveModeManager, Boolean> mAmmToReadyForChangeMap = 72 new ArrayMap<>(); 73 private static final SimpleDateFormat FORMATTER = new SimpleDateFormat("MM-dd HH:mm:ss.SSS"); 74 75 private String mTelephonyCountryCode = null; 76 private String mOverrideCountryCode = null; 77 private String mDriverCountryCode = null; 78 private String mReceivedDriverCountryCode = null; 79 private String mTelephonyCountryTimestamp = null; 80 private String mDriverCountryTimestamp = null; 81 private String mReadyTimestamp = null; 82 83 private class ModeChangeCallbackInternal implements ActiveModeWarden.ModeChangeCallback { 84 @Override onActiveModeManagerAdded(@onNull ActiveModeManager activeModeManager)85 public void onActiveModeManagerAdded(@NonNull ActiveModeManager activeModeManager) { 86 if (activeModeManager.getRole() instanceof ActiveModeManager.ClientRole 87 || activeModeManager instanceof SoftApManager) { 88 // Add this CMM for tracking. Interface is up and HAL is initialized at this point. 89 // If this device runs the 1.5 HAL version, use the IWifiChip.setCountryCode() 90 // to set the country code. 91 mAmmToReadyForChangeMap.put(activeModeManager, true); 92 evaluateAllCmmStateAndApplyIfAllReady(); 93 } 94 } 95 96 @Override onActiveModeManagerRemoved(@onNull ActiveModeManager activeModeManager)97 public void onActiveModeManagerRemoved(@NonNull ActiveModeManager activeModeManager) { 98 if (mAmmToReadyForChangeMap.remove(activeModeManager) != null) { 99 // Remove this CMM from tracking. 100 evaluateAllCmmStateAndApplyIfAllReady(); 101 } 102 } 103 104 @Override onActiveModeManagerRoleChanged(@onNull ActiveModeManager activeModeManager)105 public void onActiveModeManagerRoleChanged(@NonNull ActiveModeManager activeModeManager) { 106 if (activeModeManager.getRole() == ActiveModeManager.ROLE_CLIENT_PRIMARY) { 107 // Set this CMM ready for change. This is needed to handle the transition from 108 // ROLE_CLIENT_SCAN_ONLY to ROLE_CLIENT_PRIMARY on devices running older HAL 109 // versions (since the IWifiChip.setCountryCode() was only added in the 1.5 HAL 110 // version, before that we need to wait till supplicant is up for country code 111 // change. 112 mAmmToReadyForChangeMap.put(activeModeManager, true); 113 evaluateAllCmmStateAndApplyIfAllReady(); 114 } 115 } 116 } 117 118 private class ClientModeListenerInternal implements ClientModeImplListener { 119 @Override onConnectionStart(@onNull ConcreteClientModeManager clientModeManager)120 public void onConnectionStart(@NonNull ConcreteClientModeManager clientModeManager) { 121 if (mAmmToReadyForChangeMap.get(clientModeManager) == null) { 122 Log.wtf(TAG, "Connection start received from unknown client mode manager"); 123 } 124 // connection start. CMM not ready for country code change. 125 mAmmToReadyForChangeMap.put(clientModeManager, false); 126 evaluateAllCmmStateAndApplyIfAllReady(); 127 } 128 129 @Override onConnectionEnd(@onNull ConcreteClientModeManager clientModeManager)130 public void onConnectionEnd(@NonNull ConcreteClientModeManager clientModeManager) { 131 if (mAmmToReadyForChangeMap.get(clientModeManager) == null) { 132 Log.wtf(TAG, "Connection end received from unknown client mode manager"); 133 } 134 // connection end. CMM ready for country code change. 135 mAmmToReadyForChangeMap.put(clientModeManager, true); 136 evaluateAllCmmStateAndApplyIfAllReady(); 137 } 138 139 } 140 141 private class CountryChangeListenerInternal implements ChangeListener { 142 @Override onDriverCountryCodeChanged(String country)143 public void onDriverCountryCodeChanged(String country) { 144 if (TextUtils.equals(country, mReceivedDriverCountryCode)) { 145 return; 146 } 147 Log.i(TAG, "Receive onDriverCountryCodeChanged " + country); 148 mReceivedDriverCountryCode = country; 149 updateDriverCountryCodeAndNotifyListener(country); 150 } 151 152 @Override onSetCountryCodeSucceeded(String country)153 public void onSetCountryCodeSucceeded(String country) { 154 Log.i(TAG, "Receive onSetCountryCodeSucceeded " + country); 155 // The driver country code updated, don't need to trigger again. 156 if (TextUtils.equals(country, mReceivedDriverCountryCode)) { 157 return; 158 } 159 updateDriverCountryCodeAndNotifyListener(country); 160 } 161 } 162 WifiCountryCode( Context context, ActiveModeWarden activeModeWarden, ClientModeImplMonitor clientModeImplMonitor, WifiNative wifiNative, @NonNull WifiSettingsConfigStore settingsConfigStore)163 public WifiCountryCode( 164 Context context, 165 ActiveModeWarden activeModeWarden, 166 ClientModeImplMonitor clientModeImplMonitor, 167 WifiNative wifiNative, 168 @NonNull WifiSettingsConfigStore settingsConfigStore) { 169 mContext = context; 170 mTelephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); 171 mActiveModeWarden = activeModeWarden; 172 mWifiNative = wifiNative; 173 mSettingsConfigStore = settingsConfigStore; 174 175 mActiveModeWarden.registerModeChangeCallback(new ModeChangeCallbackInternal()); 176 clientModeImplMonitor.registerListener(new ClientModeListenerInternal()); 177 mWifiNative.registerCountryCodeEventListener(new CountryChangeListenerInternal()); 178 179 Log.d(TAG, "Default country code from system property " 180 + BOOT_DEFAULT_WIFI_COUNTRY_CODE + " is " + getOemDefaultCountryCode()); 181 } 182 183 /** 184 * Default country code stored in system property 185 * @return Country code if available, null otherwise. 186 */ getOemDefaultCountryCode()187 public static String getOemDefaultCountryCode() { 188 String country = SystemProperties.get(BOOT_DEFAULT_WIFI_COUNTRY_CODE); 189 return WifiCountryCode.isValid(country) ? country.toUpperCase(Locale.US) : null; 190 } 191 192 /** 193 * Is this a valid country code 194 * @param countryCode A 2-Character alphanumeric country code. 195 * @return true if the countryCode is valid, false otherwise. 196 */ isValid(String countryCode)197 public static boolean isValid(String countryCode) { 198 return countryCode != null && countryCode.length() == 2 199 && countryCode.chars().allMatch(Character::isLetterOrDigit); 200 } 201 202 /** 203 * The class for country code related change listener 204 */ 205 public interface ChangeListener { 206 /** 207 * Called when receiving country code changed from driver. 208 */ onDriverCountryCodeChanged(String countryCode)209 void onDriverCountryCodeChanged(String countryCode); 210 211 /** 212 * Called when country code set to native layer successful, framework sends event to 213 * force country code changed. 214 * 215 * Reason: The country code change listener from wificond rely on driver supported 216 * NL80211_CMD_REG_CHANGE/NL80211_CMD_WIPHY_REG_CHANGE. Trigger update country code 217 * to listener here for non-supported platform. 218 */ onSetCountryCodeSucceeded(String country)219 default void onSetCountryCodeSucceeded(String country) {} 220 } 221 222 /** 223 * Register Country code changed listener. 224 */ registerListener(@onNull ChangeListener listener)225 public void registerListener(@NonNull ChangeListener listener) { 226 mListeners.add(listener); 227 if (mDriverCountryCode != null) { 228 listener.onDriverCountryCodeChanged(mDriverCountryCode); 229 } 230 } 231 232 /** 233 * Enable verbose logging for WifiCountryCode. 234 */ enableVerboseLogging(boolean verbose)235 public void enableVerboseLogging(boolean verbose) { 236 DBG = verbose; 237 } 238 initializeTelephonyCountryCodeIfNeeded()239 private void initializeTelephonyCountryCodeIfNeeded() { 240 // If we don't have telephony country code set yet, poll it. 241 if (mTelephonyCountryCode == null) { 242 Log.d(TAG, "Reading country code from telephony"); 243 setTelephonyCountryCode(mTelephonyManager.getNetworkCountryIso()); 244 } 245 } 246 247 /** 248 * We call native code to request country code changes only if all {@link ClientModeManager} 249 * instances are ready for country code change. Country code is a chip level configuration and 250 * results in all the connections on the chip being disrupted. 251 * 252 * @return true if there are active CMM's and all are ready for country code change. 253 */ isReady()254 private boolean isReady() { 255 return !mAmmToReadyForChangeMap.isEmpty() 256 && mAmmToReadyForChangeMap.values().stream().allMatch(r -> r); 257 } 258 259 /** 260 * Check all active CMM instances and apply country code change if ready. 261 */ evaluateAllCmmStateAndApplyIfAllReady()262 private void evaluateAllCmmStateAndApplyIfAllReady() { 263 Log.d(TAG, "evaluateAllCmmStateAndApplyIfAllReady: " + mAmmToReadyForChangeMap); 264 if (isReady()) { 265 mReadyTimestamp = FORMATTER.format(new Date(System.currentTimeMillis())); 266 // We are ready to set country code now. 267 // We need to post pending country code request. 268 initializeTelephonyCountryCodeIfNeeded(); 269 updateCountryCode(); 270 } 271 } 272 273 /** 274 * This call will override any existing country code. 275 * This is for test purpose only and we should disallow any update from 276 * telephony in this mode. 277 * @param countryCode A 2-Character alphanumeric country code. 278 */ setOverrideCountryCode(String countryCode)279 public synchronized void setOverrideCountryCode(String countryCode) { 280 if (TextUtils.isEmpty(countryCode)) { 281 Log.d(TAG, "Fail to override country code because" 282 + "the received country code is empty"); 283 return; 284 } 285 mOverrideCountryCode = countryCode.toUpperCase(Locale.US); 286 287 // If wpa_supplicant is ready we set the country code now, otherwise it will be 288 // set once wpa_supplicant is ready. 289 if (isReady()) { 290 updateCountryCode(); 291 } else { 292 Log.d(TAG, "skip update supplicant not ready yet"); 293 } 294 } 295 296 /** 297 * This is for clearing the country code previously set through #setOverrideCountryCode() method 298 */ clearOverrideCountryCode()299 public synchronized void clearOverrideCountryCode() { 300 mOverrideCountryCode = null; 301 302 // If wpa_supplicant is ready we set the country code now, otherwise it will be 303 // set once wpa_supplicant is ready. 304 if (isReady()) { 305 updateCountryCode(); 306 } else { 307 Log.d(TAG, "skip update supplicant not ready yet"); 308 } 309 } 310 setTelephonyCountryCode(String countryCode)311 private void setTelephonyCountryCode(String countryCode) { 312 Log.d(TAG, "Set telephony country code to: " + countryCode); 313 mTelephonyCountryTimestamp = FORMATTER.format(new Date(System.currentTimeMillis())); 314 315 // Empty country code. 316 if (TextUtils.isEmpty(countryCode)) { 317 if (mContext.getResources() 318 .getBoolean(R.bool.config_wifi_revert_country_code_on_cellular_loss)) { 319 Log.d(TAG, "Received empty country code, reset to default country code"); 320 mTelephonyCountryCode = null; 321 } 322 } else { 323 mTelephonyCountryCode = countryCode.toUpperCase(Locale.US); 324 } 325 } 326 327 /** 328 * Handle country code change request. 329 * @param countryCode The country code intended to set. 330 * This is supposed to be from Telephony service. 331 * otherwise we think it is from other applications. 332 * @return Returns true if the country code passed in is acceptable. 333 */ setTelephonyCountryCodeAndUpdate(String countryCode)334 public boolean setTelephonyCountryCodeAndUpdate(String countryCode) { 335 setTelephonyCountryCode(countryCode); 336 if (mOverrideCountryCode != null) { 337 Log.d(TAG, "Skip Telephony Country code update due to override country code set"); 338 return false; 339 } 340 // If wpa_supplicant is ready we set the country code now, otherwise it will be 341 // set once wpa_supplicant is ready. 342 if (isReady()) { 343 updateCountryCode(); 344 } else { 345 Log.d(TAG, "skip update supplicant not ready yet"); 346 } 347 348 return true; 349 } 350 351 /** 352 * Method to get the Country Code that was sent to wpa_supplicant. 353 * 354 * @return Returns the local copy of the Country Code that was sent to the driver upon 355 * setReadyForChange(true). 356 * If wpa_supplicant was never started, this may be null even if Telephony reported a valid 357 * country code. 358 * Returns null if no Country Code was sent to driver. 359 */ 360 @VisibleForTesting getCountryCodeSentToDriver()361 public synchronized String getCountryCodeSentToDriver() { 362 return mDriverCountryCode; 363 } 364 365 /** 366 * Method to return the currently reported Country Code resolved from various sources: 367 * e.g. default country code, cellular network country code, country code override, etc. 368 * 369 * @return The current Wifi Country Code resolved from various sources. Returns null when there 370 * is no Country Code available. 371 */ 372 @Nullable getCountryCode()373 public synchronized String getCountryCode() { 374 initializeTelephonyCountryCodeIfNeeded(); 375 return pickCountryCode(); 376 } 377 378 /** 379 * set default country code 380 * @param countryCode A 2-Character alphanumeric country code. 381 */ setDefaultCountryCode(String countryCode)382 public synchronized void setDefaultCountryCode(String countryCode) { 383 if (TextUtils.isEmpty(countryCode)) { 384 Log.d(TAG, "Fail to set default country code because the country code is empty"); 385 return; 386 } 387 388 mSettingsConfigStore.put(WIFI_DEFAULT_COUNTRY_CODE, 389 countryCode.toUpperCase(Locale.US)); 390 Log.i(TAG, "Default country code updated in config store: " + countryCode); 391 392 // If wpa_supplicant is ready we set the country code now, otherwise it will be 393 // set once wpa_supplicant is ready. 394 if (isReady()) { 395 updateCountryCode(); 396 } else { 397 Log.d(TAG, "skip update supplicant not ready yet"); 398 } 399 } 400 401 /** 402 * Method to dump the current state of this WifiCounrtyCode object. 403 */ dump(FileDescriptor fd, PrintWriter pw, String[] args)404 public synchronized void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 405 pw.println("mRevertCountryCodeOnCellularLoss: " 406 + mContext.getResources().getBoolean( 407 R.bool.config_wifi_revert_country_code_on_cellular_loss)); 408 pw.println("DefaultCountryCode(system property): " + getOemDefaultCountryCode()); 409 pw.println("DefaultCountryCode(config store): " 410 + mSettingsConfigStore.get(WIFI_DEFAULT_COUNTRY_CODE)); 411 pw.println("mDriverCountryCode: " + mDriverCountryCode); 412 pw.println("mTelephonyCountryCode: " + mTelephonyCountryCode); 413 pw.println("mTelephonyCountryTimestamp: " + mTelephonyCountryTimestamp); 414 pw.println("mOverrideCountryCode: " + mOverrideCountryCode); 415 pw.println("mDriverCountryTimestamp: " + mDriverCountryTimestamp); 416 pw.println("mReadyTimestamp: " + mReadyTimestamp); 417 pw.println("isReady: " + isReady()); 418 pw.println("mAmmToReadyForChangeMap: " + mAmmToReadyForChangeMap); 419 } 420 updateCountryCode()421 private void updateCountryCode() { 422 String country = pickCountryCode(); 423 Log.d(TAG, "updateCountryCode to " + country); 424 425 // We do not check if the country code equals the current one. 426 // There are two reasons: 427 // 1. Wpa supplicant may silently modify the country code. 428 // 2. If Wifi restarted therefore wpa_supplicant also restarted, 429 // the country code counld be reset to '00' by wpa_supplicant. 430 if (country != null) { 431 setCountryCodeNative(country); 432 } 433 // We do not set country code if there is no candidate. This is reasonable 434 // because wpa_supplicant usually starts with an international safe country 435 // code setting: '00'. 436 } 437 pickCountryCode()438 private String pickCountryCode() { 439 if (mOverrideCountryCode != null) { 440 return mOverrideCountryCode; 441 } 442 if (mTelephonyCountryCode != null) { 443 return mTelephonyCountryCode; 444 } 445 return mSettingsConfigStore.get(WIFI_DEFAULT_COUNTRY_CODE); 446 } 447 setCountryCodeNative(String country)448 private boolean setCountryCodeNative(String country) { 449 Set<ActiveModeManager> amms = mAmmToReadyForChangeMap.keySet(); 450 boolean isConcreteClientModeManagerUpdated = false; 451 boolean anyAmmConfigured = false; 452 for (ActiveModeManager am : amms) { 453 if (!isConcreteClientModeManagerUpdated && am instanceof ConcreteClientModeManager) { 454 // Set the country code using one of the active mode managers. Since 455 // country code is a chip level global setting, it can be set as long 456 // as there is at least one active interface to communicate to Wifi chip 457 ConcreteClientModeManager cm = (ConcreteClientModeManager) am; 458 if (!cm.setCountryCode(country)) { 459 Log.d(TAG, "Failed to set country code (ConcreteClientModeManager) to " 460 + country); 461 } else { 462 isConcreteClientModeManagerUpdated = true; 463 anyAmmConfigured = true; 464 // Start from S, frameworks support country code callback from wificond, 465 // move "notify the lister" to CountryChangeListenerInternal. 466 if (!SdkLevel.isAtLeastS()) { 467 updateDriverCountryCodeAndNotifyListener(country); 468 } 469 } 470 } else if (am instanceof SoftApManager) { 471 // The API:updateCountryCode in SoftApManager is asynchronous, it requires a new 472 // callback support in S to trigger "updateDriverCountryCodeAndNotifyListener" for 473 // the new S API: SoftApCapability#getSupportedChannelList(band). 474 // It requires: 475 // 1. a new overlay configuration which is introduced from S. 476 // 2. wificond support in S for S API: SoftApCapability#getSupportedChannelList 477 // Any case if device supported to set country code in R, 478 // the new S API: SoftApCapability#getSupportedChannelList(band) still doesn't work 479 // normally in R build when wifi disabled. 480 SoftApManager sm = (SoftApManager) am; 481 if (!sm.updateCountryCode(country)) { 482 Log.d(TAG, "Can't set country code (SoftApManager) to " 483 + country + " (Device doesn't support it)"); 484 } else { 485 anyAmmConfigured = true; 486 } 487 } 488 } 489 return anyAmmConfigured; 490 } 491 updateDriverCountryCodeAndNotifyListener(String country)492 private void updateDriverCountryCodeAndNotifyListener(String country) { 493 mDriverCountryTimestamp = FORMATTER.format(new Date(System.currentTimeMillis())); 494 mDriverCountryCode = country; 495 for (ChangeListener listener : mListeners) { 496 listener.onDriverCountryCodeChanged(country); 497 } 498 } 499 } 500 501