• 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 static com.android.server.uwb.data.UwbUciConstants.STATUS_CODE_OK;
20 
21 import android.annotation.NonNull;
22 import android.content.BroadcastReceiver;
23 import android.content.Context;
24 import android.content.ContextParams;
25 import android.content.Intent;
26 import android.content.IntentFilter;
27 import android.content.pm.PackageManager;
28 import android.location.Address;
29 import android.location.Geocoder;
30 import android.location.Location;
31 import android.location.LocationManager;
32 import android.net.wifi.WifiManager;
33 import android.net.wifi.WifiManager.ActiveCountryCodeChangedCallback;
34 import android.os.Handler;
35 import android.telephony.SubscriptionInfo;
36 import android.telephony.SubscriptionManager;
37 import android.telephony.TelephonyManager;
38 import android.text.TextUtils;
39 import android.util.ArrayMap;
40 import android.util.ArraySet;
41 import android.util.Log;
42 import android.util.Pair;
43 
44 import androidx.annotation.Nullable;
45 
46 import com.android.internal.annotations.Keep;
47 import com.android.internal.annotations.VisibleForTesting;
48 import com.android.modules.utils.HandlerExecutor;
49 import com.android.server.uwb.jni.NativeUwbManager;
50 
51 import java.io.FileDescriptor;
52 import java.io.PrintWriter;
53 import java.nio.charset.StandardCharsets;
54 import java.time.LocalDateTime;
55 import java.time.format.DateTimeFormatter;
56 import java.util.List;
57 import java.util.Locale;
58 import java.util.Map;
59 import java.util.Objects;
60 import java.util.Set;
61 import java.util.stream.Collectors;
62 
63 /**
64  * Provide functions for making changes to UWB country code.
65  * This Country Code is from MCC or phone default setting. This class sends Country Code
66  * to UWB venodr via the HAL.
67  */
68 public class UwbCountryCode {
69     private static final String TAG = "UwbCountryCode";
70     // To be used when there is no country code available.
71     @VisibleForTesting
72     public static final String DEFAULT_COUNTRY_CODE = "00";
73     private static final DateTimeFormatter FORMATTER =
74             DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS");
75     /**
76      * Copied from {@link TelephonyManager} because it's @hide.
77      * TODO (b/242326831): Use @SystemApi.
78      */
79     @VisibleForTesting
80     public static final String EXTRA_LAST_KNOWN_NETWORK_COUNTRY =
81             "android.telephony.extra.LAST_KNOWN_NETWORK_COUNTRY";
82 
83     // Wait 1 hour between updates
84     private static final long TIME_BETWEEN_UPDATES_MS = 1000L * 60 * 60 * 1;
85     // Minimum distance before an update is triggered, in meters. We don't need this to be too
86     // exact because all we care about is what country the user is in.
87     private static final float DISTANCE_BETWEEN_UPDATES_METERS = 5_000.0f;
88 
89     private final Context mContext;
90     private final Handler mHandler;
91     private final TelephonyManager mTelephonyManager;
92     private final SubscriptionManager mSubscriptionManager;
93     private final LocationManager mLocationManager;
94     private final Geocoder mGeocoder;
95     private final NativeUwbManager mNativeUwbManager;
96     private final UwbInjector mUwbInjector;
97     private final Set<CountryCodeChangedListener> mListeners = new ArraySet<>();
98 
99     private Map<Integer, TelephonyCountryCodeSlotInfo> mTelephonyCountryCodeInfoPerSlot =
100             new ArrayMap();
101     private String mWifiCountryCode = null;
102     private String mLocationCountryCode = null;
103     private String mOverrideCountryCode = null;
104     private String mCountryCode = null;
105     private String mCountryCodeUpdatedTimestamp = null;
106     private String mWifiCountryTimestamp = null;
107     private String mLocationCountryTimestamp = null;
108 
109     /**
110      * Container class to store country code per sim slot.
111      */
112     public static class TelephonyCountryCodeSlotInfo {
113         public int slotIdx;
114         public String countryCode;
115         public String lastKnownCountryCode;
116         public String timestamp;
117 
118         @Override
toString()119         public String toString() {
120             return "TelephonyCountryCodeSlotInfo[ slotIdx: " + slotIdx
121                     + ", countryCode: " + countryCode
122                     + ", lastKnownCountryCode: " + lastKnownCountryCode
123                     + ", timestamp: " + timestamp + "]";
124         }
125     }
126 
127     public interface CountryCodeChangedListener {
onCountryCodeChanged(@ullable String newCountryCode)128         void onCountryCodeChanged(@Nullable String newCountryCode);
129     }
130 
UwbCountryCode( Context context, NativeUwbManager nativeUwbManager, Handler handler, UwbInjector uwbInjector)131     public UwbCountryCode(
132             Context context, NativeUwbManager nativeUwbManager, Handler handler,
133             UwbInjector uwbInjector) {
134         mContext = context.createContext(
135                 new ContextParams.Builder().setAttributionTag(TAG).build());
136         mTelephonyManager = mContext.getSystemService(TelephonyManager.class);
137         mSubscriptionManager = mContext.getSystemService(SubscriptionManager.class);
138         mLocationManager = mContext.getSystemService(LocationManager.class);
139         mGeocoder = uwbInjector.makeGeocoder();
140         mNativeUwbManager = nativeUwbManager;
141         mHandler = handler;
142         mUwbInjector = uwbInjector;
143     }
144 
145     @Keep
146     private class WifiCountryCodeCallback implements ActiveCountryCodeChangedCallback {
onActiveCountryCodeChanged(@onNull String countryCode)147         public void onActiveCountryCodeChanged(@NonNull String countryCode) {
148             setWifiCountryCode(countryCode);
149         }
150 
onCountryCodeInactive()151         public void onCountryCodeInactive() {
152             setWifiCountryCode("");
153         }
154     }
155 
setCountryCodeFromGeocodingLocation(@ullable Location location)156     private void setCountryCodeFromGeocodingLocation(@Nullable Location location) {
157         if (location == null) return;
158         Geocoder.GeocodeListener geocodeListener = (List<Address> addresses) -> {
159             if (addresses != null && !addresses.isEmpty()) {
160                 String countryCode = addresses.get(0).getCountryCode();
161                 mHandler.post(() -> setLocationCountryCode(countryCode));
162             }
163         };
164         mGeocoder.getFromLocation(
165                 location.getLatitude(), location.getLongitude(), 1, geocodeListener);
166     }
167 
168     /**
169      * Initialize the module.
170      */
initialize()171     public void initialize() {
172         mContext.registerReceiver(
173                 new BroadcastReceiver() {
174                     @Override
175                     public void onReceive(Context context, Intent intent) {
176                         int slotIdx = intent.getIntExtra(
177                                 SubscriptionManager.EXTRA_SLOT_INDEX,
178                                 SubscriptionManager.INVALID_SIM_SLOT_INDEX);
179                         String countryCode = intent.getStringExtra(
180                                 TelephonyManager.EXTRA_NETWORK_COUNTRY);
181                         String lastKnownCountryCode = intent.getStringExtra(
182                                 EXTRA_LAST_KNOWN_NETWORK_COUNTRY);
183                         Log.d(TAG, "Country code changed to: " + countryCode);
184                         setTelephonyCountryCodeAndLastKnownCountryCode(
185                                 slotIdx, countryCode, lastKnownCountryCode);
186                     }
187                 },
188                 new IntentFilter(TelephonyManager.ACTION_NETWORK_COUNTRY_CHANGED),
189                 null, mHandler);
190         if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WIFI)) {
191             mContext.getSystemService(WifiManager.class).registerActiveCountryCodeChangedCallback(
192                     new HandlerExecutor(mHandler), new WifiCountryCodeCallback());
193         }
194         if (mUwbInjector.isGeocoderPresent()) {
195             mLocationManager.requestLocationUpdates(
196                     LocationManager.PASSIVE_PROVIDER,
197                     TIME_BETWEEN_UPDATES_MS,
198                     DISTANCE_BETWEEN_UPDATES_METERS,
199                     location -> setCountryCodeFromGeocodingLocation(location));
200 
201         }
202         Log.d(TAG, "Default country code from system property is "
203                 + mUwbInjector.getOemDefaultCountryCode());
204         List<SubscriptionInfo> subscriptionInfoList =
205                 mSubscriptionManager.getActiveSubscriptionInfoList();
206         if (subscriptionInfoList == null) return; // No sim
207         Set<Integer> slotIdxs = subscriptionInfoList
208                 .stream()
209                 .map(SubscriptionInfo::getSimSlotIndex)
210                 .collect(Collectors.toSet());
211         for (Integer slotIdx : slotIdxs) {
212             String countryCode;
213             try {
214                 countryCode = mTelephonyManager.getNetworkCountryIso(slotIdx);
215             } catch (IllegalArgumentException e) {
216                 Log.e(TAG, "Failed to get country code for slot id:" + slotIdx, e);
217                 continue;
218             }
219             setTelephonyCountryCodeAndLastKnownCountryCode(slotIdx, countryCode, null);
220         }
221         if (mUwbInjector.isGeocoderPresent()) {
222             setCountryCodeFromGeocodingLocation(
223                     mLocationManager.getLastKnownLocation(LocationManager.FUSED_PROVIDER));
224         }
225         // Current Wifi country code update is sent immediately on registration.
226     }
227 
addListener(@onNull CountryCodeChangedListener listener)228     public void addListener(@NonNull CountryCodeChangedListener listener) {
229         mListeners.add(listener);
230     }
231 
setTelephonyCountryCodeAndLastKnownCountryCode(int slotIdx, String countryCode, String lastKnownCountryCode)232     private void setTelephonyCountryCodeAndLastKnownCountryCode(int slotIdx, String countryCode,
233             String lastKnownCountryCode) {
234         Log.d(TAG, "Set telephony country code to: " + countryCode
235                 + ", last country code to: " + lastKnownCountryCode + " for slotIdx: " + slotIdx);
236         TelephonyCountryCodeSlotInfo telephonyCountryCodeInfoSlot =
237                 mTelephonyCountryCodeInfoPerSlot.computeIfAbsent(
238                         slotIdx, k -> new TelephonyCountryCodeSlotInfo());
239         telephonyCountryCodeInfoSlot.slotIdx = slotIdx;
240         telephonyCountryCodeInfoSlot.timestamp = LocalDateTime.now().format(FORMATTER);
241         // Empty country code.
242         if (TextUtils.isEmpty(countryCode)) {
243             Log.d(TAG, "Received empty telephony country code");
244             telephonyCountryCodeInfoSlot.countryCode = null;
245         } else {
246             telephonyCountryCodeInfoSlot.countryCode = countryCode.toUpperCase(Locale.US);
247         }
248         if (TextUtils.isEmpty(lastKnownCountryCode)) {
249             Log.d(TAG, "Received empty telephony last known country code");
250             telephonyCountryCodeInfoSlot.lastKnownCountryCode = null;
251         } else {
252             telephonyCountryCodeInfoSlot.lastKnownCountryCode =
253                     lastKnownCountryCode.toUpperCase(Locale.US);
254         }
255         setCountryCode(false);
256     }
257 
setWifiCountryCode(String countryCode)258     private void setWifiCountryCode(String countryCode) {
259         Log.d(TAG, "Set wifi country code to: " + countryCode);
260         mWifiCountryTimestamp = LocalDateTime.now().format(FORMATTER);
261         // Empty country code.
262         if (TextUtils.isEmpty(countryCode) || TextUtils.equals(countryCode, DEFAULT_COUNTRY_CODE)) {
263             Log.d(TAG, "Received empty wifi country code");
264             mWifiCountryCode = null;
265         } else {
266             mWifiCountryCode = countryCode.toUpperCase(Locale.US);
267         }
268         setCountryCode(false);
269     }
270 
setLocationCountryCode(String countryCode)271     private void setLocationCountryCode(String countryCode) {
272         Log.d(TAG, "Set location country code to: " + countryCode);
273         mLocationCountryTimestamp = LocalDateTime.now().format(FORMATTER);
274         // Empty country code.
275         if (TextUtils.isEmpty(countryCode) || TextUtils.equals(countryCode, DEFAULT_COUNTRY_CODE)) {
276             Log.d(TAG, "Received empty location country code");
277             mLocationCountryCode = null;
278         } else {
279             mLocationCountryCode = countryCode.toUpperCase(Locale.US);
280         }
281         setCountryCode(false);
282     }
283 
284     /**
285      * Priority order of country code sources (we stop at the first known country code source):
286      * 1. Override country code - Country code forced via shell command (local/automated testing)
287      * 2. Telephony country code - Current country code retrieved via cellular. If there are
288      * multiple SIM's, the country code chosen is non-deterministic if they return different codes.
289      * 3. Wifi country code - Current country code retrieved via wifi (via 80211.ad).
290      * 4. Last known telephony country code - Last known country code retrieved via cellular. If
291      * there are multiple SIM's, the country code chosen is non-deterministic if they return
292      * different codes.
293      * 5. Location Country code - Country code retrieved from LocationManager Fused location
294      * provider.
295      * 6. OEM default country code - If set by the OEM, then we default to this country code.
296      * @return
297      */
pickCountryCode()298     private String pickCountryCode() {
299         if (mOverrideCountryCode != null) {
300             return mOverrideCountryCode;
301         }
302         for (TelephonyCountryCodeSlotInfo telephonyCountryCodeInfoSlot :
303                 mTelephonyCountryCodeInfoPerSlot.values()) {
304             if (telephonyCountryCodeInfoSlot.countryCode != null) {
305                 return telephonyCountryCodeInfoSlot.countryCode;
306             }
307         }
308         if (mWifiCountryCode != null) {
309             return mWifiCountryCode;
310         }
311         for (TelephonyCountryCodeSlotInfo telephonyCountryCodeInfoSlot :
312                 mTelephonyCountryCodeInfoPerSlot.values()) {
313             if (telephonyCountryCodeInfoSlot.lastKnownCountryCode != null) {
314                 return telephonyCountryCodeInfoSlot.lastKnownCountryCode;
315             }
316         }
317         if (mLocationCountryCode != null) {
318             return mLocationCountryCode;
319         }
320         return mUwbInjector.getOemDefaultCountryCode();
321     }
322 
323     /**
324      * Set country code
325      *
326      * @param forceUpdate Force update the country code even if it was the same as previously cached
327      *                    value.
328      * @return Pair<UWBS StatusCode from setting the country code,
329      *              Country code that was attempted to be set in UWBS>
330      */
setCountryCode(boolean forceUpdate)331     public Pair<Integer, String> setCountryCode(boolean forceUpdate) {
332         String country = pickCountryCode();
333         if (country == null) {
334             Log.i(TAG, "No valid country code, reset to " + DEFAULT_COUNTRY_CODE);
335             country = DEFAULT_COUNTRY_CODE;
336         }
337         if (!forceUpdate && Objects.equals(country, mCountryCode)) {
338             Log.i(TAG, "Ignoring already set country code: " + country);
339             return new Pair<>(STATUS_CODE_OK, mCountryCode);
340         }
341         Log.d(TAG, "setCountryCode to " + country);
342         int status = mNativeUwbManager.setCountryCode(country.getBytes(StandardCharsets.UTF_8));
343         if (status != STATUS_CODE_OK) {
344             Log.i(TAG, "Failed to set country code, with status code: " + status);
345             return new Pair<>(status, country);
346         }
347         mCountryCode = country;
348         mCountryCodeUpdatedTimestamp = LocalDateTime.now().format(FORMATTER);
349         for (CountryCodeChangedListener listener : mListeners) {
350             listener.onCountryCodeChanged(country);
351         }
352         return new Pair<>(status, mCountryCode);
353     }
354 
355     /**
356      * Get country code
357      *
358      * @return true if the country code is set successfully, false otherwise.
359      */
getCountryCode()360     public String getCountryCode() {
361         return mCountryCode;
362     }
363 
364     /**
365      * Is this a valid country code
366      * @param countryCode A 2-Character alphanumeric country code.
367      * @return true if the countryCode is valid, false otherwise.
368      */
isValid(String countryCode)369     public static boolean isValid(String countryCode) {
370         return countryCode != null && countryCode.length() == 2
371                 && countryCode.chars().allMatch(Character::isLetterOrDigit)
372                 && !countryCode.equals(DEFAULT_COUNTRY_CODE);
373     }
374 
375     /**
376      * This call will override any existing country code.
377      * This is for test purpose only and we should disallow any update from
378      * telephony in this mode.
379      * @param countryCode A 2-Character alphanumeric country code.
380      */
setOverrideCountryCode(String countryCode)381     public synchronized void setOverrideCountryCode(String countryCode) {
382         if (TextUtils.isEmpty(countryCode)) {
383             Log.d(TAG, "Fail to override country code because"
384                     + "the received country code is empty");
385             return;
386         }
387         mOverrideCountryCode = countryCode.toUpperCase(Locale.US);
388         setCountryCode(true);
389     }
390 
391     /**
392      * This is for clearing the country code previously set through #setOverrideCountryCode() method
393      */
clearOverrideCountryCode()394     public synchronized void clearOverrideCountryCode() {
395         mOverrideCountryCode = null;
396         setCountryCode(true);
397     }
398 
399     /**
400      * Method to dump the current state of this UwbCountryCode object.
401      */
dump(FileDescriptor fd, PrintWriter pw, String[] args)402     public synchronized void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
403         pw.println("---- Dump of UwbCountryCode ----");
404         pw.println("DefaultCountryCode(system property): "
405                 + mUwbInjector.getOemDefaultCountryCode());
406         pw.println("mOverrideCountryCode: " + mOverrideCountryCode);
407         pw.println("mTelephonyCountryCodeInfoSlot: " + mTelephonyCountryCodeInfoPerSlot);
408         pw.println("mWifiCountryCode: " + mWifiCountryCode);
409         pw.println("mWifiCountryTimestamp: " + mWifiCountryTimestamp);
410         pw.println("mLocationCountryCode: " + mLocationCountryCode);
411         pw.println("mLocationCountryTimestamp: " + mLocationCountryTimestamp);
412         pw.println("mCountryCode: " + mCountryCode);
413         pw.println("mCountryCodeUpdatedTimestamp: " + mCountryCodeUpdatedTimestamp);
414         pw.println("---- Dump of UwbCountryCode ----");
415     }
416 }
417