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 = isConnected && TelephonyManager.getDefault().getDataEnabled();
325         NetworkAttributes networkAttributes = updateTrackedNetworksState(isConnected, network,
326                 capabilities);
327         String apn = networkAttributes.mApn;
328         int type = networkAttributes.mType;
329         // When isConnected is false, capabilities argument is null. So, use last received
330         // capabilities.
331         capabilities = networkAttributes.mCapabilities;
332         Log.i(TAG, String.format(
333                 "updateNetworkState, state=%s, connected=%s, network=%s, capabilities=%s"
334                         + ", apn: %s, availableNetworkCount: %d",
335                 agpsDataConnStateAsString(),
336                 isConnected,
337                 network,
338                 capabilities,
339                 apn,
340                 mAvailableNetworkAttributes.size()));
341 
342         if (native_is_agps_ril_supported()) {
343             native_update_network_state(
344                     isConnected,
345                     type,
346                     !capabilities.hasTransport(
347                             NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING), /* isRoaming */
348                     networkAvailable,
349                     apn != null ? apn : "",
350                     network.getNetworkHandle(),
351                     NetworkAttributes.getCapabilityFlags(capabilities));
352         } else if (DEBUG) {
353             Log.d(TAG, "Skipped network state update because GPS HAL AGPS-RIL is not  supported");
354         }
355     }
356 
357     private NetworkAttributes updateTrackedNetworksState(boolean isConnected, Network network,
358             NetworkCapabilities capabilities) {
359         if (!isConnected) {
360             // Connection lost event. So, remove it from tracked networks.
361             return mAvailableNetworkAttributes.remove(network);
362         }
363 
364         NetworkAttributes networkAttributes = mAvailableNetworkAttributes.get(network);
365         if (networkAttributes != null) {
366             // Capabilities updated event for the connected network.
367             networkAttributes.mCapabilities = capabilities;
368             return networkAttributes;
369         }
370 
371         // Initial capabilities event (equivalent to connection available event).
372         networkAttributes = new NetworkAttributes();
373         networkAttributes.mCapabilities = capabilities;
374 
375         // TODO: The synchronous method ConnectivityManager.getNetworkInfo() should not be called
376         //       inside the asynchronous ConnectivityManager.NetworkCallback methods.
377         NetworkInfo info = mConnMgr.getNetworkInfo(network);
378         if (info != null) {
379             networkAttributes.mApn = info.getExtraInfo();
380             networkAttributes.mType = info.getType();
381         }
382 
383         // Start tracking this network for connection status updates.
384         mAvailableNetworkAttributes.put(network, networkAttributes);
385         return networkAttributes;
386     }
387 
388     private void handleSuplConnectionAvailable(Network network) {
389         // TODO: The synchronous method ConnectivityManager.getNetworkInfo() should not be called
390         //       inside the asynchronous ConnectivityManager.NetworkCallback methods.
391         NetworkInfo info = mConnMgr.getNetworkInfo(network);
392         String apn = null;
393         if (info != null) {
394             apn = info.getExtraInfo();
395         }
396 
397         if (DEBUG) {
398             String message = String.format(
399                     "handleSuplConnectionAvailable: state=%s, suplNetwork=%s, info=%s",
400                     agpsDataConnStateAsString(),
401                     network,
402                     info);
403             Log.d(TAG, message);
404         }
405 
406         if (mAGpsDataConnectionState == AGPS_DATA_CONNECTION_OPENING) {
407             if (apn == null) {
408                 // assign a dummy value in the case of C2K as otherwise we will have a runtime
409                 // exception in the following call to native_agps_data_conn_open
410                 apn = "dummy-apn";
411             }
412 
413             // Setting route to host is needed for GNSS HAL implementations earlier than
414             // @2.0::IAgnssCallback. The HAL @2.0::IAgnssCallback.agnssStatusCb() method does
415             // not require setting route to SUPL host and hence does not provide an IP address.
416             if (mAGpsDataConnectionIpAddr != null) {
417                 setRouting();
418             }
419 
420             int apnIpType = getApnIpType(apn);
421             if (DEBUG) {
422                 String message = String.format(
423                         "native_agps_data_conn_open: mAgpsApn=%s, mApnIpType=%s",
424                         apn,
425                         apnIpType);
426                 Log.d(TAG, message);
427             }
428             native_agps_data_conn_open(network.getNetworkHandle(), apn, apnIpType);
429             mAGpsDataConnectionState = AGPS_DATA_CONNECTION_OPEN;
430         }
431     }
432 
433     private void handleRequestSuplConnection(int agpsType, byte[] suplIpAddr) {
434         mAGpsDataConnectionIpAddr = null;
435         mAGpsType = agpsType;
436         if (suplIpAddr != null) {
437             if (VERBOSE) Log.v(TAG, "Received SUPL IP addr[]: " + Arrays.toString(suplIpAddr));
438             try {
439                 mAGpsDataConnectionIpAddr = InetAddress.getByAddress(suplIpAddr);
440                 if (DEBUG) Log.d(TAG, "IP address converted to: " + mAGpsDataConnectionIpAddr);
441             } catch (UnknownHostException e) {
442                 Log.e(TAG, "Bad IP Address: " + suplIpAddr, e);
443             }
444         }
445 
446         if (DEBUG) {
447             String message = String.format(
448                     "requestSuplConnection, state=%s, agpsType=%s, address=%s",
449                     agpsDataConnStateAsString(),
450                     agpsTypeAsString(agpsType),
451                     mAGpsDataConnectionIpAddr);
452             Log.d(TAG, message);
453         }
454 
455         if (mAGpsDataConnectionState != AGPS_DATA_CONNECTION_CLOSED) {
456             return;
457         }
458         mAGpsDataConnectionState = AGPS_DATA_CONNECTION_OPENING;
459 
460         // The transport type must be set to NetworkCapabilities.TRANSPORT_CELLULAR for the
461         // deprecated requestRouteToHostAddress() method in ConnectivityService to work for
462         // pre-gnss@2.0 devices.
463         NetworkRequest.Builder networkRequestBuilder = new NetworkRequest.Builder();
464         networkRequestBuilder.addCapability(getNetworkCapability(mAGpsType));
465         networkRequestBuilder.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR);
466         NetworkRequest networkRequest = networkRequestBuilder.build();
467         mConnMgr.requestNetwork(
468                 networkRequest,
469                 mSuplConnectivityCallback,
470                 mHandler,
471                 SUPL_NETWORK_REQUEST_TIMEOUT_MILLIS);
472     }
473 
474     private int getNetworkCapability(int agpsType) {
475         switch (agpsType) {
476             case AGPS_TYPE_C2K:
477             case AGPS_TYPE_SUPL:
478                 return NetworkCapabilities.NET_CAPABILITY_SUPL;
479             case AGPS_TYPE_EIMS:
480                 return NetworkCapabilities.NET_CAPABILITY_EIMS;
481             case AGPS_TYPE_IMS:
482                 return NetworkCapabilities.NET_CAPABILITY_IMS;
483             default:
484                 throw new IllegalArgumentException("agpsType: " + agpsType);
485         }
486     }
487 
488     private void handleReleaseSuplConnection(int agpsDataConnStatus) {
489         if (DEBUG) {
490             String message = String.format(
491                     "releaseSuplConnection, state=%s, status=%s",
492                     agpsDataConnStateAsString(),
493                     agpsDataConnStatusAsString(agpsDataConnStatus));
494             Log.d(TAG, message);
495         }
496 
497         if (mAGpsDataConnectionState == AGPS_DATA_CONNECTION_CLOSED) {
498             return;
499         }
500 
501         mAGpsDataConnectionState = AGPS_DATA_CONNECTION_CLOSED;
502         mConnMgr.unregisterNetworkCallback(mSuplConnectivityCallback);
503         switch (agpsDataConnStatus) {
504             case GPS_AGPS_DATA_CONN_FAILED:
505                 native_agps_data_conn_failed();
506                 break;
507             case GPS_RELEASE_AGPS_DATA_CONN:
508                 native_agps_data_conn_closed();
509                 break;
510             default:
511                 Log.e(TAG, "Invalid status to release SUPL connection: " + agpsDataConnStatus);
512         }
513     }
514 
515     // TODO: Delete this method when all devices upgrade to HAL @2.0::IAGnssCallback
516     //       interface which does not require setting route to host.
517     private void setRouting() {
518         boolean result = mConnMgr.requestRouteToHostAddress(
519                 ConnectivityManager.TYPE_MOBILE_SUPL,
520                 mAGpsDataConnectionIpAddr);
521 
522         if (!result) {
523             Log.e(TAG, "Error requesting route to host: " + mAGpsDataConnectionIpAddr);
524         } else if (DEBUG) {
525             Log.d(TAG, "Successfully requested route to host: " + mAGpsDataConnectionIpAddr);
526         }
527     }
528 
529     /**
530      * Ensures the calling function is running in the thread associated with {@link #mHandler}.
531      */
532     private void ensureInHandlerThread() {
533         if (mHandler != null && Looper.myLooper() == mHandler.getLooper()) {
534             return;
535         }
536         throw new IllegalStateException("This method must run on the Handler thread.");
537     }
538 
539     /**
540      * @return A string representing the current state stored in {@link #mAGpsDataConnectionState}.
541      */
542     private String agpsDataConnStateAsString() {
543         switch (mAGpsDataConnectionState) {
544             case AGPS_DATA_CONNECTION_CLOSED:
545                 return "CLOSED";
546             case AGPS_DATA_CONNECTION_OPEN:
547                 return "OPEN";
548             case AGPS_DATA_CONNECTION_OPENING:
549                 return "OPENING";
550             default:
551                 return "<Unknown>(" + mAGpsDataConnectionState + ")";
552         }
553     }
554 
555     /**
556      * @return A string representing the given GPS_AGPS_DATA status.
557      */
558     private String agpsDataConnStatusAsString(int agpsDataConnStatus) {
559         switch (agpsDataConnStatus) {
560             case GPS_AGPS_DATA_CONNECTED:
561                 return "CONNECTED";
562             case GPS_AGPS_DATA_CONN_DONE:
563                 return "DONE";
564             case GPS_AGPS_DATA_CONN_FAILED:
565                 return "FAILED";
566             case GPS_RELEASE_AGPS_DATA_CONN:
567                 return "RELEASE";
568             case GPS_REQUEST_AGPS_DATA_CONN:
569                 return "REQUEST";
570             default:
571                 return "<Unknown>(" + agpsDataConnStatus + ")";
572         }
573     }
574 
575     private String agpsTypeAsString(int agpsType) {
576         switch (agpsType) {
577             case AGPS_TYPE_SUPL:
578                 return "SUPL";
579             case AGPS_TYPE_C2K:
580                 return "C2K";
581             case AGPS_TYPE_EIMS:
582                 return "EIMS";
583             case AGPS_TYPE_IMS:
584                 return "IMS";
585             default:
586                 return "<Unknown>(" + agpsType + ")";
587         }
588     }
589 
590     private int getApnIpType(String apn) {
591         ensureInHandlerThread();
592         if (apn == null) {
593             return APN_INVALID;
594         }
595         TelephonyManager phone = (TelephonyManager)
596                 mContext.getSystemService(Context.TELEPHONY_SERVICE);
597         ServiceState serviceState = phone.getServiceState();
598         String projection = null;
599         String selection = null;
600 
601         // Carrier configuration may override framework roaming state, we need to use the actual
602         // modem roaming state instead of the framework roaming state.
603         if (serviceState != null && serviceState.getDataRoamingFromRegistration()) {
604             projection = Carriers.ROAMING_PROTOCOL;
605         } else {
606             projection = Carriers.PROTOCOL;
607         }
608         // No SIM case for emergency
609         if (TelephonyManager.NETWORK_TYPE_UNKNOWN == phone.getNetworkType()
610                 && AGPS_TYPE_EIMS == mAGpsType) {
611             selection = String.format(
612                 "type like '%%emergency%%' and apn = '%s' and carrier_enabled = 1", apn);
613         } else {
614             selection = String.format("current = 1 and apn = '%s' and carrier_enabled = 1", apn);
615         }
616         try (Cursor cursor = mContext.getContentResolver().query(
617                 Carriers.CONTENT_URI,
618                 new String[]{projection},
619                 selection,
620                 null,
621                 Carriers.DEFAULT_SORT_ORDER)) {
622             if (null != cursor && cursor.moveToFirst()) {
623                 return translateToApnIpType(cursor.getString(0), apn);
624             } else {
625                 Log.e(TAG, "No entry found in query for APN: " + apn);
626             }
627         } catch (Exception e) {
628             Log.e(TAG, "Error encountered on APN query for: " + apn, e);
629         }
630 
631         return APN_IPV4V6;
632     }
633 
634     private int translateToApnIpType(String ipProtocol, String apn) {
635         if ("IP".equals(ipProtocol)) {
636             return APN_IPV4;
637         }
638         if ("IPV6".equals(ipProtocol)) {
639             return APN_IPV6;
640         }
641         if ("IPV4V6".equals(ipProtocol)) {
642             return APN_IPV4V6;
643         }
644 
645         // we hit the default case so the ipProtocol is not recognized
646         String message = String.format("Unknown IP Protocol: %s, for APN: %s", ipProtocol, apn);
647         Log.e(TAG, message);
648         return APN_IPV4V6;
649     }
650 
651     // AGPS support
652     private native void native_agps_data_conn_open(long networkHandle, String apn, int apnIpType);
653 
654     private native void native_agps_data_conn_closed();
655 
656     private native void native_agps_data_conn_failed();
657 
658     // AGPS ril support
659     private static native boolean native_is_agps_ril_supported();
660 
661     private native void native_update_network_state(boolean connected, int type, boolean roaming,
662             boolean available, String apn, long networkHandle, short capabilities);
663 }
664