Java程序  |  664行  |  26.91 KB

/*
 * Copyright (C) 2018 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.server.location;

import android.content.Context;
import android.database.Cursor;
import android.net.ConnectivityManager;
import android.net.Network;
import android.net.NetworkCapabilities;
import android.net.NetworkInfo;
import android.net.NetworkRequest;
import android.os.Handler;
import android.os.Looper;
import android.os.PowerManager;
import android.provider.Telephony.Carriers;
import android.telephony.ServiceState;
import android.telephony.TelephonyManager;
import android.util.Log;

import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.Arrays;
import java.util.HashMap;

/**
 * Handles network connection requests and network state change updates for AGPS data download.
 */
class GnssNetworkConnectivityHandler {
    static final String TAG = "GnssNetworkConnectivityHandler";

    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
    private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);

    // for mAGpsDataConnectionState
    private static final int AGPS_DATA_CONNECTION_CLOSED = 0;
    private static final int AGPS_DATA_CONNECTION_OPENING = 1;
    private static final int AGPS_DATA_CONNECTION_OPEN = 2;

    // these need to match AGnssStatusValue enum in IAGnssCallback.hal
    /** AGPS status event values. */
    private static final int GPS_REQUEST_AGPS_DATA_CONN = 1;
    private static final int GPS_RELEASE_AGPS_DATA_CONN = 2;
    private static final int GPS_AGPS_DATA_CONNECTED = 3;
    private static final int GPS_AGPS_DATA_CONN_DONE = 4;
    private static final int GPS_AGPS_DATA_CONN_FAILED = 5;

    // these must match the ApnIpType enum in IAGnss.hal
    private static final int APN_INVALID = 0;
    private static final int APN_IPV4 = 1;
    private static final int APN_IPV6 = 2;
    private static final int APN_IPV4V6 = 3;

    // these must match the NetworkCapability enum flags in IAGnssRil.hal
    private static final int AGNSS_NET_CAPABILITY_NOT_METERED = 1 << 0;
    private static final int AGNSS_NET_CAPABILITY_NOT_ROAMING = 1 << 1;

    // these need to match AGnssType enum in IAGnssCallback.hal
    public static final int AGPS_TYPE_SUPL = 1;
    public static final int AGPS_TYPE_C2K = 2;
    private static final int AGPS_TYPE_EIMS = 3;
    private static final int AGPS_TYPE_IMS = 4;

    // Default time limit in milliseconds for the ConnectivityManager to find a suitable
    // network with SUPL connectivity or report an error.
    private static final int SUPL_NETWORK_REQUEST_TIMEOUT_MILLIS = 10 * 1000;

    private static final int HASH_MAP_INITIAL_CAPACITY_TO_TRACK_CONNECTED_NETWORKS = 5;

    // Keeps track of networks and their state as notified by the network request callbacks.
    // Limit initial capacity to 5 as the number of connected networks will likely be small.
    // NOTE: Must be accessed/modified only through the mHandler thread.
    private HashMap<Network, NetworkAttributes> mAvailableNetworkAttributes =
            new HashMap<>(HASH_MAP_INITIAL_CAPACITY_TO_TRACK_CONNECTED_NETWORKS);

    private final ConnectivityManager mConnMgr;

    private final Handler mHandler;
    private final GnssNetworkListener mGnssNetworkListener;

    private int mAGpsDataConnectionState;
    private InetAddress mAGpsDataConnectionIpAddr;
    private int mAGpsType;

    private final Context mContext;

    // Wakelocks
    private static final String WAKELOCK_KEY = "GnssNetworkConnectivityHandler";
    private static final long WAKELOCK_TIMEOUT_MILLIS = 60 * 1000;
    private final PowerManager.WakeLock mWakeLock;

    /**
     * Network attributes needed when updating HAL about network connectivity status changes.
     */
    private static class NetworkAttributes {
        private NetworkCapabilities mCapabilities;
        private String mApn;
        private int mType = ConnectivityManager.TYPE_NONE;

        /**
         * Returns true if the capabilities that we pass on to HAL change between {@curCapabilities}
         * and {@code newCapabilities}.
         */
        private static boolean hasCapabilitiesChanged(NetworkCapabilities curCapabilities,
                NetworkCapabilities newCapabilities) {
            if (curCapabilities == null || newCapabilities == null) {
                return true;
            }

            // Monitor for roaming and metered capability changes.
            return hasCapabilityChanged(curCapabilities, newCapabilities,
                    NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING)
                    || hasCapabilityChanged(curCapabilities, newCapabilities,
                    NetworkCapabilities.NET_CAPABILITY_NOT_METERED);
        }

        private static boolean hasCapabilityChanged(NetworkCapabilities curCapabilities,
                NetworkCapabilities newCapabilities, int capability) {
            return curCapabilities.hasCapability(capability)
                    != newCapabilities.hasCapability(capability);
        }

        private static short getCapabilityFlags(NetworkCapabilities capabilities) {
            short capabilityFlags = 0;
            if (capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING)) {
                capabilityFlags |= AGNSS_NET_CAPABILITY_NOT_ROAMING;
            }
            if (capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED)) {
                capabilityFlags |= AGNSS_NET_CAPABILITY_NOT_METERED;
            }
            return capabilityFlags;
        }
    }

    /**
     * Callback used to listen for data connectivity changes.
     */
    private ConnectivityManager.NetworkCallback mNetworkConnectivityCallback;

    /**
     * Callback used to listen for availability of a requested SUPL connection.
     * It is kept as a separate instance from {@link #mNetworkConnectivityCallback} to be able to
     * manage the registration/un-registration lifetimes separately.
     */
    private ConnectivityManager.NetworkCallback mSuplConnectivityCallback;

    /**
     * Interface to listen for network availability changes.
     */
    interface GnssNetworkListener {
        void onNetworkAvailable();
    }

    GnssNetworkConnectivityHandler(Context context,
            GnssNetworkListener gnssNetworkListener,
            Looper looper) {
        mContext = context;
        mGnssNetworkListener = gnssNetworkListener;

        PowerManager powerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
        mWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, WAKELOCK_KEY);

        mHandler = new Handler(looper);
        mConnMgr = (ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
        mSuplConnectivityCallback = createSuplConnectivityCallback();
    }

    void registerNetworkCallbacks() {
        // register for connectivity change events.
        NetworkRequest.Builder networkRequestBuilder = new NetworkRequest.Builder();
        networkRequestBuilder.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET);
        networkRequestBuilder.addCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED);
        networkRequestBuilder.removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN);
        NetworkRequest networkRequest = networkRequestBuilder.build();
        mNetworkConnectivityCallback = createNetworkConnectivityCallback();
        mConnMgr.registerNetworkCallback(networkRequest, mNetworkConnectivityCallback, mHandler);
    }

    /**
     * @return {@code true} if there is a data network available for outgoing connections,
     * {@code false} otherwise.
     */
    boolean isDataNetworkConnected() {
        NetworkInfo activeNetworkInfo = mConnMgr.getActiveNetworkInfo();
        return activeNetworkInfo != null && activeNetworkInfo.isConnected();
    }

    /**
     * Called from native code to update AGPS connection status, or to request or release a SUPL
     * connection.
     *
     * <p>Note: {@code suplIpAddr} parameter is not present from IAGnssCallback.hal@2.0 onwards
     * and is set to {@code null}.
     */
    void onReportAGpsStatus(int agpsType, int agpsStatus, byte[] suplIpAddr) {
        if (DEBUG) Log.d(TAG, "AGPS_DATA_CONNECTION: " + agpsDataConnStatusAsString(agpsStatus));
        switch (agpsStatus) {
            case GPS_REQUEST_AGPS_DATA_CONN:
                runOnHandler(() -> handleRequestSuplConnection(agpsType, suplIpAddr));
                break;
            case GPS_RELEASE_AGPS_DATA_CONN:
                runOnHandler(() -> handleReleaseSuplConnection(GPS_RELEASE_AGPS_DATA_CONN));
                break;
            case GPS_AGPS_DATA_CONNECTED:
            case GPS_AGPS_DATA_CONN_DONE:
            case GPS_AGPS_DATA_CONN_FAILED:
                break;
            default:
                Log.w(TAG, "Received unknown AGPS status: " + agpsStatus);
        }
    }

    private ConnectivityManager.NetworkCallback createNetworkConnectivityCallback() {
        return new ConnectivityManager.NetworkCallback() {
            // Used to filter out network capabilities changes that we are not interested in.
            // NOTE: Not using a ConcurrentHashMap and also not using locking around updates
            //       and access to the map object because it is all done inside the same
            //       handler thread invoking the callback methods.
            private HashMap<Network, NetworkCapabilities>
                    mAvailableNetworkCapabilities = new HashMap<>(
                    HASH_MAP_INITIAL_CAPACITY_TO_TRACK_CONNECTED_NETWORKS);

            @Override
            public void onCapabilitiesChanged(Network network,
                    NetworkCapabilities capabilities) {
                // This callback is invoked for any change in the network capabilities including
                // initial availability, and changes while still available. Only process if the
                // capabilities that we pass on to HAL change.
                if (!NetworkAttributes.hasCapabilitiesChanged(
                        mAvailableNetworkCapabilities.get(network), capabilities)) {
                    if (VERBOSE) {
                        Log.v(TAG, "Relevant network capabilities unchanged. Capabilities: "
                                + capabilities);
                    }
                    return;
                }

                mAvailableNetworkCapabilities.put(network, capabilities);
                if (DEBUG) {
                    Log.d(TAG, "Network connected/capabilities updated. Available networks count: "
                            + mAvailableNetworkCapabilities.size());
                }

                mGnssNetworkListener.onNetworkAvailable();

                // Always on, notify HAL so it can get data it needs
                handleUpdateNetworkState(network, true, capabilities);
            }

            @Override
            public void onLost(Network network) {
                if (mAvailableNetworkCapabilities.remove(network) == null) {
                    Log.w(TAG, "Incorrectly received network callback onLost() before"
                            + " onCapabilitiesChanged() for network: " + network);
                    return;
                }

                Log.i(TAG, "Network connection lost. Available networks count: "
                        + mAvailableNetworkCapabilities.size());
                handleUpdateNetworkState(network, false, null);
            }
        };
    }

    private ConnectivityManager.NetworkCallback createSuplConnectivityCallback() {
        return new ConnectivityManager.NetworkCallback() {
            @Override
            public void onAvailable(Network network) {
                if (DEBUG) Log.d(TAG, "SUPL network connection available.");
                // Specific to a change to a SUPL enabled network becoming ready
                handleSuplConnectionAvailable(network);
            }

            @Override
            public void onLost(Network network) {
                Log.i(TAG, "SUPL network connection lost.");
                handleReleaseSuplConnection(GPS_RELEASE_AGPS_DATA_CONN);
            }

            @Override
            public void onUnavailable() {
                Log.i(TAG, "SUPL network connection request timed out.");
                // Could not setup the connection to the network in the specified time duration.
                handleReleaseSuplConnection(GPS_AGPS_DATA_CONN_FAILED);
            }
        };
    }

    private void runOnHandler(Runnable event) {
        // hold a wake lock until this message is delivered
        // note that this assumes the message will not be removed from the queue before
        // it is handled (otherwise the wake lock would be leaked).
        mWakeLock.acquire(WAKELOCK_TIMEOUT_MILLIS);
        if (!mHandler.post(runEventAndReleaseWakeLock(event))) {
            mWakeLock.release();
        }
    }

    private Runnable runEventAndReleaseWakeLock(Runnable event) {
        return () -> {
            try {
                event.run();
            } finally {
                mWakeLock.release();
            }
        };
    }

    private void handleUpdateNetworkState(Network network, boolean isConnected,
            NetworkCapabilities capabilities) {
        boolean networkAvailable = isConnected && TelephonyManager.getDefault().getDataEnabled();
        NetworkAttributes networkAttributes = updateTrackedNetworksState(isConnected, network,
                capabilities);
        String apn = networkAttributes.mApn;
        int type = networkAttributes.mType;
        // When isConnected is false, capabilities argument is null. So, use last received
        // capabilities.
        capabilities = networkAttributes.mCapabilities;
        Log.i(TAG, String.format(
                "updateNetworkState, state=%s, connected=%s, network=%s, capabilities=%s"
                        + ", apn: %s, availableNetworkCount: %d",
                agpsDataConnStateAsString(),
                isConnected,
                network,
                capabilities,
                apn,
                mAvailableNetworkAttributes.size()));

        if (native_is_agps_ril_supported()) {
            native_update_network_state(
                    isConnected,
                    type,
                    !capabilities.hasTransport(
                            NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING), /* isRoaming */
                    networkAvailable,
                    apn != null ? apn : "",
                    network.getNetworkHandle(),
                    NetworkAttributes.getCapabilityFlags(capabilities));
        } else if (DEBUG) {
            Log.d(TAG, "Skipped network state update because GPS HAL AGPS-RIL is not  supported");
        }
    }

    private NetworkAttributes updateTrackedNetworksState(boolean isConnected, Network network,
            NetworkCapabilities capabilities) {
        if (!isConnected) {
            // Connection lost event. So, remove it from tracked networks.
            return mAvailableNetworkAttributes.remove(network);
        }

        NetworkAttributes networkAttributes = mAvailableNetworkAttributes.get(network);
        if (networkAttributes != null) {
            // Capabilities updated event for the connected network.
            networkAttributes.mCapabilities = capabilities;
            return networkAttributes;
        }

        // Initial capabilities event (equivalent to connection available event).
        networkAttributes = new NetworkAttributes();
        networkAttributes.mCapabilities = capabilities;

        // TODO: The synchronous method ConnectivityManager.getNetworkInfo() should not be called
        //       inside the asynchronous ConnectivityManager.NetworkCallback methods.
        NetworkInfo info = mConnMgr.getNetworkInfo(network);
        if (info != null) {
            networkAttributes.mApn = info.getExtraInfo();
            networkAttributes.mType = info.getType();
        }

        // Start tracking this network for connection status updates.
        mAvailableNetworkAttributes.put(network, networkAttributes);
        return networkAttributes;
    }

    private void handleSuplConnectionAvailable(Network network) {
        // TODO: The synchronous method ConnectivityManager.getNetworkInfo() should not be called
        //       inside the asynchronous ConnectivityManager.NetworkCallback methods.
        NetworkInfo info = mConnMgr.getNetworkInfo(network);
        String apn = null;
        if (info != null) {
            apn = info.getExtraInfo();
        }

        if (DEBUG) {
            String message = String.format(
                    "handleSuplConnectionAvailable: state=%s, suplNetwork=%s, info=%s",
                    agpsDataConnStateAsString(),
                    network,
                    info);
            Log.d(TAG, message);
        }

        if (mAGpsDataConnectionState == AGPS_DATA_CONNECTION_OPENING) {
            if (apn == null) {
                // assign a dummy value in the case of C2K as otherwise we will have a runtime
                // exception in the following call to native_agps_data_conn_open
                apn = "dummy-apn";
            }

            // Setting route to host is needed for GNSS HAL implementations earlier than
            // @2.0::IAgnssCallback. The HAL @2.0::IAgnssCallback.agnssStatusCb() method does
            // not require setting route to SUPL host and hence does not provide an IP address.
            if (mAGpsDataConnectionIpAddr != null) {
                setRouting();
            }

            int apnIpType = getApnIpType(apn);
            if (DEBUG) {
                String message = String.format(
                        "native_agps_data_conn_open: mAgpsApn=%s, mApnIpType=%s",
                        apn,
                        apnIpType);
                Log.d(TAG, message);
            }
            native_agps_data_conn_open(network.getNetworkHandle(), apn, apnIpType);
            mAGpsDataConnectionState = AGPS_DATA_CONNECTION_OPEN;
        }
    }

    private void handleRequestSuplConnection(int agpsType, byte[] suplIpAddr) {
        mAGpsDataConnectionIpAddr = null;
        mAGpsType = agpsType;
        if (suplIpAddr != null) {
            if (VERBOSE) Log.v(TAG, "Received SUPL IP addr[]: " + Arrays.toString(suplIpAddr));
            try {
                mAGpsDataConnectionIpAddr = InetAddress.getByAddress(suplIpAddr);
                if (DEBUG) Log.d(TAG, "IP address converted to: " + mAGpsDataConnectionIpAddr);
            } catch (UnknownHostException e) {
                Log.e(TAG, "Bad IP Address: " + suplIpAddr, e);
            }
        }

        if (DEBUG) {
            String message = String.format(
                    "requestSuplConnection, state=%s, agpsType=%s, address=%s",
                    agpsDataConnStateAsString(),
                    agpsTypeAsString(agpsType),
                    mAGpsDataConnectionIpAddr);
            Log.d(TAG, message);
        }

        if (mAGpsDataConnectionState != AGPS_DATA_CONNECTION_CLOSED) {
            return;
        }
        mAGpsDataConnectionState = AGPS_DATA_CONNECTION_OPENING;

        // The transport type must be set to NetworkCapabilities.TRANSPORT_CELLULAR for the
        // deprecated requestRouteToHostAddress() method in ConnectivityService to work for
        // pre-gnss@2.0 devices.
        NetworkRequest.Builder networkRequestBuilder = new NetworkRequest.Builder();
        networkRequestBuilder.addCapability(getNetworkCapability(mAGpsType));
        networkRequestBuilder.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR);
        NetworkRequest networkRequest = networkRequestBuilder.build();
        mConnMgr.requestNetwork(
                networkRequest,
                mSuplConnectivityCallback,
                mHandler,
                SUPL_NETWORK_REQUEST_TIMEOUT_MILLIS);
    }

    private int getNetworkCapability(int agpsType) {
        switch (agpsType) {
            case AGPS_TYPE_C2K:
            case AGPS_TYPE_SUPL:
                return NetworkCapabilities.NET_CAPABILITY_SUPL;
            case AGPS_TYPE_EIMS:
                return NetworkCapabilities.NET_CAPABILITY_EIMS;
            case AGPS_TYPE_IMS:
                return NetworkCapabilities.NET_CAPABILITY_IMS;
            default:
                throw new IllegalArgumentException("agpsType: " + agpsType);
        }
    }

    private void handleReleaseSuplConnection(int agpsDataConnStatus) {
        if (DEBUG) {
            String message = String.format(
                    "releaseSuplConnection, state=%s, status=%s",
                    agpsDataConnStateAsString(),
                    agpsDataConnStatusAsString(agpsDataConnStatus));
            Log.d(TAG, message);
        }

        if (mAGpsDataConnectionState == AGPS_DATA_CONNECTION_CLOSED) {
            return;
        }

        mAGpsDataConnectionState = AGPS_DATA_CONNECTION_CLOSED;
        mConnMgr.unregisterNetworkCallback(mSuplConnectivityCallback);
        switch (agpsDataConnStatus) {
            case GPS_AGPS_DATA_CONN_FAILED:
                native_agps_data_conn_failed();
                break;
            case GPS_RELEASE_AGPS_DATA_CONN:
                native_agps_data_conn_closed();
                break;
            default:
                Log.e(TAG, "Invalid status to release SUPL connection: " + agpsDataConnStatus);
        }
    }

    // TODO: Delete this method when all devices upgrade to HAL @2.0::IAGnssCallback
    //       interface which does not require setting route to host.
    private void setRouting() {
        boolean result = mConnMgr.requestRouteToHostAddress(
                ConnectivityManager.TYPE_MOBILE_SUPL,
                mAGpsDataConnectionIpAddr);

        if (!result) {
            Log.e(TAG, "Error requesting route to host: " + mAGpsDataConnectionIpAddr);
        } else if (DEBUG) {
            Log.d(TAG, "Successfully requested route to host: " + mAGpsDataConnectionIpAddr);
        }
    }

    /**
     * Ensures the calling function is running in the thread associated with {@link #mHandler}.
     */
    private void ensureInHandlerThread() {
        if (mHandler != null && Looper.myLooper() == mHandler.getLooper()) {
            return;
        }
        throw new IllegalStateException("This method must run on the Handler thread.");
    }

    /**
     * @return A string representing the current state stored in {@link #mAGpsDataConnectionState}.
     */
    private String agpsDataConnStateAsString() {
        switch (mAGpsDataConnectionState) {
            case AGPS_DATA_CONNECTION_CLOSED:
                return "CLOSED";
            case AGPS_DATA_CONNECTION_OPEN:
                return "OPEN";
            case AGPS_DATA_CONNECTION_OPENING:
                return "OPENING";
            default:
                return "<Unknown>(" + mAGpsDataConnectionState + ")";
        }
    }

    /**
     * @return A string representing the given GPS_AGPS_DATA status.
     */
    private String agpsDataConnStatusAsString(int agpsDataConnStatus) {
        switch (agpsDataConnStatus) {
            case GPS_AGPS_DATA_CONNECTED:
                return "CONNECTED";
            case GPS_AGPS_DATA_CONN_DONE:
                return "DONE";
            case GPS_AGPS_DATA_CONN_FAILED:
                return "FAILED";
            case GPS_RELEASE_AGPS_DATA_CONN:
                return "RELEASE";
            case GPS_REQUEST_AGPS_DATA_CONN:
                return "REQUEST";
            default:
                return "<Unknown>(" + agpsDataConnStatus + ")";
        }
    }

    private String agpsTypeAsString(int agpsType) {
        switch (agpsType) {
            case AGPS_TYPE_SUPL:
                return "SUPL";
            case AGPS_TYPE_C2K:
                return "C2K";
            case AGPS_TYPE_EIMS:
                return "EIMS";
            case AGPS_TYPE_IMS:
                return "IMS";
            default:
                return "<Unknown>(" + agpsType + ")";
        }
    }

    private int getApnIpType(String apn) {
        ensureInHandlerThread();
        if (apn == null) {
            return APN_INVALID;
        }
        TelephonyManager phone = (TelephonyManager)
                mContext.getSystemService(Context.TELEPHONY_SERVICE);
        ServiceState serviceState = phone.getServiceState();
        String projection = null;
        String selection = null;

        // Carrier configuration may override framework roaming state, we need to use the actual
        // modem roaming state instead of the framework roaming state.
        if (serviceState != null && serviceState.getDataRoamingFromRegistration()) {
            projection = Carriers.ROAMING_PROTOCOL;
        } else {
            projection = Carriers.PROTOCOL;
        }
        // No SIM case for emergency
        if (TelephonyManager.NETWORK_TYPE_UNKNOWN == phone.getNetworkType()
                && AGPS_TYPE_EIMS == mAGpsType) {
            selection = String.format(
                "type like '%%emergency%%' and apn = '%s' and carrier_enabled = 1", apn);
        } else {
            selection = String.format("current = 1 and apn = '%s' and carrier_enabled = 1", apn);
        }
        try (Cursor cursor = mContext.getContentResolver().query(
                Carriers.CONTENT_URI,
                new String[]{projection},
                selection,
                null,
                Carriers.DEFAULT_SORT_ORDER)) {
            if (null != cursor && cursor.moveToFirst()) {
                return translateToApnIpType(cursor.getString(0), apn);
            } else {
                Log.e(TAG, "No entry found in query for APN: " + apn);
            }
        } catch (Exception e) {
            Log.e(TAG, "Error encountered on APN query for: " + apn, e);
        }

        return APN_IPV4V6;
    }

    private int translateToApnIpType(String ipProtocol, String apn) {
        if ("IP".equals(ipProtocol)) {
            return APN_IPV4;
        }
        if ("IPV6".equals(ipProtocol)) {
            return APN_IPV6;
        }
        if ("IPV4V6".equals(ipProtocol)) {
            return APN_IPV4V6;
        }

        // we hit the default case so the ipProtocol is not recognized
        String message = String.format("Unknown IP Protocol: %s, for APN: %s", ipProtocol, apn);
        Log.e(TAG, message);
        return APN_IPV4V6;
    }

    // AGPS support
    private native void native_agps_data_conn_open(long networkHandle, String apn, int apnIpType);

    private native void native_agps_data_conn_closed();

    private native void native_agps_data_conn_failed();

    // AGPS ril support
    private static native boolean native_is_agps_ril_supported();

    private native void native_update_network_state(boolean connected, int type, boolean roaming,
            boolean available, String apn, long networkHandle, short capabilities);
}