• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 (state.PRECISE_CALL_STATE_ACTIVE == state.getForegroundCallState()) {
219                 mActiveSubId = mSubId;
220                 if (DEBUG) Log.d(TAG, "mActiveSubId: " + mActiveSubId);
221             }
222         }
223     };
224 
225     /**
226      * Subscription Changed Listener is used to get all active subscriptions and create a
227      * Phone State Listener for each Sub ID that we find in the active subscription list
228      */
229     private final SubscriptionManager.OnSubscriptionsChangedListener mOnSubscriptionsChangeListener
230             = new SubscriptionManager.OnSubscriptionsChangedListener() {
231         @Override
232         public void onSubscriptionsChanged() {
233             if (mPhoneStateListeners == null) {
234                 // Capacity=2 Load-Factor=1.0, as typically no more than 2 SIMs
235                 mPhoneStateListeners = new HashMap<Integer, SubIdPhoneStateListener>(2,1);
236             }
237             SubscriptionManager subManager = mContext.getSystemService(SubscriptionManager.class);
238             TelephonyManager telManager = mContext.getSystemService(TelephonyManager.class);
239             if (subManager != null && telManager != null) {
240                 List<SubscriptionInfo> subscriptionInfoList =
241                         subManager.getActiveSubscriptionInfoList();
242                 HashSet<Integer> activeSubIds = new HashSet<Integer>();
243                 if (subscriptionInfoList != null) {
244                     if (DEBUG) Log.d(TAG, "Active Sub List size: " + subscriptionInfoList.size());
245                     // populate phone state listeners with all new active subs
246                     for (SubscriptionInfo subInfo : subscriptionInfoList) {
247                         activeSubIds.add(subInfo.getSubscriptionId());
248                         if (!mPhoneStateListeners.containsKey(subInfo.getSubscriptionId())) {
249                             TelephonyManager subIdTelManager =
250                                     telManager.createForSubscriptionId(subInfo.getSubscriptionId());
251                             if (subIdTelManager != null) {
252                                 if (DEBUG) Log.d(TAG, "Listener sub" + subInfo.getSubscriptionId());
253                                 SubIdPhoneStateListener subIdPhoneStateListener =
254                                         new SubIdPhoneStateListener(subInfo.getSubscriptionId());
255                                 mPhoneStateListeners.put(subInfo.getSubscriptionId(),
256                                         subIdPhoneStateListener);
257                                 subIdTelManager.listen(subIdPhoneStateListener,
258                                         PhoneStateListener.LISTEN_PRECISE_CALL_STATE);
259                             }
260                         }
261                     }
262                 }
263                 // clean up phone state listeners than no longer have active subs
264                 Iterator<Map.Entry<Integer, SubIdPhoneStateListener> > iterator =
265                         mPhoneStateListeners.entrySet().iterator();
266                 while (iterator.hasNext()) {
267                     Map.Entry<Integer, SubIdPhoneStateListener> element = iterator.next();
268                     if (!activeSubIds.contains(element.getKey())) {
269                         TelephonyManager subIdTelManager =
270                                 telManager.createForSubscriptionId(element.getKey());
271                         if (subIdTelManager != null) {
272                             if (DEBUG) Log.d(TAG, "unregister listener sub " + element.getKey());
273                             subIdTelManager.listen(element.getValue(),
274                                                    PhoneStateListener.LISTEN_NONE);
275                             // removes the element from mPhoneStateListeners
276                             iterator.remove();
277                         } else {
278                             Log.e(TAG, "Telephony Manager for Sub " + element.getKey() + " null");
279                         }
280                     }
281                 }
282                 // clean up cached active phone call sub if it is no longer an active sub
283                 if (!activeSubIds.contains(mActiveSubId)) {
284                     mActiveSubId = -1;
285                 }
286             }
287         }
288     };
289 
registerNetworkCallbacks()290     void registerNetworkCallbacks() {
291         // register for connectivity change events.
292         NetworkRequest.Builder networkRequestBuilder = new NetworkRequest.Builder();
293         networkRequestBuilder.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET);
294         networkRequestBuilder.addCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED);
295         networkRequestBuilder.removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN);
296         NetworkRequest networkRequest = networkRequestBuilder.build();
297         mNetworkConnectivityCallback = createNetworkConnectivityCallback();
298         mConnMgr.registerNetworkCallback(networkRequest, mNetworkConnectivityCallback, mHandler);
299     }
300 
301     /**
302      * @return {@code true} if there is a data network available for outgoing connections,
303      * {@code false} otherwise.
304      */
isDataNetworkConnected()305     boolean isDataNetworkConnected() {
306         NetworkInfo activeNetworkInfo = mConnMgr.getActiveNetworkInfo();
307         return activeNetworkInfo != null && activeNetworkInfo.isConnected();
308     }
309 
310     /**
311      * Called from native code to update AGPS connection status, or to request or release a SUPL
312      * connection.
313      *
314      * <p>Note: {@code suplIpAddr} parameter is not present from IAGnssCallback.hal@2.0 onwards
315      * and is set to {@code null}.
316      */
onReportAGpsStatus(int agpsType, int agpsStatus, byte[] suplIpAddr)317     void onReportAGpsStatus(int agpsType, int agpsStatus, byte[] suplIpAddr) {
318         if (DEBUG) Log.d(TAG, "AGPS_DATA_CONNECTION: " + agpsDataConnStatusAsString(agpsStatus));
319         switch (agpsStatus) {
320             case GPS_REQUEST_AGPS_DATA_CONN:
321                 runOnHandler(() -> handleRequestSuplConnection(agpsType, suplIpAddr));
322                 break;
323             case GPS_RELEASE_AGPS_DATA_CONN:
324                 runOnHandler(() -> handleReleaseSuplConnection(GPS_RELEASE_AGPS_DATA_CONN));
325                 break;
326             case GPS_AGPS_DATA_CONNECTED:
327             case GPS_AGPS_DATA_CONN_DONE:
328             case GPS_AGPS_DATA_CONN_FAILED:
329                 break;
330             default:
331                 Log.w(TAG, "Received unknown AGPS status: " + agpsStatus);
332         }
333     }
334 
createNetworkConnectivityCallback()335     private ConnectivityManager.NetworkCallback createNetworkConnectivityCallback() {
336         return new ConnectivityManager.NetworkCallback() {
337             // Used to filter out network capabilities changes that we are not interested in.
338             // NOTE: Not using a ConcurrentHashMap and also not using locking around updates
339             //       and access to the map object because it is all done inside the same
340             //       handler thread invoking the callback methods.
341             private HashMap<Network, NetworkCapabilities>
342                     mAvailableNetworkCapabilities = new HashMap<>(
343                     HASH_MAP_INITIAL_CAPACITY_TO_TRACK_CONNECTED_NETWORKS);
344 
345             @Override
346             public void onCapabilitiesChanged(Network network,
347                     NetworkCapabilities capabilities) {
348                 // This callback is invoked for any change in the network capabilities including
349                 // initial availability, and changes while still available. Only process if the
350                 // capabilities that we pass on to HAL change.
351                 if (!NetworkAttributes.hasCapabilitiesChanged(
352                         mAvailableNetworkCapabilities.get(network), capabilities)) {
353                     if (VERBOSE) {
354                         Log.v(TAG, "Relevant network capabilities unchanged. Capabilities: "
355                                 + capabilities);
356                     }
357                     return;
358                 }
359 
360                 mAvailableNetworkCapabilities.put(network, capabilities);
361                 if (DEBUG) {
362                     Log.d(TAG, "Network connected/capabilities updated. Available networks count: "
363                             + mAvailableNetworkCapabilities.size());
364                 }
365 
366                 mGnssNetworkListener.onNetworkAvailable();
367 
368                 // Always on, notify HAL so it can get data it needs
369                 handleUpdateNetworkState(network, true, capabilities);
370             }
371 
372             @Override
373             public void onLost(Network network) {
374                 if (mAvailableNetworkCapabilities.remove(network) == null) {
375                     Log.w(TAG, "Incorrectly received network callback onLost() before"
376                             + " onCapabilitiesChanged() for network: " + network);
377                     return;
378                 }
379 
380                 Log.i(TAG, "Network connection lost. Available networks count: "
381                         + mAvailableNetworkCapabilities.size());
382                 handleUpdateNetworkState(network, false, null);
383             }
384         };
385     }
386 
createSuplConnectivityCallback()387     private ConnectivityManager.NetworkCallback createSuplConnectivityCallback() {
388         return new ConnectivityManager.NetworkCallback() {
389             @Override
390             public void onLinkPropertiesChanged(Network network, LinkProperties linkProperties) {
391                 if (DEBUG) Log.d(TAG, "SUPL network connection available.");
392                 // Specific to a change to a SUPL enabled network becoming ready
393                 handleSuplConnectionAvailable(network, linkProperties);
394             }
395 
396             @Override
397             public void onLost(Network network) {
398                 Log.i(TAG, "SUPL network connection lost.");
399                 handleReleaseSuplConnection(GPS_RELEASE_AGPS_DATA_CONN);
400             }
401 
402             @Override
403             public void onUnavailable() {
404                 Log.i(TAG, "SUPL network connection request timed out.");
405                 // Could not setup the connection to the network in the specified time duration.
406                 handleReleaseSuplConnection(GPS_AGPS_DATA_CONN_FAILED);
407             }
408         };
409     }
410 
411     private void runOnHandler(Runnable event) {
412         // hold a wake lock until this message is delivered
413         // note that this assumes the message will not be removed from the queue before
414         // it is handled (otherwise the wake lock would be leaked).
415         mWakeLock.acquire(WAKELOCK_TIMEOUT_MILLIS);
416         if (!mHandler.post(runEventAndReleaseWakeLock(event))) {
417             mWakeLock.release();
418         }
419     }
420 
421     private Runnable runEventAndReleaseWakeLock(Runnable event) {
422         return () -> {
423             try {
424                 event.run();
425             } finally {
426                 mWakeLock.release();
427             }
428         };
429     }
430 
431     private void handleUpdateNetworkState(Network network, boolean isConnected,
432             NetworkCapabilities capabilities) {
433         boolean networkAvailable = false;
434         TelephonyManager telephonyManager = mContext.getSystemService(TelephonyManager.class);
435         if (telephonyManager != null) {
436             networkAvailable = isConnected && telephonyManager.getDataEnabled();
437         }
438         NetworkAttributes networkAttributes = updateTrackedNetworksState(isConnected, network,
439                 capabilities);
440         String apn = networkAttributes.mApn;
441         int type = networkAttributes.mType;
442         // When isConnected is false, capabilities argument is null. So, use last received
443         // capabilities.
444         capabilities = networkAttributes.mCapabilities;
445         Log.i(TAG, String.format(
446                 "updateNetworkState, state=%s, connected=%s, network=%s, capabilities=%s"
447                         + ", apn: %s, availableNetworkCount: %d",
448                 agpsDataConnStateAsString(),
449                 isConnected,
450                 network,
451                 capabilities,
452                 apn,
453                 mAvailableNetworkAttributes.size()));
454 
455         if (native_is_agps_ril_supported()) {
456             native_update_network_state(
457                     isConnected,
458                     type,
459                     !capabilities.hasTransport(
460                             NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING), /* isRoaming */
461                     networkAvailable,
462                     apn != null ? apn : "",
463                     network.getNetworkHandle(),
464                     NetworkAttributes.getCapabilityFlags(capabilities));
465         } else if (DEBUG) {
466             Log.d(TAG, "Skipped network state update because GPS HAL AGPS-RIL is not  supported");
467         }
468     }
469 
470     private NetworkAttributes updateTrackedNetworksState(boolean isConnected, Network network,
471             NetworkCapabilities capabilities) {
472         if (!isConnected) {
473             // Connection lost event. So, remove it from tracked networks.
474             return mAvailableNetworkAttributes.remove(network);
475         }
476 
477         NetworkAttributes networkAttributes = mAvailableNetworkAttributes.get(network);
478         if (networkAttributes != null) {
479             // Capabilities updated event for the connected network.
480             networkAttributes.mCapabilities = capabilities;
481             return networkAttributes;
482         }
483 
484         // Initial capabilities event (equivalent to connection available event).
485         networkAttributes = new NetworkAttributes();
486         networkAttributes.mCapabilities = capabilities;
487 
488         // TODO: The synchronous method ConnectivityManager.getNetworkInfo() should not be called
489         //       inside the asynchronous ConnectivityManager.NetworkCallback methods.
490         NetworkInfo info = mConnMgr.getNetworkInfo(network);
491         if (info != null) {
492             networkAttributes.mApn = info.getExtraInfo();
493             networkAttributes.mType = info.getType();
494         }
495 
496         // Start tracking this network for connection status updates.
497         mAvailableNetworkAttributes.put(network, networkAttributes);
498         return networkAttributes;
499     }
500 
501     private void handleSuplConnectionAvailable(Network network, LinkProperties linkProperties) {
502         // TODO: The synchronous method ConnectivityManager.getNetworkInfo() should not be called
503         //       inside the asynchronous ConnectivityManager.NetworkCallback methods.
504         NetworkInfo info = mConnMgr.getNetworkInfo(network);
505         String apn = null;
506         if (info != null) {
507             apn = info.getExtraInfo();
508         }
509 
510         if (DEBUG) {
511             String message = String.format(
512                     "handleSuplConnectionAvailable: state=%s, suplNetwork=%s, info=%s",
513                     agpsDataConnStateAsString(),
514                     network,
515                     info);
516             Log.d(TAG, message);
517         }
518 
519         if (mAGpsDataConnectionState == AGPS_DATA_CONNECTION_OPENING) {
520             if (apn == null) {
521                 // assign a placeholder value in the case of C2K as otherwise we will have a runtime
522                 // exception in the following call to native_agps_data_conn_open
523                 apn = "dummy-apn";
524             }
525 
526             // Setting route to host is needed for GNSS HAL implementations earlier than
527             // @2.0::IAgnssCallback. The HAL @2.0::IAgnssCallback.agnssStatusCb() method does
528             // not require setting route to SUPL host and hence does not provide an IP address.
529             if (mAGpsDataConnectionIpAddr != null) {
530                 setRouting();
531             }
532 
533             int apnIpType = getLinkIpType(linkProperties);
534             if (DEBUG) {
535                 String message = String.format(
536                         "native_agps_data_conn_open: mAgpsApn=%s, mApnIpType=%s",
537                         apn,
538                         apnIpType);
539                 Log.d(TAG, message);
540             }
541             native_agps_data_conn_open(network.getNetworkHandle(), apn, apnIpType);
542             mAGpsDataConnectionState = AGPS_DATA_CONNECTION_OPEN;
543         }
544     }
545 
546     private void handleRequestSuplConnection(int agpsType, byte[] suplIpAddr) {
547         mAGpsDataConnectionIpAddr = null;
548         mAGpsType = agpsType;
549         if (suplIpAddr != null) {
550             if (VERBOSE) Log.v(TAG, "Received SUPL IP addr[]: " + Arrays.toString(suplIpAddr));
551             try {
552                 mAGpsDataConnectionIpAddr = InetAddress.getByAddress(suplIpAddr);
553                 if (DEBUG) Log.d(TAG, "IP address converted to: " + mAGpsDataConnectionIpAddr);
554             } catch (UnknownHostException e) {
555                 Log.e(TAG, "Bad IP Address: " + suplIpAddr, e);
556             }
557         }
558 
559         if (DEBUG) {
560             String message = String.format(
561                     "requestSuplConnection, state=%s, agpsType=%s, address=%s",
562                     agpsDataConnStateAsString(),
563                     agpsTypeAsString(agpsType),
564                     mAGpsDataConnectionIpAddr);
565             Log.d(TAG, message);
566         }
567 
568         if (mAGpsDataConnectionState != AGPS_DATA_CONNECTION_CLOSED) {
569             return;
570         }
571         mAGpsDataConnectionState = AGPS_DATA_CONNECTION_OPENING;
572 
573         // The transport type must be set to NetworkCapabilities.TRANSPORT_CELLULAR for the
574         // deprecated requestRouteToHostAddress() method in ConnectivityService to work for
575         // pre-gnss@2.0 devices.
576         NetworkRequest.Builder networkRequestBuilder = new NetworkRequest.Builder();
577         networkRequestBuilder.addCapability(getNetworkCapability(mAGpsType));
578         networkRequestBuilder.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR);
579         // During an emergency call, and when we have cached the Active Sub Id, we set the
580         // Network Specifier so that the network request goes to the correct Sub Id
581         if (mNiHandler.getInEmergency() && mActiveSubId >= 0) {
582             if (DEBUG) Log.d(TAG, "Adding Network Specifier: " + Integer.toString(mActiveSubId));
583             networkRequestBuilder.setNetworkSpecifier(Integer.toString(mActiveSubId));
584         }
585         NetworkRequest networkRequest = networkRequestBuilder.build();
586         mConnMgr.requestNetwork(
587                 networkRequest,
588                 mSuplConnectivityCallback,
589                 mHandler,
590                 SUPL_NETWORK_REQUEST_TIMEOUT_MILLIS);
591     }
592 
593     private int getNetworkCapability(int agpsType) {
594         switch (agpsType) {
595             case AGPS_TYPE_C2K:
596             case AGPS_TYPE_SUPL:
597                 return NetworkCapabilities.NET_CAPABILITY_SUPL;
598             case AGPS_TYPE_EIMS:
599                 return NetworkCapabilities.NET_CAPABILITY_EIMS;
600             case AGPS_TYPE_IMS:
601                 return NetworkCapabilities.NET_CAPABILITY_IMS;
602             default:
603                 throw new IllegalArgumentException("agpsType: " + agpsType);
604         }
605     }
606 
607     private void handleReleaseSuplConnection(int agpsDataConnStatus) {
608         if (DEBUG) {
609             String message = String.format(
610                     "releaseSuplConnection, state=%s, status=%s",
611                     agpsDataConnStateAsString(),
612                     agpsDataConnStatusAsString(agpsDataConnStatus));
613             Log.d(TAG, message);
614         }
615 
616         if (mAGpsDataConnectionState == AGPS_DATA_CONNECTION_CLOSED) {
617             return;
618         }
619 
620         mAGpsDataConnectionState = AGPS_DATA_CONNECTION_CLOSED;
621         mConnMgr.unregisterNetworkCallback(mSuplConnectivityCallback);
622         switch (agpsDataConnStatus) {
623             case GPS_AGPS_DATA_CONN_FAILED:
624                 native_agps_data_conn_failed();
625                 break;
626             case GPS_RELEASE_AGPS_DATA_CONN:
627                 native_agps_data_conn_closed();
628                 break;
629             default:
630                 Log.e(TAG, "Invalid status to release SUPL connection: " + agpsDataConnStatus);
631         }
632     }
633 
634     // TODO: Delete this method when all devices upgrade to HAL @2.0::IAGnssCallback
635     //       interface which does not require setting route to host.
636     private void setRouting() {
637         boolean result = mConnMgr.requestRouteToHostAddress(
638                 ConnectivityManager.TYPE_MOBILE_SUPL,
639                 mAGpsDataConnectionIpAddr);
640 
641         if (!result) {
642             Log.e(TAG, "Error requesting route to host: " + mAGpsDataConnectionIpAddr);
643         } else if (DEBUG) {
644             Log.d(TAG, "Successfully requested route to host: " + mAGpsDataConnectionIpAddr);
645         }
646     }
647 
648     /**
649      * Ensures the calling function is running in the thread associated with {@link #mHandler}.
650      */
651     private void ensureInHandlerThread() {
652         if (mHandler != null && Looper.myLooper() == mHandler.getLooper()) {
653             return;
654         }
655         throw new IllegalStateException("This method must run on the Handler thread.");
656     }
657 
658     /**
659      * @return A string representing the current state stored in {@link #mAGpsDataConnectionState}.
660      */
661     private String agpsDataConnStateAsString() {
662         switch (mAGpsDataConnectionState) {
663             case AGPS_DATA_CONNECTION_CLOSED:
664                 return "CLOSED";
665             case AGPS_DATA_CONNECTION_OPEN:
666                 return "OPEN";
667             case AGPS_DATA_CONNECTION_OPENING:
668                 return "OPENING";
669             default:
670                 return "<Unknown>(" + mAGpsDataConnectionState + ")";
671         }
672     }
673 
674     /**
675      * @return A string representing the given GPS_AGPS_DATA status.
676      */
677     private String agpsDataConnStatusAsString(int agpsDataConnStatus) {
678         switch (agpsDataConnStatus) {
679             case GPS_AGPS_DATA_CONNECTED:
680                 return "CONNECTED";
681             case GPS_AGPS_DATA_CONN_DONE:
682                 return "DONE";
683             case GPS_AGPS_DATA_CONN_FAILED:
684                 return "FAILED";
685             case GPS_RELEASE_AGPS_DATA_CONN:
686                 return "RELEASE";
687             case GPS_REQUEST_AGPS_DATA_CONN:
688                 return "REQUEST";
689             default:
690                 return "<Unknown>(" + agpsDataConnStatus + ")";
691         }
692     }
693 
694     private String agpsTypeAsString(int agpsType) {
695         switch (agpsType) {
696             case AGPS_TYPE_SUPL:
697                 return "SUPL";
698             case AGPS_TYPE_C2K:
699                 return "C2K";
700             case AGPS_TYPE_EIMS:
701                 return "EIMS";
702             case AGPS_TYPE_IMS:
703                 return "IMS";
704             default:
705                 return "<Unknown>(" + agpsType + ")";
706         }
707     }
708 
709     private int getLinkIpType(LinkProperties linkProperties) {
710         ensureInHandlerThread();
711         boolean isIPv4 = false;
712         boolean isIPv6 = false;
713 
714         List<LinkAddress> linkAddresses = linkProperties.getLinkAddresses();
715         for (LinkAddress linkAddress : linkAddresses) {
716             InetAddress inetAddress = linkAddress.getAddress();
717             if (inetAddress instanceof Inet4Address) {
718                 isIPv4 = true;
719             } else if (inetAddress instanceof Inet6Address) {
720                 isIPv6 = true;
721             }
722             if (DEBUG) Log.d(TAG, "LinkAddress : " + inetAddress.toString());
723         }
724 
725         if (isIPv4 && isIPv6) {
726             return APN_IPV4V6;
727         }
728         if (isIPv4) {
729             return APN_IPV4;
730         }
731         if (isIPv6) {
732             return APN_IPV6;
733         }
734         return APN_INVALID;
735     }
736 
737     // AGPS support
738     private native void native_agps_data_conn_open(long networkHandle, String apn, int apnIpType);
739 
740     private native void native_agps_data_conn_closed();
741 
742     private native void native_agps_data_conn_failed();
743 
744     // AGPS ril support
745     private static native boolean native_is_agps_ril_supported();
746 
747     private native void native_update_network_state(boolean connected, int type, boolean roaming,
748             boolean available, String apn, long networkHandle, short capabilities);
749 }
750