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