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