| /* |
| * Copyright (C) 2020 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.gnss; |
| |
| 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.telephony.SubscriptionInfo; |
| import android.telephony.SubscriptionManager; |
| import android.telephony.PreciseCallState; |
| import android.telephony.PhoneStateListener; |
| import android.util.Log; |
| |
| import com.android.internal.location.GpsNetInitiatedHandler; |
| |
| import java.net.InetAddress; |
| import java.net.UnknownHostException; |
| import java.util.Arrays; |
| import java.util.Map; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Iterator; |
| |
| /** |
| * 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); |
| |
| // Phone State Listeners to track all the active sub IDs |
| private HashMap<Integer, SubIdPhoneStateListener> mPhoneStateListeners; |
| |
| private final ConnectivityManager mConnMgr; |
| |
| private final Handler mHandler; |
| private final GnssNetworkListener mGnssNetworkListener; |
| |
| private int mAGpsDataConnectionState; |
| private InetAddress mAGpsDataConnectionIpAddr; |
| private int mAGpsType; |
| private int mActiveSubId = -1; |
| private final GpsNetInitiatedHandler mNiHandler; |
| |
| |
| 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, |
| GpsNetInitiatedHandler niHandler) { |
| mContext = context; |
| mGnssNetworkListener = gnssNetworkListener; |
| |
| SubscriptionManager subManager = mContext.getSystemService(SubscriptionManager.class); |
| if (subManager != null) { |
| subManager.addOnSubscriptionsChangedListener(mOnSubscriptionsChangeListener); |
| } |
| |
| PowerManager powerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE); |
| mWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, WAKELOCK_KEY); |
| |
| mHandler = new Handler(looper); |
| mNiHandler = niHandler; |
| mConnMgr = (ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE); |
| mSuplConnectivityCallback = createSuplConnectivityCallback(); |
| } |
| |
| /** |
| * SubId Phone State Listener is used cache the last active Sub ID when a call is made, |
| * which will be used during an emergency call to set the Network Specifier to the particular |
| * sub when an emergency supl connection is requested |
| */ |
| private final class SubIdPhoneStateListener extends PhoneStateListener { |
| private Integer mSubId; |
| SubIdPhoneStateListener(Integer subId) { |
| mSubId = subId; |
| } |
| @Override |
| public void onPreciseCallStateChanged(PreciseCallState state) { |
| if (state.PRECISE_CALL_STATE_ACTIVE == state.getForegroundCallState()) { |
| mActiveSubId = mSubId; |
| if (DEBUG) Log.d(TAG, "mActiveSubId: " + mActiveSubId); |
| } |
| } |
| }; |
| |
| /** |
| * Subscription Changed Listener is used to get all active subscriptions and create a |
| * Phone State Listener for each Sub ID that we find in the active subscription list |
| */ |
| private final SubscriptionManager.OnSubscriptionsChangedListener mOnSubscriptionsChangeListener |
| = new SubscriptionManager.OnSubscriptionsChangedListener() { |
| @Override |
| public void onSubscriptionsChanged() { |
| if (mPhoneStateListeners == null) { |
| // Capacity=2 Load-Factor=1.0, as typically no more than 2 SIMs |
| mPhoneStateListeners = new HashMap<Integer, SubIdPhoneStateListener>(2,1); |
| } |
| SubscriptionManager subManager = mContext.getSystemService(SubscriptionManager.class); |
| TelephonyManager telManager = mContext.getSystemService(TelephonyManager.class); |
| if (subManager != null && telManager != null) { |
| List<SubscriptionInfo> subscriptionInfoList = |
| subManager.getActiveSubscriptionInfoList(); |
| HashSet<Integer> activeSubIds = new HashSet<Integer>(); |
| if (subscriptionInfoList != null) { |
| if (DEBUG) Log.d(TAG, "Active Sub List size: " + subscriptionInfoList.size()); |
| // populate phone state listeners with all new active subs |
| for (SubscriptionInfo subInfo : subscriptionInfoList) { |
| activeSubIds.add(subInfo.getSubscriptionId()); |
| if (!mPhoneStateListeners.containsKey(subInfo.getSubscriptionId())) { |
| TelephonyManager subIdTelManager = |
| telManager.createForSubscriptionId(subInfo.getSubscriptionId()); |
| if (subIdTelManager != null) { |
| if (DEBUG) Log.d(TAG, "Listener sub" + subInfo.getSubscriptionId()); |
| SubIdPhoneStateListener subIdPhoneStateListener = |
| new SubIdPhoneStateListener(subInfo.getSubscriptionId()); |
| mPhoneStateListeners.put(subInfo.getSubscriptionId(), |
| subIdPhoneStateListener); |
| subIdTelManager.listen(subIdPhoneStateListener, |
| PhoneStateListener.LISTEN_PRECISE_CALL_STATE); |
| } |
| } |
| } |
| } |
| // clean up phone state listeners than no longer have active subs |
| Iterator<Map.Entry<Integer, SubIdPhoneStateListener> > iterator = |
| mPhoneStateListeners.entrySet().iterator(); |
| while (iterator.hasNext()) { |
| Map.Entry<Integer, SubIdPhoneStateListener> element = iterator.next(); |
| if (!activeSubIds.contains(element.getKey())) { |
| TelephonyManager subIdTelManager = |
| telManager.createForSubscriptionId(element.getKey()); |
| if (subIdTelManager != null) { |
| if (DEBUG) Log.d(TAG, "unregister listener sub " + element.getKey()); |
| subIdTelManager.listen(element.getValue(), |
| PhoneStateListener.LISTEN_NONE); |
| // removes the element from mPhoneStateListeners |
| iterator.remove(); |
| } else { |
| Log.e(TAG, "Telephony Manager for Sub " + element.getKey() + " null"); |
| } |
| } |
| } |
| // clean up cached active phone call sub if it is no longer an active sub |
| if (!activeSubIds.contains(mActiveSubId)) { |
| mActiveSubId = -1; |
| } |
| } |
| } |
| }; |
| |
| 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 = false; |
| TelephonyManager telephonyManager = mContext.getSystemService(TelephonyManager.class); |
| if (telephonyManager != null) { |
| networkAvailable = isConnected && telephonyManager.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); |
| // During an emergency call, and when we have cached the Active Sub Id, we set the |
| // Network Specifier so that the network request goes to the correct Sub Id |
| if (mNiHandler.getInEmergency() && mActiveSubId >= 0) { |
| if (DEBUG) Log.d(TAG, "Adding Network Specifier: " + Integer.toString(mActiveSubId)); |
| networkRequestBuilder.setNetworkSpecifier(Integer.toString(mActiveSubId)); |
| } |
| 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); |
| // During an emergency call with an active sub id, get the Telephony Manager specific |
| // to the active sub to get the correct value from getServiceState and getNetworkType |
| if (mNiHandler.getInEmergency() && mActiveSubId >= 0) { |
| TelephonyManager subIdTelManager = |
| phone.createForSubscriptionId(mActiveSubId); |
| if (subIdTelManager != null) { |
| phone = subIdTelManager; |
| } |
| } |
| 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); |
| } |