1 /* 2 * Copyright (C) 2021 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.uwb; 18 19 import android.annotation.NonNull; 20 import android.content.BroadcastReceiver; 21 import android.content.Context; 22 import android.content.Intent; 23 import android.content.IntentFilter; 24 import android.content.pm.PackageManager; 25 import android.net.wifi.WifiManager; 26 import android.net.wifi.WifiManager.ActiveCountryCodeChangedCallback; 27 import android.os.Handler; 28 import android.telephony.TelephonyManager; 29 import android.text.TextUtils; 30 import android.util.ArraySet; 31 import android.util.Log; 32 33 import androidx.annotation.Nullable; 34 35 import com.android.internal.annotations.VisibleForTesting; 36 import com.android.modules.utils.HandlerExecutor; 37 import com.android.server.uwb.data.UwbUciConstants; 38 import com.android.server.uwb.jni.NativeUwbManager; 39 40 import java.io.FileDescriptor; 41 import java.io.PrintWriter; 42 import java.nio.charset.StandardCharsets; 43 import java.time.LocalDateTime; 44 import java.time.format.DateTimeFormatter; 45 import java.util.Locale; 46 import java.util.Objects; 47 import java.util.Set; 48 49 /** 50 * Provide functions for making changes to UWB country code. 51 * This Country Code is from MCC or phone default setting. This class sends Country Code 52 * to UWB venodr via the HAL. 53 */ 54 public class UwbCountryCode { 55 private static final String TAG = "UwbCountryCode"; 56 // To be used when there is no country code available. 57 @VisibleForTesting 58 public static final String DEFAULT_COUNTRY_CODE = "00"; 59 private static final DateTimeFormatter FORMATTER = 60 DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS"); 61 62 private final Context mContext; 63 private final Handler mHandler; 64 private final TelephonyManager mTelephonyManager; 65 private final NativeUwbManager mNativeUwbManager; 66 private final UwbInjector mUwbInjector; 67 private final Set<CountryCodeChangedListener> mListeners = new ArraySet<>(); 68 69 private String mTelephonyCountryCode = null; 70 private String mWifiCountryCode = null; 71 private String mOverrideCountryCode = null; 72 private String mCountryCode = null; 73 private String mCountryCodeUpdatedTimestamp = null; 74 private String mTelephonyCountryTimestamp = null; 75 private String mWifiCountryTimestamp = null; 76 77 public interface CountryCodeChangedListener { onCountryCodeChanged(@ullable String newCountryCode)78 void onCountryCodeChanged(@Nullable String newCountryCode); 79 } 80 UwbCountryCode( Context context, NativeUwbManager nativeUwbManager, Handler handler, UwbInjector uwbInjector)81 public UwbCountryCode( 82 Context context, NativeUwbManager nativeUwbManager, Handler handler, 83 UwbInjector uwbInjector) { 84 mContext = context; 85 mTelephonyManager = context.getSystemService(TelephonyManager.class); 86 mNativeUwbManager = nativeUwbManager; 87 mHandler = handler; 88 mUwbInjector = uwbInjector; 89 } 90 91 private class WifiCountryCodeCallback implements ActiveCountryCodeChangedCallback { onActiveCountryCodeChanged(@onNull String countryCode)92 public void onActiveCountryCodeChanged(@NonNull String countryCode) { 93 setWifiCountryCode(countryCode); 94 } 95 onCountryCodeInactive()96 public void onCountryCodeInactive() { 97 setWifiCountryCode(""); 98 } 99 } 100 101 /** 102 * Initialize the module. 103 */ initialize()104 public void initialize() { 105 mContext.registerReceiver( 106 new BroadcastReceiver() { 107 @Override 108 public void onReceive(Context context, Intent intent) { 109 String countryCode = intent.getStringExtra( 110 TelephonyManager.EXTRA_NETWORK_COUNTRY); 111 Log.d(TAG, "Country code changed to :" + countryCode); 112 setTelephonyCountryCode(countryCode); 113 } 114 }, 115 new IntentFilter(TelephonyManager.ACTION_NETWORK_COUNTRY_CHANGED), 116 null, mHandler); 117 if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WIFI)) { 118 mContext.getSystemService(WifiManager.class).registerActiveCountryCodeChangedCallback( 119 new HandlerExecutor(mHandler), new WifiCountryCodeCallback()); 120 } 121 122 Log.d(TAG, "Default country code from system property is " 123 + mUwbInjector.getOemDefaultCountryCode()); 124 setTelephonyCountryCode(mTelephonyManager.getNetworkCountryIso()); 125 // Current Wifi country code update is sent immediately on registration. 126 } 127 addListener(@onNull CountryCodeChangedListener listener)128 public void addListener(@NonNull CountryCodeChangedListener listener) { 129 mListeners.add(listener); 130 } 131 setTelephonyCountryCode(String countryCode)132 private boolean setTelephonyCountryCode(String countryCode) { 133 if (TextUtils.isEmpty(countryCode) 134 && !TextUtils.isEmpty(mTelephonyManager.getNetworkCountryIso())) { 135 Log.i(TAG, "Skip Telephony CC update to empty because there is " 136 + "an available CC from default active SIM"); 137 return false; 138 } 139 Log.d(TAG, "Set telephony country code to: " + countryCode); 140 mTelephonyCountryTimestamp = LocalDateTime.now().format(FORMATTER); 141 // Empty country code. 142 if (TextUtils.isEmpty(countryCode)) { 143 Log.d(TAG, "Received empty telephony country code, reset to default country code"); 144 mTelephonyCountryCode = null; 145 } else { 146 mTelephonyCountryCode = countryCode.toUpperCase(Locale.US); 147 } 148 return setCountryCode(false); 149 } 150 setWifiCountryCode(String countryCode)151 private boolean setWifiCountryCode(String countryCode) { 152 Log.d(TAG, "Set wifi country code to: " + countryCode); 153 mWifiCountryTimestamp = LocalDateTime.now().format(FORMATTER); 154 // Empty country code. 155 if (TextUtils.isEmpty(countryCode)) { 156 Log.d(TAG, "Received empty wifi country code, reset to default country code"); 157 mWifiCountryCode = null; 158 } else { 159 mWifiCountryCode = countryCode.toUpperCase(Locale.US); 160 } 161 return setCountryCode(false); 162 } 163 pickCountryCode()164 private String pickCountryCode() { 165 if (mOverrideCountryCode != null) { 166 return mOverrideCountryCode; 167 } 168 if (mTelephonyCountryCode != null) { 169 return mTelephonyCountryCode; 170 } 171 if (mWifiCountryCode != null) { 172 return mWifiCountryCode; 173 } 174 return mUwbInjector.getOemDefaultCountryCode(); 175 } 176 177 /** 178 * Set country code 179 * 180 * @param forceUpdate Force update the country code even if it was the same as previously cached 181 * value. 182 * @return true if the country code is set successfully, false otherwise. 183 */ setCountryCode(boolean forceUpdate)184 public boolean setCountryCode(boolean forceUpdate) { 185 String country = pickCountryCode(); 186 if (country == null) { 187 Log.i(TAG, "No valid country code, reset to " + DEFAULT_COUNTRY_CODE); 188 country = DEFAULT_COUNTRY_CODE; 189 } 190 if (!forceUpdate && Objects.equals(country, mCountryCode)) { 191 Log.i(TAG, "Ignoring already set country code: " + country); 192 return false; 193 } 194 Log.d(TAG, "setCountryCode to " + country); 195 int status = mNativeUwbManager.setCountryCode(country.getBytes(StandardCharsets.UTF_8)); 196 boolean success = (status == UwbUciConstants.STATUS_CODE_OK); 197 if (!success) { 198 Log.i(TAG, "Failed to set country code"); 199 return false; 200 } 201 mCountryCode = country; 202 mCountryCodeUpdatedTimestamp = LocalDateTime.now().format(FORMATTER); 203 for (CountryCodeChangedListener listener : mListeners) { 204 listener.onCountryCodeChanged(country); 205 } 206 return true; 207 } 208 209 /** 210 * Get country code 211 * 212 * @return true if the country code is set successfully, false otherwise. 213 */ getCountryCode()214 public String getCountryCode() { 215 return mCountryCode; 216 } 217 218 /** 219 * Is this a valid country code 220 * @param countryCode A 2-Character alphanumeric country code. 221 * @return true if the countryCode is valid, false otherwise. 222 */ isValid(String countryCode)223 public static boolean isValid(String countryCode) { 224 return countryCode != null && countryCode.length() == 2 225 && countryCode.chars().allMatch(Character::isLetterOrDigit); 226 } 227 228 /** 229 * This call will override any existing country code. 230 * This is for test purpose only and we should disallow any update from 231 * telephony in this mode. 232 * @param countryCode A 2-Character alphanumeric country code. 233 */ setOverrideCountryCode(String countryCode)234 public synchronized void setOverrideCountryCode(String countryCode) { 235 if (TextUtils.isEmpty(countryCode)) { 236 Log.d(TAG, "Fail to override country code because" 237 + "the received country code is empty"); 238 return; 239 } 240 mOverrideCountryCode = countryCode.toUpperCase(Locale.US); 241 setCountryCode(true); 242 } 243 244 /** 245 * This is for clearing the country code previously set through #setOverrideCountryCode() method 246 */ clearOverrideCountryCode()247 public synchronized void clearOverrideCountryCode() { 248 mOverrideCountryCode = null; 249 setCountryCode(true); 250 } 251 252 /** 253 * Method to dump the current state of this UwbCountryCode object. 254 */ dump(FileDescriptor fd, PrintWriter pw, String[] args)255 public synchronized void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 256 pw.println("DefaultCountryCode(system property): " 257 + mUwbInjector.getOemDefaultCountryCode()); 258 pw.println("mOverrideCountryCode: " + mOverrideCountryCode); 259 pw.println("mTelephonyCountryCode: " + mTelephonyCountryCode); 260 pw.println("mTelephonyCountryTimestamp: " + mTelephonyCountryTimestamp); 261 pw.println("mWifiCountryCode: " + mWifiCountryCode); 262 pw.println("mWifiCountryTimestamp: " + mWifiCountryTimestamp); 263 pw.println("mCountryCode: " + mCountryCode); 264 pw.println("mCountryCodeUpdatedTimestamp: " + mCountryCodeUpdatedTimestamp); 265 } 266 } 267