• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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