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