• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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