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