1 /* 2 * Copyright 2020 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.google.android.iwlan; 18 19 import android.annotation.NonNull; 20 import android.content.Context; 21 import android.content.SharedPreferences; 22 import android.location.Country; 23 import android.location.CountryDetector; 24 import android.net.ConnectivityManager; 25 import android.net.IpPrefix; 26 import android.net.LinkAddress; 27 import android.net.LinkProperties; 28 import android.net.Network; 29 import android.os.SystemClock; 30 import android.telephony.SubscriptionInfo; 31 import android.telephony.SubscriptionManager; 32 import android.telephony.TelephonyManager; 33 import android.telephony.ims.ImsManager; 34 import android.telephony.ims.ImsMmTelManager; 35 import android.text.TextUtils; 36 import android.util.Log; 37 38 import java.net.Inet4Address; 39 import java.net.Inet6Address; 40 import java.net.InetAddress; 41 import java.nio.charset.StandardCharsets; 42 import java.util.ArrayList; 43 import java.util.List; 44 45 public class IwlanHelper { 46 47 private static final String TAG = IwlanHelper.class.getSimpleName(); 48 private static CountryDetector mCountryDetector; 49 private static final String LAST_KNOWN_COUNTRY_CODE_KEY = "last_known_country_code"; 50 private static IpPrefix mNat64Prefix = new IpPrefix("64:ff9b::/96"); 51 getNai(Context context, int slotId, byte[] nextReauthId)52 public static String getNai(Context context, int slotId, byte[] nextReauthId) { 53 if (nextReauthId != null) { 54 return new String(nextReauthId, StandardCharsets.UTF_8); 55 } 56 57 StringBuilder naiBuilder = new StringBuilder(); 58 TelephonyManager tm = context.getSystemService(TelephonyManager.class); 59 SubscriptionInfo subInfo = null; 60 tm = tm.createForSubscriptionId(getSubId(context, slotId)); 61 62 try { 63 subInfo = getSubInfo(context, slotId); 64 } catch (IllegalStateException e) { 65 return null; 66 } 67 68 String mnc = subInfo.getMncString(); 69 mnc = (mnc.length() == 2) ? '0' + mnc : mnc; 70 71 naiBuilder.append('0').append(tm.getSubscriberId()).append('@'); 72 73 return naiBuilder 74 .append("nai.epc.mnc") 75 .append(mnc) 76 .append(".mcc") 77 .append(subInfo.getMccString()) 78 .append(".3gppnetwork.org") 79 .toString(); 80 } 81 getSubId(Context context, int slotId)82 public static int getSubId(Context context, int slotId) { 83 int subid = SubscriptionManager.INVALID_SUBSCRIPTION_ID; 84 85 try { 86 subid = getSubInfo(context, slotId).getSubscriptionId(); 87 } catch (IllegalStateException e) { 88 subid = SubscriptionManager.INVALID_SUBSCRIPTION_ID; 89 } 90 return subid; 91 } 92 getCarrierId(Context context, int slotId)93 public static int getCarrierId(Context context, int slotId) { 94 TelephonyManager tm = context.getSystemService(TelephonyManager.class); 95 tm = tm.createForSubscriptionId(IwlanHelper.getSubId(context, slotId)); 96 return tm.getSimCarrierId(); 97 } 98 getSubInfo(Context context, int slotId)99 private static SubscriptionInfo getSubInfo(Context context, int slotId) 100 throws IllegalStateException { 101 SubscriptionManager sm = context.getSystemService(SubscriptionManager.class); 102 SubscriptionInfo info = sm.getActiveSubscriptionInfoForSimSlotIndex(slotId); 103 104 if (info == null) { 105 throw new IllegalStateException("Subscription info is null."); 106 } 107 108 return info; 109 } 110 111 /** 112 * Retrieves all IP addresses of a Network, including stacked IPv4 addresses. 113 * 114 * @param context a valid {@link Context} instance. 115 * @param network the network for which IP addresses are to be retrieved. 116 * @return a list of all IP addresses for the specified network. Returns an empty list if none 117 * are found. 118 */ getAllAddressesForNetwork(Context context, Network network)119 public static List<InetAddress> getAllAddressesForNetwork(Context context, Network network) { 120 ConnectivityManager connectivityManager = 121 context.getSystemService(ConnectivityManager.class); 122 List<InetAddress> gatewayList = new ArrayList<>(); 123 if (network != null) { 124 LinkProperties linkProperties = connectivityManager.getLinkProperties(network); 125 if (linkProperties != null) { 126 for (LinkAddress linkAddr : linkProperties.getAllLinkAddresses()) { 127 InetAddress inetAddr = linkAddr.getAddress(); 128 // skip linklocal and loopback addresses 129 if (!inetAddr.isLoopbackAddress() && !inetAddr.isLinkLocalAddress()) { 130 gatewayList.add(inetAddr); 131 } 132 } 133 if (linkProperties.getNat64Prefix() != null) { 134 mNat64Prefix = linkProperties.getNat64Prefix(); 135 } 136 } 137 } 138 return gatewayList; 139 } 140 141 /** 142 * The method is to check if this IP address is an IPv4-embedded IPv6 address(Pref64::/n). 143 * 144 * @param ipAddress IP address 145 * @return True if it is an IPv4-embedded IPv6 addres, otherwise false. 146 */ isIpv4EmbeddedIpv6Address(@onNull InetAddress ipAddress)147 public static boolean isIpv4EmbeddedIpv6Address(@NonNull InetAddress ipAddress) { 148 return (ipAddress instanceof Inet6Address) && mNat64Prefix.contains(ipAddress); 149 } 150 hasIpv6Address(List<InetAddress> localAddresses)151 public static boolean hasIpv6Address(List<InetAddress> localAddresses) { 152 if (localAddresses != null) { 153 for (InetAddress address : localAddresses) { 154 if (address instanceof Inet6Address) { 155 return true; 156 } 157 } 158 } 159 return false; 160 } 161 hasIpv4Address(List<InetAddress> localAddresses)162 public static boolean hasIpv4Address(List<InetAddress> localAddresses) { 163 if (localAddresses != null) { 164 for (InetAddress address : localAddresses) { 165 if (address instanceof Inet4Address) { 166 return true; 167 } 168 } 169 } 170 return false; 171 } 172 getIpv4Address(List<InetAddress> localAddresses)173 public static InetAddress getIpv4Address(List<InetAddress> localAddresses) { 174 for (InetAddress address : localAddresses) { 175 if (address instanceof Inet4Address) { 176 return address; 177 } 178 } 179 180 throw new IllegalStateException("Local address should not be null."); 181 } 182 getIpv6Address(List<InetAddress> localAddresses)183 public static InetAddress getIpv6Address(List<InetAddress> localAddresses) { 184 for (InetAddress address : localAddresses) { 185 if (address instanceof Inet6Address) { 186 return address; 187 } 188 } 189 190 throw new IllegalStateException("Local address should not be null."); 191 } 192 isDefaultDataSlot(Context context, int slotId)193 public static boolean isDefaultDataSlot(Context context, int slotId) { 194 int ddsSlotId = 195 SubscriptionManager.getSlotIndex( 196 SubscriptionManager.getDefaultDataSubscriptionId()); 197 if (ddsSlotId != SubscriptionManager.INVALID_SIM_SLOT_INDEX) { 198 return ddsSlotId == slotId; 199 } 200 return false; 201 } 202 isWfcEnabled(Context context, int slotId)203 static boolean isWfcEnabled(Context context, int slotId) { 204 boolean isWfcEnabled = false; 205 int subId = getSubId(context, slotId); 206 207 if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) { 208 // Fail to query subscription id, just return false. 209 return false; 210 } 211 212 ImsManager imsManager = context.getSystemService(ImsManager.class); 213 if (imsManager != null) { 214 return false; 215 } 216 217 try { 218 isWfcEnabled = imsManager.getImsMmTelManager(subId).isVoWiFiSettingEnabled(); 219 } catch (Exception e) { 220 Log.e(TAG, "Fail to query Wi-Fi calling setting"); 221 } 222 return isWfcEnabled; 223 } 224 isCrossSimCallingEnabled(Context context, int slotId)225 public static boolean isCrossSimCallingEnabled(Context context, int slotId) { 226 boolean isCstEnabled = false; 227 int subid = getSubId(context, slotId); 228 229 if (subid == SubscriptionManager.INVALID_SUBSCRIPTION_ID) { 230 // Fail to query subscription id, just return false. 231 return false; 232 } 233 234 ImsManager imsManager = context.getSystemService(ImsManager.class); 235 if (imsManager != null) { 236 ImsMmTelManager imsMmTelManager = imsManager.getImsMmTelManager(subid); 237 if (imsMmTelManager != null) { 238 try { 239 isCstEnabled = imsMmTelManager.isCrossSimCallingEnabled(); 240 } catch (Exception e) { 241 // Fail to query Cross-SIM calling setting, just return false to avoid an 242 // exception. 243 } 244 } 245 } 246 return isCstEnabled; 247 } 248 startCountryDetector(Context context)249 public static void startCountryDetector(Context context) { 250 mCountryDetector = context.getSystemService(CountryDetector.class); 251 if (mCountryDetector != null) { 252 updateCountryCodeFromCountryDetector(mCountryDetector.detectCountry()); 253 254 mCountryDetector.addCountryListener( 255 (newCountry) -> { 256 updateCountryCodeFromCountryDetector(newCountry); 257 }, 258 null); 259 } 260 } 261 262 @NonNull getLastKnownCountryCode(Context context)263 public static String getLastKnownCountryCode(Context context) { 264 final SharedPreferences prefs = 265 context.getSharedPreferences(LAST_KNOWN_COUNTRY_CODE_KEY, Context.MODE_PRIVATE); 266 return prefs.getString(LAST_KNOWN_COUNTRY_CODE_KEY, ""); 267 } 268 updateCountryCodeWhenNetworkConnected()269 public static void updateCountryCodeWhenNetworkConnected() { 270 if (mCountryDetector != null) { 271 updateCountryCodeFromCountryDetector(mCountryDetector.detectCountry()); 272 } 273 } 274 updateLastKnownCountryCode(String countryCode)275 private static void updateLastKnownCountryCode(String countryCode) { 276 Context context = IwlanDataService.getContext(); 277 final SharedPreferences prefs = 278 context.getSharedPreferences(LAST_KNOWN_COUNTRY_CODE_KEY, Context.MODE_PRIVATE); 279 final SharedPreferences.Editor editor = prefs.edit(); 280 editor.putString(LAST_KNOWN_COUNTRY_CODE_KEY, countryCode); 281 editor.commit(); 282 Log.d(TAG, "Update the last known country code in sharedPrefs " + countryCode); 283 } 284 updateCountryCodeFromCountryDetector(Country country)285 private static void updateCountryCodeFromCountryDetector(Country country) { 286 if (country == null) { 287 return; 288 } 289 290 if (country.getSource() == Country.COUNTRY_SOURCE_NETWORK 291 || country.getSource() == Country.COUNTRY_SOURCE_LOCATION) { 292 Context context = IwlanDataService.getContext(); 293 String newCountryCode = country.getCountryIso(); 294 String lastKnownCountryCode = getLastKnownCountryCode(context); 295 if (!TextUtils.isEmpty(newCountryCode) 296 && (TextUtils.isEmpty(lastKnownCountryCode) 297 || !lastKnownCountryCode.equalsIgnoreCase(newCountryCode))) { 298 updateLastKnownCountryCode(newCountryCode); 299 } 300 } 301 } 302 elapsedRealtime()303 public static long elapsedRealtime() { 304 /*Returns milliseconds since boot, including time spent in sleep.*/ 305 return SystemClock.elapsedRealtime(); 306 } 307 } 308