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 android.content.BroadcastReceiver;
20 import android.content.Context;
21 import android.content.Intent;
22 import android.content.IntentFilter;
23 import android.os.Handler;
24 import android.telephony.TelephonyManager;
25 import android.text.TextUtils;
26 import android.util.Log;
27 
28 import com.android.internal.annotations.VisibleForTesting;
29 import com.android.wifi.resources.R;
30 
31 import java.io.FileDescriptor;
32 import java.io.PrintWriter;
33 import java.text.SimpleDateFormat;
34 import java.util.Date;
35 import java.util.Locale;
36 
37 /**
38  * Provide functions for making changes to WiFi country code.
39  * This Country Code is from MCC or phone default setting. This class sends Country Code
40  * to driver through wpa_supplicant when ClientModeImpl marks current state as ready
41  * using setReadyForChange(true).
42  */
43 public class WifiCountryCode {
44     private static final String TAG = "WifiCountryCode";
45     private final Context mContext;
46     private final TelephonyManager mTelephonyManager;
47     private final WifiNative mWifiNative;
48     private boolean DBG = false;
49     private boolean mReady = false;
50     private static final SimpleDateFormat FORMATTER = new SimpleDateFormat("MM-dd HH:mm:ss.SSS");
51 
52     private String mDefaultCountryCode = null;
53     private String mTelephonyCountryCode = null;
54     private String mDriverCountryCode = null;
55     private String mTelephonyCountryTimestamp = null;
56     private String mDriverCountryTimestamp = null;
57     private String mReadyTimestamp = null;
58     private boolean mForceCountryCode = false;
59 
WifiCountryCode( Context context, Handler handler, WifiNative wifiNative, String oemDefaultCountryCode)60     public WifiCountryCode(
61             Context context,
62             Handler handler,
63             WifiNative wifiNative,
64             String oemDefaultCountryCode) {
65         mContext = context;
66         mTelephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
67         mWifiNative = wifiNative;
68 
69         if (!TextUtils.isEmpty(oemDefaultCountryCode)) {
70             mDefaultCountryCode = oemDefaultCountryCode.toUpperCase(Locale.US);
71         }
72         context.registerReceiver(new BroadcastReceiver() {
73             @Override
74             public void onReceive(Context context, Intent intent) {
75                 String countryCode = intent.getStringExtra(TelephonyManager.EXTRA_NETWORK_COUNTRY);
76                 Log.d(TAG, "Country code changed");
77                 setCountryCodeAndUpdate(countryCode);
78             }}, new IntentFilter(TelephonyManager.ACTION_NETWORK_COUNTRY_CHANGED), null, handler);
79 
80         Log.d(TAG, "mDefaultCountryCode " + mDefaultCountryCode);
81     }
82 
83     /**
84      * Enable verbose logging for WifiCountryCode.
85      */
enableVerboseLogging(int verbose)86     public void enableVerboseLogging(int verbose) {
87         if (verbose > 0) {
88             DBG = true;
89         } else {
90             DBG = false;
91         }
92     }
93 
initializeTelephonyCountryCodeIfNeeded()94     private void initializeTelephonyCountryCodeIfNeeded() {
95         // If we don't have telephony country code set yet, poll it.
96         if (mTelephonyCountryCode == null) {
97             Log.d(TAG, "Reading country code from telephony");
98             setCountryCode(mTelephonyManager.getNetworkCountryIso());
99         }
100     }
101 
102     /**
103      * Change the state to indicates if wpa_supplicant is ready to handle country code changing
104      * request or not.
105      * We call native code to request country code changes only when wpa_supplicant is
106      * started but not yet L2 connected.
107      */
setReadyForChange(boolean ready)108     public synchronized void setReadyForChange(boolean ready) {
109         mReady = ready;
110         mReadyTimestamp = FORMATTER.format(new Date(System.currentTimeMillis()));
111         // We are ready to set country code now.
112         // We need to post pending country code request.
113         if (mReady) {
114             updateCountryCode();
115         }
116     }
117 
118     /**
119      * Enable force-country-code mode
120      * This is for forcing a country using cmd wifi from adb shell
121      * This is for test purpose only and we should disallow any update from
122      * telephony in this mode
123      * @param countryCode The forced two-letter country code
124      */
enableForceCountryCode(String countryCode)125     synchronized void enableForceCountryCode(String countryCode) {
126         if (TextUtils.isEmpty(countryCode)) {
127             Log.d(TAG, "Fail to force country code because the received country code is empty");
128             return;
129         }
130         mForceCountryCode = true;
131         mTelephonyCountryCode = countryCode.toUpperCase(Locale.US);
132 
133         // If wpa_supplicant is ready we set the country code now, otherwise it will be
134         // set once wpa_supplicant is ready.
135         if (mReady) {
136             updateCountryCode();
137         } else {
138             Log.d(TAG, "skip update supplicant not ready yet");
139         }
140     }
141 
142     /**
143      * Disable force-country-code mode
144      */
disableForceCountryCode()145     synchronized void disableForceCountryCode() {
146         mForceCountryCode = false;
147         mTelephonyCountryCode = null;
148 
149         // If wpa_supplicant is ready we set the country code now, otherwise it will be
150         // set once wpa_supplicant is ready.
151         if (mReady) {
152             updateCountryCode();
153         } else {
154             Log.d(TAG, "skip update supplicant not ready yet");
155         }
156     }
157 
setCountryCode(String countryCode)158     private boolean setCountryCode(String countryCode) {
159         if (mForceCountryCode) {
160             Log.d(TAG, "Telephony Country code ignored due to force-country-code mode");
161             return false;
162         }
163         Log.d(TAG, "Set telephony country code to: " + countryCode);
164         mTelephonyCountryTimestamp = FORMATTER.format(new Date(System.currentTimeMillis()));
165 
166         // Empty country code.
167         if (TextUtils.isEmpty(countryCode)) {
168             if (mContext.getResources()
169                         .getBoolean(R.bool.config_wifi_revert_country_code_on_cellular_loss)) {
170                 Log.d(TAG, "Received empty country code, reset to default country code");
171                 mTelephonyCountryCode = null;
172             }
173         } else {
174             mTelephonyCountryCode = countryCode.toUpperCase(Locale.US);
175         }
176         return true;
177     }
178 
179     /**
180      * Handle country code change request.
181      * @param countryCode The country code intended to set.
182      * This is supposed to be from Telephony service.
183      * otherwise we think it is from other applications.
184      * @return Returns true if the country code passed in is acceptable.
185      */
setCountryCodeAndUpdate(String countryCode)186     private boolean setCountryCodeAndUpdate(String countryCode) {
187         if (!setCountryCode(countryCode)) return false;
188         // If wpa_supplicant is ready we set the country code now, otherwise it will be
189         // set once wpa_supplicant is ready.
190         if (mReady) {
191             updateCountryCode();
192         } else {
193             Log.d(TAG, "skip update supplicant not ready yet");
194         }
195 
196         return true;
197     }
198 
199     /**
200      * Method to get the Country Code that was sent to wpa_supplicant.
201      *
202      * @return Returns the local copy of the Country Code that was sent to the driver upon
203      * setReadyForChange(true).
204      * If wpa_supplicant was never started, this may be null even if a SIM reported a valid
205      * country code.
206      * Returns null if no Country Code was sent to driver.
207      */
208     @VisibleForTesting
getCountryCodeSentToDriver()209     public synchronized String getCountryCodeSentToDriver() {
210         return mDriverCountryCode;
211     }
212 
213     /**
214      * Method to return the currently reported Country Code from the SIM or phone default setting.
215      *
216      * @return The currently reported Country Code from the SIM. If there is no Country Code
217      * reported from SIM, a phone default Country Code will be returned.
218      * Returns null when there is no Country Code available.
219      */
getCountryCode()220     public synchronized String getCountryCode() {
221         return pickCountryCode();
222     }
223 
224     /**
225      * Method to dump the current state of this WifiCounrtyCode object.
226      */
dump(FileDescriptor fd, PrintWriter pw, String[] args)227     public synchronized void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
228         pw.println("mRevertCountryCodeOnCellularLoss: "
229                 + mContext.getResources().getBoolean(
230                         R.bool.config_wifi_revert_country_code_on_cellular_loss));
231         pw.println("mDefaultCountryCode: " + mDefaultCountryCode);
232         pw.println("mDriverCountryCode: " + mDriverCountryCode);
233         pw.println("mTelephonyCountryCode: " + mTelephonyCountryCode);
234         pw.println("mTelephonyCountryTimestamp: " + mTelephonyCountryTimestamp);
235         pw.println("mDriverCountryTimestamp: " + mDriverCountryTimestamp);
236         pw.println("mReadyTimestamp: " + mReadyTimestamp);
237         pw.println("mReady: " + mReady);
238     }
239 
updateCountryCode()240     private void updateCountryCode() {
241         String country = pickCountryCode();
242         Log.d(TAG, "updateCountryCode to " + country);
243 
244         // We do not check if the country code equals the current one.
245         // There are two reasons:
246         // 1. Wpa supplicant may silently modify the country code.
247         // 2. If Wifi restarted therefoere wpa_supplicant also restarted,
248         // the country code counld be reset to '00' by wpa_supplicant.
249         if (country != null) {
250             setCountryCodeNative(country);
251         }
252         // We do not set country code if there is no candidate. This is reasonable
253         // because wpa_supplicant usually starts with an international safe country
254         // code setting: '00'.
255     }
256 
pickCountryCode()257     private String pickCountryCode() {
258 
259         initializeTelephonyCountryCodeIfNeeded();
260 
261         if (mTelephonyCountryCode != null) {
262             return mTelephonyCountryCode;
263         }
264         if (mDefaultCountryCode != null) {
265             return mDefaultCountryCode;
266         }
267         // If there is no candidate country code we will return null.
268         return null;
269     }
270 
setCountryCodeNative(String country)271     private boolean setCountryCodeNative(String country) {
272         mDriverCountryTimestamp = FORMATTER.format(new Date(System.currentTimeMillis()));
273         if (mWifiNative.setCountryCode(mWifiNative.getClientInterfaceName(), country)) {
274             Log.d(TAG, "Succeeded to set country code to: " + country);
275             mDriverCountryCode = country;
276             return true;
277         }
278         Log.d(TAG, "Failed to set country code to: " + country);
279         return false;
280     }
281 }
282 
283