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