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