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