/* * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.server.wifi; import static com.android.server.wifi.WifiSettingsConfigStore.WIFI_DEFAULT_COUNTRY_CODE; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; import android.net.wifi.WifiInfo; import android.os.SystemProperties; import android.telephony.TelephonyManager; import android.text.TextUtils; import android.util.ArrayMap; import android.util.Log; import com.android.modules.utils.build.SdkLevel; import com.android.server.wifi.hotspot2.NetworkDetail; import com.android.server.wifi.p2p.WifiP2pMetrics; import com.android.server.wifi.util.ApConfigUtil; import com.android.server.wifi.util.WifiPermissionsUtil; import com.android.wifi.resources.R; import java.io.FileDescriptor; import java.io.PrintWriter; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Set; /** * Provide functions for making changes to WiFi country code. * This Country Code is from MCC or phone default setting. This class sends Country Code * to driver through wpa_supplicant when ClientModeImpl marks current state as ready * using setReadyForChange(true). */ public class WifiCountryCode { private static final String TAG = "WifiCountryCode"; private static final String BOOT_DEFAULT_WIFI_COUNTRY_CODE = "ro.boot.wificountrycode"; private static final int PKT_COUNT_HIGH_PKT_PER_SEC = 16; private static final int DISCONNECT_WIFI_COUNT_MAX = 1; /* TODO: replace with PackageManager.FEATURE_TELEPHONY_CALLING once * wifi-module-sdk-version-defaults min_sdk_version bumps to API 33. */ private static final String FEATURE_TELEPHONY_CALLING = "android.hardware.telephony.calling"; static final int MIN_COUNTRY_CODE_COUNT_US = 3; static final int MIN_COUNTRY_CODE_COUNT_OTHER = 2; static final String COUNTRY_CODE_US = "US"; static final int MAX_DURATION_SINCE_LAST_UPDATE_TIME_MS = 500_000; static final int MIN_SCAN_RSSI_DBM = -85; private final String mWorldModeCountryCode; private final Context mContext; private final TelephonyManager mTelephonyManager; private final ActiveModeWarden mActiveModeWarden; private final WifiP2pMetrics mWifiP2pMetrics; private final WifiNative mWifiNative; private final WifiSettingsConfigStore mSettingsConfigStore; private final Clock mClock; private final WifiPermissionsUtil mWifiPermissionsUtil; private final WifiCarrierInfoManager mWifiCarrierInfoManager; private List mListeners = new ArrayList<>(); private boolean mVerboseLoggingEnabled = false; private boolean mIsCountryCodePendingToUpdateToCmm = true; // default to true for first update. /** * Map of active ClientModeManager instance to whether it is ready for country code change. * * - When a new ClientModeManager instance is created, it is added to this map and starts out * ready for any country code changes (value = true). * - When the ClientModeManager instance starts a connection attempt, it is marked not ready for * country code changes (value = false). * - When the ClientModeManager instance ends the connection, it is again marked ready for * country code changes (value = true). * - When the ClientModeManager instance is destroyed, it is removed from this map. */ private final Map mAmmToReadyForChangeMap = new ArrayMap<>(); private static final SimpleDateFormat FORMATTER = new SimpleDateFormat("MM-dd HH:mm:ss.SSS"); private String mTelephonyCountryCode = null; private String mOverrideCountryCode = null; private String mDriverCountryCode = null; private String mFrameworkCountryCode = null; private String mLastReceivedActiveDriverCountryCode = null; private long mDriverCountryCodeUpdatedTimestamp = 0; private String mTelephonyCountryTimestamp = null; private long mFrameworkCountryCodeUpdatedTimestamp = 0; private String mAllCmmReadyTimestamp = null; private int mDisconnectWifiToForceUpdateCount = 0; private class ModeChangeCallbackInternal implements ActiveModeWarden.ModeChangeCallback { @Override public void onActiveModeManagerAdded(@NonNull ActiveModeManager activeModeManager) { if (activeModeManager.getRole() instanceof ActiveModeManager.ClientRole) { // Add this CMM for tracking. Interface is up and HAL is initialized at this point. // If this device runs the 1.5 HAL version, use the IWifiChip.setCountryCode() // to set the country code. mAmmToReadyForChangeMap.put(activeModeManager, true); evaluateAllCmmStateAndApplyIfAllReady(); } else if (activeModeManager instanceof SoftApManager) { // Put SoftApManager ready for consistence behavior in mAmmToReadyForChangeMap. // No need to trigger CC change because SoftApManager takes CC when starting up. mAmmToReadyForChangeMap.put(activeModeManager, true); } } @Override public void onActiveModeManagerRemoved(@NonNull ActiveModeManager activeModeManager) { if (mAmmToReadyForChangeMap.remove(activeModeManager) != null) { if (activeModeManager instanceof ActiveModeManager.ClientRole) { // Remove this CMM from tracking. evaluateAllCmmStateAndApplyIfAllReady(); } } if (mAmmToReadyForChangeMap.size() == 0) { handleCountryCodeChanged(null); Log.i(TAG, "No active mode, call onDriverCountryCodeChanged with Null"); } } @Override public void onActiveModeManagerRoleChanged(@NonNull ActiveModeManager activeModeManager) { if (activeModeManager.getRole() == ActiveModeManager.ROLE_CLIENT_PRIMARY) { // Set this CMM ready for change. This is needed to handle the transition from // ROLE_CLIENT_SCAN_ONLY to ROLE_CLIENT_PRIMARY on devices running older HAL // versions (since the IWifiChip.setCountryCode() was only added in the 1.5 HAL // version, before that we need to wait till supplicant is up for country code // change. mAmmToReadyForChangeMap.put(activeModeManager, true); evaluateAllCmmStateAndApplyIfAllReady(); } } } private class ClientModeListenerInternal implements ClientModeImplListener { @Override public void onConnectionStart(@NonNull ConcreteClientModeManager clientModeManager) { if (mAmmToReadyForChangeMap.get(clientModeManager) == null) { Log.wtf(TAG, "Connection start received from unknown client mode manager"); } // connection start. CMM not ready for country code change. mAmmToReadyForChangeMap.put(clientModeManager, false); evaluateAllCmmStateAndApplyIfAllReady(); } @Override public void onConnectionEnd(@NonNull ConcreteClientModeManager clientModeManager) { if (mAmmToReadyForChangeMap.get(clientModeManager) == null) { Log.wtf(TAG, "Connection end received from unknown client mode manager"); } // connection end. CMM ready for country code change. mAmmToReadyForChangeMap.put(clientModeManager, true); evaluateAllCmmStateAndApplyIfAllReady(); } } private class CountryChangeListenerInternal implements ChangeListener { @Override public void onDriverCountryCodeChanged(String country) { Log.i(TAG, "Receive onDriverCountryCodeChanged " + country); mLastReceivedActiveDriverCountryCode = country; // Before T build, always handle country code changed. if (!SdkLevel.isAtLeastT() || isDriverSupportedRegChangedEvent()) { // CC doesn't notify listener after sending to the driver, notify the listener // after we received CC changed event. handleCountryCodeChanged(country); } } @Override public void onSetCountryCodeSucceeded(String country) { Log.i(TAG, "Receive onSetCountryCodeSucceeded " + country); // The country code callback might not be triggered even if the driver supports reg // changed event when the maintained country code in the driver is same as last one. // So notify the country code changed event to listener when the set one is same as // last received one. if (!SdkLevel.isAtLeastT() || !isDriverSupportedRegChangedEvent() || TextUtils.equals(country, mLastReceivedActiveDriverCountryCode)) { mWifiNative.countryCodeChanged(country); handleCountryCodeChanged(country); } } } public WifiCountryCode( Context context, ActiveModeWarden activeModeWarden, WifiP2pMetrics wifiP2pMetrics, ClientModeImplMonitor clientModeImplMonitor, WifiNative wifiNative, @NonNull WifiSettingsConfigStore settingsConfigStore, Clock clock, WifiPermissionsUtil wifiPermissionsUtil, @NonNull WifiCarrierInfoManager wifiCarrierInfoManager) { mContext = context; mTelephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); mActiveModeWarden = activeModeWarden; mWifiP2pMetrics = wifiP2pMetrics; mWifiNative = wifiNative; mSettingsConfigStore = settingsConfigStore; mClock = clock; mWifiPermissionsUtil = wifiPermissionsUtil; mWifiCarrierInfoManager = wifiCarrierInfoManager; mActiveModeWarden.registerModeChangeCallback(new ModeChangeCallbackInternal()); clientModeImplMonitor.registerListener(new ClientModeListenerInternal()); mWifiNative.registerCountryCodeEventListener(new CountryChangeListenerInternal()); mWorldModeCountryCode = mContext.getResources() .getString(R.string.config_wifiDriverWorldModeCountryCode); Log.d(TAG, "Default country code from system property " + BOOT_DEFAULT_WIFI_COUNTRY_CODE + " is " + getOemDefaultCountryCode()); } /** * Default country code stored in system property * @return Country code if available, null otherwise. */ public static String getOemDefaultCountryCode() { String country = SystemProperties.get(BOOT_DEFAULT_WIFI_COUNTRY_CODE); return WifiCountryCode.isValid(country) ? country.toUpperCase(Locale.US) : null; } /** * Is this a valid country code * @param countryCode A 2-Character alphanumeric country code. * @return true if the countryCode is valid, false otherwise. */ public static boolean isValid(String countryCode) { return countryCode != null && countryCode.length() == 2 && countryCode.chars().allMatch(Character::isLetterOrDigit); } /** * The class for country code related change listener */ public interface ChangeListener { /** * Called when receiving new country code change pending. */ default void onCountryCodeChangePending(@NonNull String countryCode) {}; /** * Called when receiving country code changed from driver. */ void onDriverCountryCodeChanged(String countryCode); /** * Called when country code set to native layer successful, framework sends event to * force country code changed. * * Reason: The country code change listener from wificond rely on driver supported * NL80211_CMD_REG_CHANGE/NL80211_CMD_WIPHY_REG_CHANGE. Trigger update country code * to listener here for non-supported platform. */ default void onSetCountryCodeSucceeded(String country) {} } /** * Register Country code changed listener. */ public void registerListener(@NonNull ChangeListener listener) { mListeners.add(listener); /** * Always called with mDriverCountryCode even if the SDK version is lower than T. * Reason: Before android S, the purpose of the internal listener is updating the supported * channels, it always depends on mDriverCountryCode. */ if (mDriverCountryCode != null) { listener.onDriverCountryCodeChanged(mDriverCountryCode); } } /** * Unregister Country code changed listener. */ public void unregisterListener(@NonNull ChangeListener listener) { mListeners.remove(listener); } /** * Enable verbose logging for WifiCountryCode. */ public void enableVerboseLogging(boolean verbose) { mVerboseLoggingEnabled = verbose; } private boolean hasCalling() { return mContext.getPackageManager().hasSystemFeature(FEATURE_TELEPHONY_CALLING); } private void initializeTelephonyCountryCodeIfNeeded() { // If we don't have telephony country code set yet, poll it. if (mTelephonyCountryCode == null) { Log.d(TAG, "Reading country code from telephony"); setTelephonyCountryCode(mTelephonyManager.getNetworkCountryIso()); } } /** * We call native code to request country code changes only if all {@link ClientModeManager} * instances are ready for country code change. Country code is a chip level configuration and * results in all the connections on the chip being disrupted. * * @return true if there are active CMM's and all are ready for country code change. */ private boolean isAllCmmReady() { boolean isAnyCmmExist = false; for (ActiveModeManager am : mAmmToReadyForChangeMap.keySet()) { if (am instanceof ConcreteClientModeManager) { isAnyCmmExist = true; if (!mAmmToReadyForChangeMap.get(am)) { return false; } } } return isAnyCmmExist; } /** * Check all active CMM instances and apply country code change if ready. */ private void evaluateAllCmmStateAndApplyIfAllReady() { Log.d(TAG, "evaluateAllCmmStateAndApplyIfAllReady: " + mAmmToReadyForChangeMap); if (isAllCmmReady() && mIsCountryCodePendingToUpdateToCmm) { mAllCmmReadyTimestamp = FORMATTER.format(new Date(mClock.getWallClockMillis())); // We are ready to set country code now. // We need to post pending country code request. initializeTelephonyCountryCodeIfNeeded(); updateCountryCode(true); } } /** * This call will override any existing country code. * This is for test purpose only and we should disallow any update from * telephony in this mode. * @param countryCode A 2-Character alphanumeric country code. */ public synchronized void setOverrideCountryCode(String countryCode) { if (TextUtils.isEmpty(countryCode)) { Log.d(TAG, "Fail to override country code because" + "the received country code is empty"); return; } // Support 00 map to device world mode country code if (TextUtils.equals("00", countryCode)) { countryCode = mWorldModeCountryCode; } mOverrideCountryCode = countryCode.toUpperCase(Locale.US); updateCountryCode(false); } /** * This is for clearing the country code previously set through #setOverrideCountryCode() method */ public synchronized void clearOverrideCountryCode() { mOverrideCountryCode = null; updateCountryCode(false); } private void setTelephonyCountryCode(String countryCode) { Log.d(TAG, "Set telephony country code to: " + countryCode); mTelephonyCountryTimestamp = FORMATTER.format(new Date(mClock.getWallClockMillis())); // Empty country code. if (TextUtils.isEmpty(countryCode)) { if (mContext.getResources() .getBoolean(R.bool.config_wifi_revert_country_code_on_cellular_loss)) { Log.d(TAG, "Received empty country code, reset to default country code"); mTelephonyCountryCode = null; } } else { mTelephonyCountryCode = countryCode.toUpperCase(Locale.US); } } /** * Handle telephony country code change request. * @param countryCode The country code intended to set. * This is supposed to be from Telephony service. * otherwise we think it is from other applications. * @return Returns true if the country code passed in is acceptable and passed to the driver. */ public boolean setTelephonyCountryCodeAndUpdate(String countryCode) { if (TextUtils.isEmpty(countryCode) && !TextUtils.isEmpty(mTelephonyManager.getNetworkCountryIso())) { Log.i(TAG, "Skip Telephony CC update to empty because there is " + "an available CC from default active SIM"); return false; } // We do not check if the country code (CC) equals the current one because // 1. Wpa supplicant may silently modify the country code. // 2. If Wifi restarted therefore wpa_supplicant also restarted, setTelephonyCountryCode(countryCode); if (mOverrideCountryCode != null) { Log.d(TAG, "Skip Telephony CC update due to override country code set"); return false; } updateCountryCode(false); return true; } /** * Update country code from scan results * Note the derived country code is used only if all following conditions are met * 1) There is no telephony country code * 2) The current driver country code is empty or equal to the worldwide code * 3) Currently the device is disconnected * @param scanDetails Wifi scan results */ public void updateCountryCodeFromScanResults(@NonNull List scanDetails) { if (mTelephonyCountryCode != null) { return; } if (!isCcUpdateGenericEnabled()) { return; } String countryCode = findCountryCodeFromScanResults(scanDetails); if (countryCode == null) { Log.i(TAG, "Skip framework CC update because it is empty"); return; } if (countryCode.equalsIgnoreCase(mFrameworkCountryCode)) { return; } mFrameworkCountryCodeUpdatedTimestamp = mClock.getWallClockMillis(); mFrameworkCountryCode = countryCode; if (mOverrideCountryCode != null) { Log.d(TAG, "Skip framework CC update due to override country code set"); return; } updateCountryCode(false); } private boolean isCcUpdateGenericEnabled() { return mContext.getResources().getBoolean( R.bool.config_wifiUpdateCountryCodeFromScanResultGeneric); } private String findCountryCodeFromScanResults(List scanDetails) { String selectedCountryCode = null; int count = 0; for (ScanDetail scanDetail : scanDetails) { NetworkDetail networkDetail = scanDetail.getNetworkDetail(); String countryCode = networkDetail.getCountryCode(); if (scanDetail.getScanResult().level < MIN_SCAN_RSSI_DBM) { continue; } if (countryCode == null || TextUtils.isEmpty(countryCode)) { continue; } if (selectedCountryCode == null) { selectedCountryCode = countryCode; } if (!selectedCountryCode.equalsIgnoreCase(countryCode)) { if (mVerboseLoggingEnabled) { Log.d(TAG, "CC doesn't match"); } return null; } count++; } if (mVerboseLoggingEnabled) { Log.d(TAG, selectedCountryCode + " " + count); } if (count == 0) { return null; } int min_count = selectedCountryCode.equalsIgnoreCase(COUNTRY_CODE_US) ? MIN_COUNTRY_CODE_COUNT_US : MIN_COUNTRY_CODE_COUNT_OTHER; return (count >= min_count) ? selectedCountryCode : null; } private void disconnectWifiToForceUpdateIfNeeded() { if (shouldDisconnectWifiToForceUpdate()) { Log.d(TAG, "Disconnect wifi to force update"); for (ClientModeManager cmm : mActiveModeWarden.getInternetConnectivityClientModeManagers()) { if (!cmm.isConnected()) { continue; } cmm.disconnect(); } mDisconnectWifiToForceUpdateCount++; } } private boolean shouldDisconnectWifiToForceUpdate() { if (!hasCalling() || mWifiCarrierInfoManager.isWifiCallingAvailable()) { return false; } if (mTelephonyCountryCode == null || mTelephonyCountryCode.equals(mDriverCountryCode)) { return false; } if (mDisconnectWifiToForceUpdateCount >= DISCONNECT_WIFI_COUNT_MAX) { return false; } if (mDriverCountryCode != null && !mDriverCountryCode.equalsIgnoreCase(mWorldModeCountryCode)) { return false; } for (ClientModeManager cmm : mActiveModeWarden.getInternetConnectivityClientModeManagers()) { if (!cmm.isConnected()) { continue; } WifiInfo wifiInfo = cmm.getConnectionInfo(); if (wifiInfo.getSuccessfulTxPacketsPerSecond() < PKT_COUNT_HIGH_PKT_PER_SEC && wifiInfo.getSuccessfulRxPacketsPerSecond() < PKT_COUNT_HIGH_PKT_PER_SEC) { return true; } } return false; } /** * Method to get the received driver Country Code that being used in driver. * * @return Returns the local copy of the received driver Country Code or null if * there is no Country Code was received from driver or no any active mode. */ @Nullable public synchronized String getCurrentDriverCountryCode() { return mDriverCountryCode; } /** * Method to return the currently reported Country Code resolved from various sources: * e.g. default country code, cellular network country code, country code override, etc. * * @return The current Wifi Country Code resolved from various sources. Returns null when there * is no Country Code available. */ @Nullable public synchronized String getCountryCode() { initializeTelephonyCountryCodeIfNeeded(); return pickCountryCode(true); } /** * set default country code * @param countryCode A 2-Character alphanumeric country code. */ public synchronized void setDefaultCountryCode(String countryCode) { if (TextUtils.isEmpty(countryCode)) { Log.d(TAG, "Fail to set default country code because the country code is empty"); return; } mSettingsConfigStore.put(WIFI_DEFAULT_COUNTRY_CODE, countryCode.toUpperCase(Locale.US)); Log.i(TAG, "Default country code updated in config store: " + countryCode); updateCountryCode(false); } /** * Method to dump the current state of this WifiCountryCode object. */ public synchronized void dump(FileDescriptor fd, PrintWriter pw, String[] args) { pw.println("mRevertCountryCodeOnCellularLoss: " + mContext.getResources().getBoolean( R.bool.config_wifi_revert_country_code_on_cellular_loss)); pw.println("DefaultCountryCode(system property): " + getOemDefaultCountryCode()); pw.println("DefaultCountryCode(config store): " + mSettingsConfigStore.get(WIFI_DEFAULT_COUNTRY_CODE)); pw.println("mTelephonyCountryCode: " + mTelephonyCountryCode); pw.println("mTelephonyCountryTimestamp: " + mTelephonyCountryTimestamp); pw.println("mOverrideCountryCode: " + mOverrideCountryCode); pw.println("mAllCmmReadyTimestamp: " + mAllCmmReadyTimestamp); pw.println("isAllCmmReady: " + isAllCmmReady()); pw.println("mAmmToReadyForChangeMap: " + mAmmToReadyForChangeMap); pw.println("mDisconnectWifiToForceUpdateCount: " + mDisconnectWifiToForceUpdateCount); pw.println("mDriverCountryCode: " + mDriverCountryCode); pw.println("mDriverCountryCodeUpdatedTimestamp: " + (mDriverCountryCodeUpdatedTimestamp != 0 ? FORMATTER.format(new Date(mDriverCountryCodeUpdatedTimestamp)) : "N/A")); pw.println("mFrameworkCountryCode: " + mFrameworkCountryCode); pw.println("mFrameworkCountryCodeUpdatedTimestamp: " + (mFrameworkCountryCodeUpdatedTimestamp != 0 ? FORMATTER.format(new Date(mFrameworkCountryCodeUpdatedTimestamp)) : "N/A")); pw.println("isDriverSupportedRegChangedEvent: " + isDriverSupportedRegChangedEvent()); } private boolean isDriverSupportedRegChangedEvent() { return mContext.getResources().getBoolean( R.bool.config_wifiDriverSupportedNl80211RegChangedEvent); } private void updateCountryCode(boolean isClientModeOnly) { // The mDriverCountryCode is the country code which is being used by driver now. // It should not be a candidate for writing use case. String country = pickCountryCode(false); Log.d(TAG, "updateCountryCode to " + country); // We do not check if the country code equals the current one. // There are two reasons: // 1. Wpa supplicant may silently modify the country code. // 2. If Wifi restarted therefore wpa_supplicant also restarted, // the country code could be reset to '00' by wpa_supplicant. if (country != null) { setCountryCodeNative(country, isClientModeOnly); } // We do not set country code if there is no candidate. This is reasonable // because wpa_supplicant usually starts with an international safe country // code setting: '00'. } /** * Pick up country code base on country code we have. * * @param useDriverCountryCodeIfAvailable whether or not to use driver country code * if available, and it is only for reporting purpose. * @return country code base on the use case and current country code we have. */ private String pickCountryCode(boolean useDriverCountryCodeIfAvailable) { if (mOverrideCountryCode != null) { return mOverrideCountryCode; } if (mTelephonyCountryCode != null) { return mTelephonyCountryCode; } if (useDriverCountryCodeIfAvailable && mDriverCountryCode != null) { // Returns driver country code since it may be different to WIFI_DEFAULT_COUNTRY_CODE // when driver supported 802.11d. return mDriverCountryCode; } if (mFrameworkCountryCode != null && isCcUpdateGenericEnabled()) { return mFrameworkCountryCode; } return mSettingsConfigStore.get(WIFI_DEFAULT_COUNTRY_CODE); } private boolean setCountryCodeNative(String country, boolean isClientModeOnly) { Set amms = mAmmToReadyForChangeMap.keySet(); boolean isConcreteClientModeManagerUpdated = false; boolean anyAmmConfigured = false; final boolean isNeedToUpdateCCToSta = mContext.getResources() .getBoolean(R.bool.config_wifiStaDynamicCountryCodeUpdateSupported) || isAllCmmReady(); if (!isNeedToUpdateCCToSta) { Log.d(TAG, "skip update supplicant not ready yet"); disconnectWifiToForceUpdateIfNeeded(); } boolean isCountryCodeChanged = !TextUtils.equals(mDriverCountryCode, country); Log.d(TAG, "setCountryCodeNative: " + country + ", isClientModeOnly: " + isClientModeOnly + " mDriverCountryCode: " + mDriverCountryCode); for (ActiveModeManager am : amms) { if (!isConcreteClientModeManagerUpdated && am instanceof ConcreteClientModeManager) { mIsCountryCodePendingToUpdateToCmm = !isNeedToUpdateCCToSta; if (!isNeedToUpdateCCToSta) { continue; } // Set the country code using one of the active mode managers. Since // country code is a chip level global setting, it can be set as long // as there is at least one active interface to communicate to Wifi chip ConcreteClientModeManager cm = (ConcreteClientModeManager) am; if (!cm.setCountryCode(country)) { Log.d(TAG, "Failed to set country code (ConcreteClientModeManager) to " + country); } else { isConcreteClientModeManagerUpdated = true; anyAmmConfigured = true; // Start from S, frameworks support country code callback from wificond, // move "notify the lister" to CountryChangeListenerInternal. if (!SdkLevel.isAtLeastS() && !isDriverSupportedRegChangedEvent()) { handleCountryCodeChanged(country); } } } else if (!isClientModeOnly && am instanceof SoftApManager) { SoftApManager sm = (SoftApManager) am; if (mDriverCountryCode == null || !isCountryCodeChanged) { // Ignore SoftApManager init country code case or country code didn't be // changed case. continue; } // Restart SAP if the overlay is enabled. if (ApConfigUtil.isSoftApRestartRequiredWhenCountryCodeChanged(mContext)) { Log.i(TAG, "restart SoftAp required because country code changed to " + country); SoftApModeConfiguration modeConfig = sm.getSoftApModeConfiguration(); SoftApModeConfiguration newModeConfig = new SoftApModeConfiguration( modeConfig.getTargetMode(), modeConfig.getSoftApConfiguration(), modeConfig.getCapability(), country, modeConfig.getTetheringRequest()); mActiveModeWarden.stopSoftAp(modeConfig.getTargetMode()); mActiveModeWarden.startSoftAp(newModeConfig, sm.getRequestorWs()); } else { // The API:updateCountryCode in SoftApManager is asynchronous, it requires a // new callback support in S to trigger "notifyListener" for // the new S API: SoftApCapability#getSupportedChannelList(band). // It requires: // 1. a new overlay configuration which is introduced from S. // 2. wificond support in S for S API: SoftApCapability#getSupportedChannelList // Any case if device supported to set country code in R, // the new S API: SoftApCapability#getSupportedChannelList(band) still doesn't // work normally in R build when wifi disabled. if (!sm.updateCountryCode(country)) { Log.d(TAG, "Can't set country code (SoftApManager) to " + country + " when SAP on (Device doesn't support runtime update)"); } else { anyAmmConfigured = true; } } } } if (!anyAmmConfigured) { for (ChangeListener listener : mListeners) { if (country != null) { listener.onCountryCodeChangePending(country); } } } return anyAmmConfigured; } private void handleCountryCodeChanged(String country) { mDriverCountryCodeUpdatedTimestamp = mClock.getWallClockMillis(); mDriverCountryCode = country; mWifiP2pMetrics.setIsCountryCodeWorldMode(isDriverCountryCodeWorldMode()); notifyListener(country); if (country == null) { mIsCountryCodePendingToUpdateToCmm = true; } } /** * Method to check if current driver Country Code is in the world mode */ private boolean isDriverCountryCodeWorldMode() { if (mDriverCountryCode == null) { return true; } return mDriverCountryCode.equalsIgnoreCase(mWorldModeCountryCode); } /** * Notify the listeners. There are two kind of listeners * 1. external listener, they only care what is country code which driver is using now. * 2. internal listener, frameworks also only care what is country code which driver is using * now because it requires to update supported channels with new country code. * * Note: Call this API only after confirming the CC is used in driver. * * @param country the country code is used in driver or null when driver is non-active. */ private void notifyListener(@Nullable String country) { mActiveModeWarden.updateClientScanModeAfterCountryCodeUpdate(country); for (ChangeListener listener : mListeners) { listener.onDriverCountryCodeChanged(country); } } }