1 /*
2  * Copyright (C) 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.android.server.location.gnss;
18 
19 import android.content.Context;
20 import android.net.ConnectivityManager;
21 import android.net.LinkAddress;
22 import android.net.LinkProperties;
23 import android.net.Network;
24 import android.net.NetworkCapabilities;
25 import android.net.NetworkInfo;
26 import android.net.NetworkRequest;
27 import android.os.Handler;
28 import android.os.Looper;
29 import android.os.PowerManager;
30 import android.telephony.PhoneStateListener;
31 import android.telephony.PreciseCallState;
32 import android.telephony.SubscriptionInfo;
33 import android.telephony.SubscriptionManager;
34 import android.telephony.TelephonyManager;
35 import android.util.Log;
36 
37 import com.android.internal.location.GpsNetInitiatedHandler;
38 
39 import java.net.Inet4Address;
40 import java.net.Inet6Address;
41 import java.net.InetAddress;
42 import java.net.UnknownHostException;
43 import java.util.Arrays;
44 import java.util.HashMap;
45 import java.util.HashSet;
46 import java.util.Iterator;
47 import java.util.List;
48 import java.util.Map;
49 
50 
51 /**
52  * Handles network connection requests and network state change updates for AGPS data download.
53  */
54 class GnssNetworkConnectivityHandler {
55     static final String TAG = "GnssNetworkConnectivityHandler";
56 
57     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
58     private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
59 
60     // for mAGpsDataConnectionState
61     private static final int AGPS_DATA_CONNECTION_CLOSED = 0;
62     private static final int AGPS_DATA_CONNECTION_OPENING = 1;
63     private static final int AGPS_DATA_CONNECTION_OPEN = 2;
64 
65     // these need to match AGnssStatusValue enum in IAGnssCallback.hal
66     /** AGPS status event values. */
67     private static final int GPS_REQUEST_AGPS_DATA_CONN = 1;
68     private static final int GPS_RELEASE_AGPS_DATA_CONN = 2;
69     private static final int GPS_AGPS_DATA_CONNECTED = 3;
70     private static final int GPS_AGPS_DATA_CONN_DONE = 4;
71     private static final int GPS_AGPS_DATA_CONN_FAILED = 5;
72 
73     // these must match the ApnIpType enum in IAGnss.hal
74     private static final int APN_INVALID = 0;
75     private static final int APN_IPV4 = 1;
76     private static final int APN_IPV6 = 2;
77     private static final int APN_IPV4V6 = 3;
78 
79     // these must match the NetworkCapability enum flags in IAGnssRil.hal
80     private static final int AGNSS_NET_CAPABILITY_NOT_METERED = 1 << 0;
81     private static final int AGNSS_NET_CAPABILITY_NOT_ROAMING = 1 << 1;
82 
83     // these need to match AGnssType enum in IAGnssCallback.hal
84     public static final int AGPS_TYPE_SUPL = 1;
85     public static final int AGPS_TYPE_C2K = 2;
86     private static final int AGPS_TYPE_EIMS = 3;
87     private static final int AGPS_TYPE_IMS = 4;
88 
89     // Default time limit in milliseconds for the ConnectivityManager to find a suitable
90     // network with SUPL connectivity or report an error.
91     private static final int SUPL_NETWORK_REQUEST_TIMEOUT_MILLIS = 20 * 1000;
92 
93     private static final int HASH_MAP_INITIAL_CAPACITY_TO_TRACK_CONNECTED_NETWORKS = 5;
94 
95     // Keeps track of networks and their state as notified by the network request callbacks.
96     // Limit initial capacity to 5 as the number of connected networks will likely be small.
97     // NOTE: Must be accessed/modified only through the mHandler thread.
98     private HashMap<Network, NetworkAttributes> mAvailableNetworkAttributes =
99             new HashMap<>(HASH_MAP_INITIAL_CAPACITY_TO_TRACK_CONNECTED_NETWORKS);
100 
101     // Phone State Listeners to track all the active sub IDs
102     private HashMap<Integer, SubIdPhoneStateListener> mPhoneStateListeners;
103 
104     private final ConnectivityManager mConnMgr;
105 
106     private final Handler mHandler;
107     private final GnssNetworkListener mGnssNetworkListener;
108 
109     private int mAGpsDataConnectionState;
110     private InetAddress mAGpsDataConnectionIpAddr;
111     private int mAGpsType;
112     private int mActiveSubId = -1;
113     private final GpsNetInitiatedHandler mNiHandler;
114 
115 
116     private final Context mContext;
117 
118     // Wakelocks
119     private static final String WAKELOCK_KEY = "GnssNetworkConnectivityHandler";
120     private static final long WAKELOCK_TIMEOUT_MILLIS = 60 * 1000;
121     private final PowerManager.WakeLock mWakeLock;
122 
123     /**
124      * Network attributes needed when updating HAL about network connectivity status changes.
125      */
126     private static class NetworkAttributes {
127         private NetworkCapabilities mCapabilities;
128         private String mApn;
129         private int mType = ConnectivityManager.TYPE_NONE;
130 
131         /**
132          * Returns true if the capabilities that we pass on to HAL change between {@curCapabilities}
133          * and {@code newCapabilities}.
134          */
hasCapabilitiesChanged(NetworkCapabilities curCapabilities, NetworkCapabilities newCapabilities)135         private static boolean hasCapabilitiesChanged(NetworkCapabilities curCapabilities,
136                 NetworkCapabilities newCapabilities) {
137             if (curCapabilities == null || newCapabilities == null) {
138                 return true;
139             }
140 
141             // Monitor for roaming and metered capability changes.
142             return hasCapabilityChanged(curCapabilities, newCapabilities,
143                     NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING)
144                     || hasCapabilityChanged(curCapabilities, newCapabilities,
145                     NetworkCapabilities.NET_CAPABILITY_NOT_METERED);
146         }
147 
hasCapabilityChanged(NetworkCapabilities curCapabilities, NetworkCapabilities newCapabilities, int capability)148         private static boolean hasCapabilityChanged(NetworkCapabilities curCapabilities,
149                 NetworkCapabilities newCapabilities, int capability) {
150             return curCapabilities.hasCapability(capability)
151                     != newCapabilities.hasCapability(capability);
152         }
153 
getCapabilityFlags(NetworkCapabilities capabilities)154         private static short getCapabilityFlags(NetworkCapabilities capabilities) {
155             short capabilityFlags = 0;
156             if (capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING)) {
157                 capabilityFlags |= AGNSS_NET_CAPABILITY_NOT_ROAMING;
158             }
159             if (capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED)) {
160                 capabilityFlags |= AGNSS_NET_CAPABILITY_NOT_METERED;
161             }
162             return capabilityFlags;
163         }
164     }
165 
166     /**
167      * Callback used to listen for data connectivity changes.
168      */
169     private ConnectivityManager.NetworkCallback mNetworkConnectivityCallback;
170 
171     /**
172      * Callback used to listen for availability of a requested SUPL connection.
173      * It is kept as a separate instance from {@link #mNetworkConnectivityCallback} to be able to
174      * manage the registration/un-registration lifetimes separately.
175      */
176     private ConnectivityManager.NetworkCallback mSuplConnectivityCallback;
177 
178     /**
179      * Interface to listen for network availability changes.
180      */
181     interface GnssNetworkListener {
onNetworkAvailable()182         void onNetworkAvailable();
183     }
184 
GnssNetworkConnectivityHandler(Context context, GnssNetworkListener gnssNetworkListener, Looper looper, GpsNetInitiatedHandler niHandler)185     GnssNetworkConnectivityHandler(Context context,
186             GnssNetworkListener gnssNetworkListener,
187             Looper looper,
188             GpsNetInitiatedHandler niHandler) {
189         mContext = context;
190         mGnssNetworkListener = gnssNetworkListener;
191 
192     SubscriptionManager subManager = mContext.getSystemService(SubscriptionManager.class);
193         if (subManager != null) {
194             subManager.addOnSubscriptionsChangedListener(mOnSubscriptionsChangeListener);
195         }
196 
197         PowerManager powerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
198         mWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, WAKELOCK_KEY);
199 
200         mHandler = new Handler(looper);
201         mNiHandler = niHandler;
202         mConnMgr = (ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
203         mSuplConnectivityCallback = createSuplConnectivityCallback();
204     }
205 
206     /**
207      * SubId Phone State Listener is used cache the last active Sub ID when a call is made,
208      * which will be used during an emergency call to set the Network Specifier to the particular
209      * sub when an emergency supl connection is requested
210      */
211     private final class SubIdPhoneStateListener extends PhoneStateListener {
212         private Integer mSubId;
SubIdPhoneStateListener(Integer subId)213         SubIdPhoneStateListener(Integer subId) {
214             mSubId = subId;
215         }
216         @Override
onPreciseCallStateChanged(PreciseCallState state)217         public void onPreciseCallStateChanged(PreciseCallState state) {
218             if (PreciseCallState.PRECISE_CALL_STATE_ACTIVE == state.getForegroundCallState()
219                     || PreciseCallState.PRECISE_CALL_STATE_DIALING
220                     == state.getForegroundCallState()) {
221                 mActiveSubId = mSubId;
222                 if (DEBUG) Log.d(TAG, "mActiveSubId: " + mActiveSubId);
223             }
224         }
225     };
226 
227     /**
228      * Subscription Changed Listener is used to get all active subscriptions and create a
229      * Phone State Listener for each Sub ID that we find in the active subscription list
230      */
231     private final SubscriptionManager.OnSubscriptionsChangedListener mOnSubscriptionsChangeListener
232             = new SubscriptionManager.OnSubscriptionsChangedListener() {
233         @Override
234         public void onSubscriptionsChanged() {
235             if (mPhoneStateListeners == null) {
236                 // Capacity=2 Load-Factor=1.0, as typically no more than 2 SIMs
237                 mPhoneStateListeners = new HashMap<Integer, SubIdPhoneStateListener>(2,1);
238             }
239             SubscriptionManager subManager = mContext.getSystemService(SubscriptionManager.class);
240             TelephonyManager telManager = mContext.getSystemService(TelephonyManager.class);
241             if (subManager != null && telManager != null) {
242                 List<SubscriptionInfo> subscriptionInfoList =
243                         subManager.getActiveSubscriptionInfoList();
244                 HashSet<Integer> activeSubIds = new HashSet<Integer>();
245                 if (subscriptionInfoList != null) {
246                     if (DEBUG) Log.d(TAG, "Active Sub List size: " + subscriptionInfoList.size());
247                     // populate phone state listeners with all new active subs
248                     for (SubscriptionInfo subInfo : subscriptionInfoList) {
249                         activeSubIds.add(subInfo.getSubscriptionId());
250                         if (!mPhoneStateListeners.containsKey(subInfo.getSubscriptionId())) {
251                             TelephonyManager subIdTelManager =
252                                     telManager.createForSubscriptionId(subInfo.getSubscriptionId());
253                             if (subIdTelManager != null) {
254                                 if (DEBUG) Log.d(TAG, "Listener sub" + subInfo.getSubscriptionId());
255                                 SubIdPhoneStateListener subIdPhoneStateListener =
256                                         new SubIdPhoneStateListener(subInfo.getSubscriptionId());
257                                 mPhoneStateListeners.put(subInfo.getSubscriptionId(),
258                                         subIdPhoneStateListener);
259                                 subIdTelManager.listen(subIdPhoneStateListener,
260                                         PhoneStateListener.LISTEN_PRECISE_CALL_STATE);
261                             }
262                         }
263                     }
264                 }
265                 // clean up phone state listeners than no longer have active subs
266                 Iterator<Map.Entry<Integer, SubIdPhoneStateListener> > iterator =
267                         mPhoneStateListeners.entrySet().iterator();
268                 while (iterator.hasNext()) {
269                     Map.Entry<Integer, SubIdPhoneStateListener> element = iterator.next();
270                     if (!activeSubIds.contains(element.getKey())) {
271                         TelephonyManager subIdTelManager =
272                                 telManager.createForSubscriptionId(element.getKey());
273                         if (subIdTelManager != null) {
274                             if (DEBUG) Log.d(TAG, "unregister listener sub " + element.getKey());
275                             subIdTelManager.listen(element.getValue(),
276                                                    PhoneStateListener.LISTEN_NONE);
277                             // removes the element from mPhoneStateListeners
278                             iterator.remove();
279                         } else {
280                             Log.e(TAG, "Telephony Manager for Sub " + element.getKey() + " null");
281                         }
282                     }
283                 }
284                 // clean up cached active phone call sub if it is no longer an active sub
285                 if (!activeSubIds.contains(mActiveSubId)) {
286                     mActiveSubId = -1;
287                 }
288             }
289         }
290     };
291 
registerNetworkCallbacks()292     void registerNetworkCallbacks() {
293         // register for connectivity change events.
294         NetworkRequest.Builder networkRequestBuilder = new NetworkRequest.Builder();
295         networkRequestBuilder.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET);
296         networkRequestBuilder.addCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED);
297         networkRequestBuilder.removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN);
298         NetworkRequest networkRequest = networkRequestBuilder.build();
299         mNetworkConnectivityCallback = createNetworkConnectivityCallback();
300         mConnMgr.registerNetworkCallback(networkRequest, mNetworkConnectivityCallback, mHandler);
301     }
302 
303     /**
304      * @return {@code true} if there is a data network available for outgoing connections,
305      * {@code false} otherwise.
306      */
isDataNetworkConnected()307     boolean isDataNetworkConnected() {
308         NetworkInfo activeNetworkInfo = mConnMgr.getActiveNetworkInfo();
309         return activeNetworkInfo != null && activeNetworkInfo.isConnected();
310     }
311 
312     /**
313      * Called from native code to update AGPS connection status, or to request or release a SUPL
314      * connection.
315      *
316      * <p>Note: {@code suplIpAddr} parameter is not present from IAGnssCallback.hal@2.0 onwards
317      * and is set to {@code null}.
318      */
onReportAGpsStatus(int agpsType, int agpsStatus, byte[] suplIpAddr)319     void onReportAGpsStatus(int agpsType, int agpsStatus, byte[] suplIpAddr) {
320         if (DEBUG) Log.d(TAG, "AGPS_DATA_CONNECTION: " + agpsDataConnStatusAsString(agpsStatus));
321         switch (agpsStatus) {
322             case GPS_REQUEST_AGPS_DATA_CONN:
323                 runOnHandler(() -> handleRequestSuplConnection(agpsType, suplIpAddr));
324                 break;
325             case GPS_RELEASE_AGPS_DATA_CONN:
326                 runOnHandler(() -> handleReleaseSuplConnection(GPS_RELEASE_AGPS_DATA_CONN));
327                 break;
328             case GPS_AGPS_DATA_CONNECTED:
329             case GPS_AGPS_DATA_CONN_DONE:
330             case GPS_AGPS_DATA_CONN_FAILED:
331                 break;
332             default:
333                 Log.w(TAG, "Received unknown AGPS status: " + agpsStatus);
334         }
335     }
336 
createNetworkConnectivityCallback()337     private ConnectivityManager.NetworkCallback createNetworkConnectivityCallback() {
338         return new ConnectivityManager.NetworkCallback() {
339             // Used to filter out network capabilities changes that we are not interested in.
340             // NOTE: Not using a ConcurrentHashMap and also not using locking around updates
341             //       and access to the map object because it is all done inside the same
342             //       handler thread invoking the callback methods.
343             private HashMap<Network, NetworkCapabilities>
344                     mAvailableNetworkCapabilities = new HashMap<>(
345                     HASH_MAP_INITIAL_CAPACITY_TO_TRACK_CONNECTED_NETWORKS);
346 
347             @Override
348             public void onCapabilitiesChanged(Network network,
349                     NetworkCapabilities capabilities) {
350                 // This callback is invoked for any change in the network capabilities including
351                 // initial availability, and changes while still available. Only process if the
352                 // capabilities that we pass on to HAL change.
353                 if (!NetworkAttributes.hasCapabilitiesChanged(
354                         mAvailableNetworkCapabilities.get(network), capabilities)) {
355                     if (VERBOSE) {
356                         Log.v(TAG, "Relevant network capabilities unchanged. Capabilities: "
357                                 + capabilities);
358                     }
359                     return;
360                 }
361 
362                 mAvailableNetworkCapabilities.put(network, capabilities);
363                 if (DEBUG) {
364                     Log.d(TAG, "Network connected/capabilities updated. Available networks count: "
365                             + mAvailableNetworkCapabilities.size());
366                 }
367 
368                 mGnssNetworkListener.onNetworkAvailable();
369 
370                 // Always on, notify HAL so it can get data it needs
371                 handleUpdateNetworkState(network, true, capabilities);
372             }
373 
374             @Override
375             public void onLost(Network network) {
376                 if (mAvailableNetworkCapabilities.remove(network) == null) {
377                     Log.w(TAG, "Incorrectly received network callback onLost() before"
378                             + " onCapabilitiesChanged() for network: " + network);
379                     return;
380                 }
381 
382                 Log.i(TAG, "Network connection lost. Available networks count: "
383                         + mAvailableNetworkCapabilities.size());
384                 handleUpdateNetworkState(network, false, null);
385             }
386         };
387     }
388 
createSuplConnectivityCallback()389     private ConnectivityManager.NetworkCallback createSuplConnectivityCallback() {
390         return new ConnectivityManager.NetworkCallback() {
391             @Override
392             public void onLinkPropertiesChanged(Network network, LinkProperties linkProperties) {
393                 if (DEBUG) Log.d(TAG, "SUPL network connection available.");
394                 // Specific to a change to a SUPL enabled network becoming ready
395                 handleSuplConnectionAvailable(network, linkProperties);
396             }
397 
398             @Override
399             public void onLost(Network network) {
400                 Log.i(TAG, "SUPL network connection lost.");
401                 handleReleaseSuplConnection(GPS_RELEASE_AGPS_DATA_CONN);
402             }
403 
404             @Override
405             public void onUnavailable() {
406                 Log.i(TAG, "SUPL network connection request timed out.");
407                 // Could not setup the connection to the network in the specified time duration.
408                 handleReleaseSuplConnection(GPS_AGPS_DATA_CONN_FAILED);
409             }
410         };
411     }
412 
413     private void runOnHandler(Runnable event) {
414         // hold a wake lock until this message is delivered
415         // note that this assumes the message will not be removed from the queue before
416         // it is handled (otherwise the wake lock would be leaked).
417         mWakeLock.acquire(WAKELOCK_TIMEOUT_MILLIS);
418         if (!mHandler.post(runEventAndReleaseWakeLock(event))) {
419             mWakeLock.release();
420         }
421     }
422 
423     private Runnable runEventAndReleaseWakeLock(Runnable event) {
424         return () -> {
425             try {
426                 event.run();
427             } finally {
428                 mWakeLock.release();
429             }
430         };
431     }
432 
433     private void handleUpdateNetworkState(Network network, boolean isConnected,
434             NetworkCapabilities capabilities) {
435         boolean networkAvailable = false;
436         TelephonyManager telephonyManager = mContext.getSystemService(TelephonyManager.class);
437         if (telephonyManager != null) {
438             networkAvailable = isConnected && telephonyManager.getDataEnabled();
439         }
440         NetworkAttributes networkAttributes = updateTrackedNetworksState(isConnected, network,
441                 capabilities);
442         String apn = networkAttributes.mApn;
443         int type = networkAttributes.mType;
444         // When isConnected is false, capabilities argument is null. So, use last received
445         // capabilities.
446         capabilities = networkAttributes.mCapabilities;
447         Log.i(TAG, String.format(
448                 "updateNetworkState, state=%s, connected=%s, network=%s, capabilities=%s"
449                         + ", availableNetworkCount: %d",
450                 agpsDataConnStateAsString(),
451                 isConnected,
452                 network,
453                 capabilities,
454                 mAvailableNetworkAttributes.size()));
455 
456         if (native_is_agps_ril_supported()) {
457             native_update_network_state(
458                     isConnected,
459                     type,
460                     !capabilities.hasTransport(
461                             NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING), /* isRoaming */
462                     networkAvailable,
463                     apn != null ? apn : "",
464                     network.getNetworkHandle(),
465                     NetworkAttributes.getCapabilityFlags(capabilities));
466         } else if (DEBUG) {
467             Log.d(TAG, "Skipped network state update because GPS HAL AGPS-RIL is not  supported");
468         }
469     }
470 
471     private NetworkAttributes updateTrackedNetworksState(boolean isConnected, Network network,
472             NetworkCapabilities capabilities) {
473         if (!isConnected) {
474             // Connection lost event. So, remove it from tracked networks.
475             return mAvailableNetworkAttributes.remove(network);
476         }
477 
478         NetworkAttributes networkAttributes = mAvailableNetworkAttributes.get(network);
479         if (networkAttributes != null) {
480             // Capabilities updated event for the connected network.
481             networkAttributes.mCapabilities = capabilities;
482             return networkAttributes;
483         }
484 
485         // Initial capabilities event (equivalent to connection available event).
486         networkAttributes = new NetworkAttributes();
487         networkAttributes.mCapabilities = capabilities;
488 
489         // TODO: The synchronous method ConnectivityManager.getNetworkInfo() should not be called
490         //       inside the asynchronous ConnectivityManager.NetworkCallback methods.
491         NetworkInfo info = mConnMgr.getNetworkInfo(network);
492         if (info != null) {
493             networkAttributes.mApn = info.getExtraInfo();
494             networkAttributes.mType = info.getType();
495         }
496 
497         // Start tracking this network for connection status updates.
498         mAvailableNetworkAttributes.put(network, networkAttributes);
499         return networkAttributes;
500     }
501 
502     private void handleSuplConnectionAvailable(Network network, LinkProperties linkProperties) {
503         // TODO: The synchronous method ConnectivityManager.getNetworkInfo() should not be called
504         //       inside the asynchronous ConnectivityManager.NetworkCallback methods.
505         NetworkInfo info = mConnMgr.getNetworkInfo(network);
506         String apn = null;
507         if (info != null) {
508             apn = info.getExtraInfo();
509         }
510 
511         if (DEBUG) {
512             String message = String.format(
513                     "handleSuplConnectionAvailable: state=%s, suplNetwork=%s, info=%s",
514                     agpsDataConnStateAsString(),
515                     network,
516                     info);
517             Log.d(TAG, message);
518         }
519 
520         if (mAGpsDataConnectionState == AGPS_DATA_CONNECTION_OPENING) {
521             if (apn == null) {
522                 // assign a placeholder value in the case of C2K as otherwise we will have a runtime
523                 // exception in the following call to native_agps_data_conn_open
524                 apn = "dummy-apn";
525             }
526 
527             // Setting route to host is needed for GNSS HAL implementations earlier than
528             // @2.0::IAgnssCallback. The HAL @2.0::IAgnssCallback.agnssStatusCb() method does
529             // not require setting route to SUPL host and hence does not provide an IP address.
530             if (mAGpsDataConnectionIpAddr != null) {
531                 setRouting();
532             }
533 
534             int apnIpType = getLinkIpType(linkProperties);
535             if (DEBUG) {
536                 String message = String.format(
537                         "native_agps_data_conn_open: mAgpsApn=%s, mApnIpType=%s",
538                         apn,
539                         apnIpType);
540                 Log.d(TAG, message);
541             }
542             native_agps_data_conn_open(network.getNetworkHandle(), apn, apnIpType);
543             mAGpsDataConnectionState = AGPS_DATA_CONNECTION_OPEN;
544         }
545     }
546 
547     private void handleRequestSuplConnection(int agpsType, byte[] suplIpAddr) {
548         mAGpsDataConnectionIpAddr = null;
549         mAGpsType = agpsType;
550         if (suplIpAddr != null) {
551             if (VERBOSE) Log.v(TAG, "Received SUPL IP addr[]: " + Arrays.toString(suplIpAddr));
552             try {
553                 mAGpsDataConnectionIpAddr = InetAddress.getByAddress(suplIpAddr);
554                 if (DEBUG) Log.d(TAG, "IP address converted to: " + mAGpsDataConnectionIpAddr);
555             } catch (UnknownHostException e) {
556                 Log.e(TAG, "Bad IP Address: " + suplIpAddr, e);
557             }
558         }
559 
560         if (DEBUG) {
561             String message = String.format(
562                     "requestSuplConnection, state=%s, agpsType=%s, address=%s",
563                     agpsDataConnStateAsString(),
564                     agpsTypeAsString(agpsType),
565                     mAGpsDataConnectionIpAddr);
566             Log.d(TAG, message);
567         }
568 
569         if (mAGpsDataConnectionState != AGPS_DATA_CONNECTION_CLOSED) {
570             return;
571         }
572         mAGpsDataConnectionState = AGPS_DATA_CONNECTION_OPENING;
573 
574         // The transport type must be set to NetworkCapabilities.TRANSPORT_CELLULAR for the
575         // deprecated requestRouteToHostAddress() method in ConnectivityService to work for
576         // pre-gnss@2.0 devices.
577         NetworkRequest.Builder networkRequestBuilder = new NetworkRequest.Builder();
578         networkRequestBuilder.addCapability(getNetworkCapability(mAGpsType));
579         networkRequestBuilder.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR);
580         // During an emergency call, and when we have cached the Active Sub Id, we set the
581         // Network Specifier so that the network request goes to the correct Sub Id
582         if (mNiHandler.getInEmergency() && mActiveSubId >= 0) {
583             if (DEBUG) Log.d(TAG, "Adding Network Specifier: " + Integer.toString(mActiveSubId));
584             networkRequestBuilder.setNetworkSpecifier(Integer.toString(mActiveSubId));
585         }
586         NetworkRequest networkRequest = networkRequestBuilder.build();
587         mConnMgr.requestNetwork(
588                 networkRequest,
589                 mSuplConnectivityCallback,
590                 mHandler,
591                 SUPL_NETWORK_REQUEST_TIMEOUT_MILLIS);
592     }
593 
594     private int getNetworkCapability(int agpsType) {
595         switch (agpsType) {
596             case AGPS_TYPE_C2K:
597             case AGPS_TYPE_SUPL:
598                 return NetworkCapabilities.NET_CAPABILITY_SUPL;
599             case AGPS_TYPE_EIMS:
600                 return NetworkCapabilities.NET_CAPABILITY_EIMS;
601             case AGPS_TYPE_IMS:
602                 return NetworkCapabilities.NET_CAPABILITY_IMS;
603             default:
604                 throw new IllegalArgumentException("agpsType: " + agpsType);
605         }
606     }
607 
608     private void handleReleaseSuplConnection(int agpsDataConnStatus) {
609         if (DEBUG) {
610             String message = String.format(
611                     "releaseSuplConnection, state=%s, status=%s",
612                     agpsDataConnStateAsString(),
613                     agpsDataConnStatusAsString(agpsDataConnStatus));
614             Log.d(TAG, message);
615         }
616 
617         if (mAGpsDataConnectionState == AGPS_DATA_CONNECTION_CLOSED) {
618             return;
619         }
620 
621         mAGpsDataConnectionState = AGPS_DATA_CONNECTION_CLOSED;
622         mConnMgr.unregisterNetworkCallback(mSuplConnectivityCallback);
623         switch (agpsDataConnStatus) {
624             case GPS_AGPS_DATA_CONN_FAILED:
625                 native_agps_data_conn_failed();
626                 break;
627             case GPS_RELEASE_AGPS_DATA_CONN:
628                 native_agps_data_conn_closed();
629                 break;
630             default:
631                 Log.e(TAG, "Invalid status to release SUPL connection: " + agpsDataConnStatus);
632         }
633     }
634 
635     // TODO: Delete this method when all devices upgrade to HAL @2.0::IAGnssCallback
636     //       interface which does not require setting route to host.
637     private void setRouting() {
638         boolean result = mConnMgr.requestRouteToHostAddress(
639                 ConnectivityManager.TYPE_MOBILE_SUPL,
640                 mAGpsDataConnectionIpAddr);
641 
642         if (!result) {
643             Log.e(TAG, "Error requesting route to host: " + mAGpsDataConnectionIpAddr);
644         } else if (DEBUG) {
645             Log.d(TAG, "Successfully requested route to host: " + mAGpsDataConnectionIpAddr);
646         }
647     }
648 
649     /**
650      * Ensures the calling function is running in the thread associated with {@link #mHandler}.
651      */
652     private void ensureInHandlerThread() {
653         if (mHandler != null && Looper.myLooper() == mHandler.getLooper()) {
654             return;
655         }
656         throw new IllegalStateException("This method must run on the Handler thread.");
657     }
658 
659     /**
660      * @return A string representing the current state stored in {@link #mAGpsDataConnectionState}.
661      */
662     private String agpsDataConnStateAsString() {
663         switch (mAGpsDataConnectionState) {
664             case AGPS_DATA_CONNECTION_CLOSED:
665                 return "CLOSED";
666             case AGPS_DATA_CONNECTION_OPEN:
667                 return "OPEN";
668             case AGPS_DATA_CONNECTION_OPENING:
669                 return "OPENING";
670             default:
671                 return "<Unknown>(" + mAGpsDataConnectionState + ")";
672         }
673     }
674 
675     /**
676      * @return A string representing the given GPS_AGPS_DATA status.
677      */
678     private String agpsDataConnStatusAsString(int agpsDataConnStatus) {
679         switch (agpsDataConnStatus) {
680             case GPS_AGPS_DATA_CONNECTED:
681                 return "CONNECTED";
682             case GPS_AGPS_DATA_CONN_DONE:
683                 return "DONE";
684             case GPS_AGPS_DATA_CONN_FAILED:
685                 return "FAILED";
686             case GPS_RELEASE_AGPS_DATA_CONN:
687                 return "RELEASE";
688             case GPS_REQUEST_AGPS_DATA_CONN:
689                 return "REQUEST";
690             default:
691                 return "<Unknown>(" + agpsDataConnStatus + ")";
692         }
693     }
694 
695     private String agpsTypeAsString(int agpsType) {
696         switch (agpsType) {
697             case AGPS_TYPE_SUPL:
698                 return "SUPL";
699             case AGPS_TYPE_C2K:
700                 return "C2K";
701             case AGPS_TYPE_EIMS:
702                 return "EIMS";
703             case AGPS_TYPE_IMS:
704                 return "IMS";
705             default:
706                 return "<Unknown>(" + agpsType + ")";
707         }
708     }
709 
710     private int getLinkIpType(LinkProperties linkProperties) {
711         ensureInHandlerThread();
712         boolean isIPv4 = false;
713         boolean isIPv6 = false;
714 
715         List<LinkAddress> linkAddresses = linkProperties.getLinkAddresses();
716         for (LinkAddress linkAddress : linkAddresses) {
717             InetAddress inetAddress = linkAddress.getAddress();
718             if (inetAddress instanceof Inet4Address) {
719                 isIPv4 = true;
720             } else if (inetAddress instanceof Inet6Address) {
721                 isIPv6 = true;
722             }
723             if (DEBUG) Log.d(TAG, "LinkAddress : " + inetAddress.toString());
724         }
725 
726         if (isIPv4 && isIPv6) {
727             return APN_IPV4V6;
728         }
729         if (isIPv4) {
730             return APN_IPV4;
731         }
732         if (isIPv6) {
733             return APN_IPV6;
734         }
735         return APN_INVALID;
736     }
737 
738     // AGPS support
739     private native void native_agps_data_conn_open(long networkHandle, String apn, int apnIpType);
740 
741     private native void native_agps_data_conn_closed();
742 
743     private native void native_agps_data_conn_failed();
744 
745     // AGPS ril support
746     private static native boolean native_is_agps_ril_supported();
747 
748     private native void native_update_network_state(boolean connected, int type, boolean roaming,
749             boolean available, String apn, long networkHandle, short capabilities);
750 }
751