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 isCrossSimCallingEnabled(Context context, int slotId)203 public static boolean isCrossSimCallingEnabled(Context context, int slotId) { 204 boolean isCstEnabled = 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 ImsMmTelManager imsMmTelManager = imsManager.getImsMmTelManager(subid); 215 if (imsMmTelManager != null) { 216 try { 217 isCstEnabled = imsMmTelManager.isCrossSimCallingEnabled(); 218 } catch (Exception e) { 219 // Fail to query Cross-SIM calling setting, just return false to avoid an 220 // exception. 221 } 222 } 223 } 224 return isCstEnabled; 225 } 226 startCountryDetector(Context context)227 public static void startCountryDetector(Context context) { 228 mCountryDetector = context.getSystemService(CountryDetector.class); 229 if (mCountryDetector != null) { 230 updateCountryCodeFromCountryDetector(mCountryDetector.detectCountry()); 231 232 mCountryDetector.addCountryListener( 233 (newCountry) -> { 234 updateCountryCodeFromCountryDetector(newCountry); 235 }, 236 null); 237 } 238 } 239 240 @NonNull getLastKnownCountryCode(Context context)241 public static String getLastKnownCountryCode(Context context) { 242 final SharedPreferences prefs = 243 context.getSharedPreferences(LAST_KNOWN_COUNTRY_CODE_KEY, Context.MODE_PRIVATE); 244 return prefs.getString(LAST_KNOWN_COUNTRY_CODE_KEY, ""); 245 } 246 updateCountryCodeWhenNetworkConnected()247 public static void updateCountryCodeWhenNetworkConnected() { 248 if (mCountryDetector != null) { 249 updateCountryCodeFromCountryDetector(mCountryDetector.detectCountry()); 250 } 251 } 252 updateLastKnownCountryCode(String countryCode)253 private static void updateLastKnownCountryCode(String countryCode) { 254 Context context = IwlanDataService.getContext(); 255 final SharedPreferences prefs = 256 context.getSharedPreferences(LAST_KNOWN_COUNTRY_CODE_KEY, Context.MODE_PRIVATE); 257 final SharedPreferences.Editor editor = prefs.edit(); 258 editor.putString(LAST_KNOWN_COUNTRY_CODE_KEY, countryCode); 259 editor.commit(); 260 Log.d(TAG, "Update the last known country code in sharedPrefs " + countryCode); 261 } 262 updateCountryCodeFromCountryDetector(Country country)263 private static void updateCountryCodeFromCountryDetector(Country country) { 264 if (country == null) { 265 return; 266 } 267 268 if (country.getSource() == Country.COUNTRY_SOURCE_NETWORK 269 || country.getSource() == Country.COUNTRY_SOURCE_LOCATION) { 270 Context context = IwlanDataService.getContext(); 271 String newCountryCode = country.getCountryIso(); 272 String lastKnownCountryCode = getLastKnownCountryCode(context); 273 if (!TextUtils.isEmpty(newCountryCode) 274 && (TextUtils.isEmpty(lastKnownCountryCode) 275 || !lastKnownCountryCode.equalsIgnoreCase(newCountryCode))) { 276 updateLastKnownCountryCode(newCountryCode); 277 } 278 } 279 } 280 elapsedRealtime()281 static long elapsedRealtime() { 282 /*Returns milliseconds since boot, including time spent in sleep.*/ 283 return SystemClock.elapsedRealtime(); 284 } 285 } 286