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