blob: ea112239977b9d355227352dc584fbeaeb03e3fc [file] [log] [blame]
/*
* Copyright 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.internal.telephony.dataconnection;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.StringDef;
import android.os.AsyncResult;
import android.os.Handler;
import android.os.Message;
import android.os.RegistrantList;
import android.os.SystemProperties;
import android.telephony.AccessNetworkConstants;
import android.telephony.AccessNetworkConstants.AccessNetworkType;
import android.telephony.CarrierConfigManager;
import android.telephony.Rlog;
import android.telephony.data.ApnSetting;
import android.telephony.data.ApnSetting.ApnType;
import android.util.LocalLog;
import android.util.SparseArray;
import android.util.SparseIntArray;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.telephony.Phone;
import com.android.internal.telephony.RIL;
import com.android.internal.telephony.dataconnection.AccessNetworksManager.QualifiedNetworks;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.IndentingPrintWriter;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
/**
* This class represents the transport manager which manages available transports (i.e. WWAN or
* WLAN) and determine the correct transport for {@link TelephonyNetworkFactory} to handle the data
* requests.
*
* The device can operate in the following modes, which is stored in the system properties
* ro.telephony.iwlan_operation_mode. If the system properties is missing, then it's tied to
* IRadio version. For 1.4 or above, it's legacy mode. For 1.3 or below, it's
*
* Legacy mode:
* Frameworks send all data requests to the default data service, which is the cellular data
* service. IWLAN should be still reported as a RAT on cellular network service.
*
* AP-assisted mode:
* IWLAN is handled by IWLAN data service extending {@link android.telephony.data.DataService},
* IWLAN network service extending {@link android.telephony.NetworkService}, and qualified
* network service extending {@link android.telephony.data.QualifiedNetworksService}.
*
* The following settings for service package name need to be configured properly for
* frameworks to bind.
*
* Package name of data service:
* The resource overlay 'config_wwan_data_service_package' or,
* the carrier config
* {@link CarrierConfigManager#KEY_CARRIER_DATA_SERVICE_WLAN_PACKAGE_OVERRIDE_STRING}.
* The carrier config takes precedence over the resource overlay if both exist.
*
* Package name of network service
* The resource overlay 'config_wwan_network_service_package' or
* the carrier config
* {@link CarrierConfigManager#KEY_CARRIER_NETWORK_SERVICE_WLAN_PACKAGE_OVERRIDE_STRING}.
* The carrier config takes precedence over the resource overlay if both exist.
*
* Package name of qualified network service
* The resource overlay 'config_qualified_networks_service_package' or
* the carrier config
* {@link CarrierConfigManager#
* KEY_CARRIER_QUALIFIED_NETWORKS_SERVICE_PACKAGE_OVERRIDE_STRING}.
* The carrier config takes precedence over the resource overlay if both exist.
*/
public class TransportManager extends Handler {
private static final String TAG = TransportManager.class.getSimpleName();
// Key is the access network, value is the transport.
private static final Map<Integer, Integer> ACCESS_NETWORK_TRANSPORT_TYPE_MAP;
static {
ACCESS_NETWORK_TRANSPORT_TYPE_MAP = new HashMap<>();
ACCESS_NETWORK_TRANSPORT_TYPE_MAP.put(AccessNetworkType.GERAN,
AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
ACCESS_NETWORK_TRANSPORT_TYPE_MAP.put(AccessNetworkType.UTRAN,
AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
ACCESS_NETWORK_TRANSPORT_TYPE_MAP.put(AccessNetworkType.EUTRAN,
AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
ACCESS_NETWORK_TRANSPORT_TYPE_MAP.put(AccessNetworkType.CDMA2000,
AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
ACCESS_NETWORK_TRANSPORT_TYPE_MAP.put(AccessNetworkType.IWLAN,
AccessNetworkConstants.TRANSPORT_TYPE_WLAN);
}
private static final int EVENT_QUALIFIED_NETWORKS_CHANGED = 1;
private static final int EVENT_UPDATE_AVAILABLE_NETWORKS = 2;
public static final String SYSTEM_PROPERTIES_IWLAN_OPERATION_MODE =
"ro.telephony.iwlan_operation_mode";
@Retention(RetentionPolicy.SOURCE)
@StringDef(prefix = {"IWLAN_OPERATION_MODE_"},
value = {
IWLAN_OPERATION_MODE_DEFAULT,
IWLAN_OPERATION_MODE_LEGACY,
IWLAN_OPERATION_MODE_AP_ASSISTED})
public @interface IwlanOperationMode {}
/**
* IWLAN default mode. On device that has IRadio 1.3 or above, it means
* {@link #IWLAN_OPERATION_MODE_AP_ASSISTED}. On device that has IRadio 1.2 or below, it means
* {@link #IWLAN_OPERATION_MODE_LEGACY}.
*/
public static final String IWLAN_OPERATION_MODE_DEFAULT = "default";
/**
* IWLAN legacy mode. IWLAN is completely handled by the modem, and when the device is on
* IWLAN, modem reports IWLAN as a RAT.
*/
public static final String IWLAN_OPERATION_MODE_LEGACY = "legacy";
/**
* IWLAN application processor assisted mode. IWLAN is handled by the bound IWLAN data service
* and network service separately.
*/
public static final String IWLAN_OPERATION_MODE_AP_ASSISTED = "AP-assisted";
private final Phone mPhone;
private final LocalLog mLocalLog = new LocalLog(100);
/** The available transports. Must be one or more of AccessNetworkConstants.TransportType.XXX */
private final int[] mAvailableTransports;
@Nullable
private AccessNetworksManager mAccessNetworksManager;
/**
* Current available networks. The key is the APN type, and the value is the available network
* list in the preferred order.
*/
private final SparseArray<int[]> mCurrentAvailableNetworks;
/**
* The queued available networks list.
*/
private final LinkedList<List<QualifiedNetworks>> mAvailableNetworksList;
/**
* The current transport of the APN type. The key is the APN type, and the value is the
* transport.
*/
private final Map<Integer, Integer> mCurrentTransports;
/**
* The pending handover list. This is a list of APNs that are being handover to the new
* transport. The entry will be removed once handover is completed. The key
* is the APN type, and the value is the target transport that the APN is handovered to.
*/
private final SparseIntArray mPendingHandoverApns;
/**
* The registrants for listening data handover needed events.
*/
private final RegistrantList mHandoverNeededEventRegistrants;
/**
* Handover parameters
*/
@VisibleForTesting
public static final class HandoverParams {
/**
* The callback for handover complete.
*/
public interface HandoverCallback {
/**
* Called when handover is completed.
*
* @param success {@true} if handover succeeded, otherwise failed.
* @param fallback {@true} if handover failed, the data connection fallback to the
* original transport
*/
void onCompleted(boolean success, boolean fallback);
}
public final @ApnType int apnType;
public final int targetTransport;
public final HandoverCallback callback;
HandoverParams(int apnType, int targetTransport, HandoverCallback callback) {
this.apnType = apnType;
this.targetTransport = targetTransport;
this.callback = callback;
}
}
public TransportManager(Phone phone) {
mPhone = phone;
mCurrentAvailableNetworks = new SparseArray<>();
mCurrentTransports = new ConcurrentHashMap<>();
mPendingHandoverApns = new SparseIntArray();
mHandoverNeededEventRegistrants = new RegistrantList();
mAvailableNetworksList = new LinkedList<>();
if (isInLegacyMode()) {
log("operates in legacy mode.");
// For legacy mode, WWAN is the only transport to handle all data connections, even
// the IWLAN ones.
mAvailableTransports = new int[]{AccessNetworkConstants.TRANSPORT_TYPE_WWAN};
} else {
log("operates in AP-assisted mode.");
mAccessNetworksManager = new AccessNetworksManager(phone);
mAccessNetworksManager.registerForQualifiedNetworksChanged(this,
EVENT_QUALIFIED_NETWORKS_CHANGED);
mAvailableTransports = new int[]{AccessNetworkConstants.TRANSPORT_TYPE_WWAN,
AccessNetworkConstants.TRANSPORT_TYPE_WLAN};
}
}
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case EVENT_QUALIFIED_NETWORKS_CHANGED:
AsyncResult ar = (AsyncResult) msg.obj;
List<QualifiedNetworks> networks = (List<QualifiedNetworks>) ar.result;
mAvailableNetworksList.add(networks);
sendEmptyMessage(EVENT_UPDATE_AVAILABLE_NETWORKS);
break;
case EVENT_UPDATE_AVAILABLE_NETWORKS:
updateAvailableNetworks();
break;
default:
loge("Unexpected event " + msg.what);
break;
}
}
private boolean isHandoverNeeded(QualifiedNetworks newNetworks) {
int apnType = newNetworks.apnType;
int[] newNetworkList = newNetworks.qualifiedNetworks;
int[] currentNetworkList = mCurrentAvailableNetworks.get(apnType);
if (ArrayUtils.isEmpty(currentNetworkList)
&& ACCESS_NETWORK_TRANSPORT_TYPE_MAP.get(newNetworkList[0])
== AccessNetworkConstants.TRANSPORT_TYPE_WLAN) {
// This is a special case that when first time boot up in airplane mode with wifi on,
// qualified network service reports IWLAN as the preferred network. Although there
// is no live data connection on cellular, there might be network requests which were
// already sent to cellular DCT. In this case, we still need to handover the network
// request to the new transport.
return true;
}
// If the current network list is empty, but the new network list is not, then we can
// directly setup data on the new network. If the current network list is not empty, but
// the new network is, then we can tear down the data directly. Therefore if one of the
// list is empty, then we don't need to do handover.
if (ArrayUtils.isEmpty(newNetworkList) || ArrayUtils.isEmpty(currentNetworkList)) {
return false;
}
if (mPendingHandoverApns.get(newNetworks.apnType)
== ACCESS_NETWORK_TRANSPORT_TYPE_MAP.get(newNetworkList[0])) {
log("Handover not needed. There is already an ongoing handover.");
return false;
}
// The list is networks in the preferred order. For now we only pick the first element
// because it's the most preferred. In the future we should also consider the rest in the
// list, for example, the first one violates carrier/user policy.
return !ACCESS_NETWORK_TRANSPORT_TYPE_MAP.get(newNetworkList[0])
.equals(getCurrentTransport(newNetworks.apnType));
}
private static boolean areNetworksValid(QualifiedNetworks networks) {
if (networks.qualifiedNetworks == null) {
return false;
}
for (int network : networks.qualifiedNetworks) {
if (!ACCESS_NETWORK_TRANSPORT_TYPE_MAP.containsKey(network)) {
return false;
}
}
return true;
}
/**
* Set the current transport of apn type.
*
* @param apnType The APN type
* @param transport The transport. Must be WWAN or WLAN.
*/
private synchronized void setCurrentTransport(@ApnType int apnType, int transport) {
mCurrentTransports.put(apnType, transport);
logl("setCurrentTransport: apnType=" + ApnSetting.getApnTypeString(apnType)
+ ", transport=" + AccessNetworkConstants.transportTypeToString(transport));
}
private boolean isHandoverPending() {
return mPendingHandoverApns.size() > 0;
}
private void updateAvailableNetworks() {
if (isHandoverPending()) {
log("There's ongoing handover. Will update networks once handover completed.");
return;
}
if (mAvailableNetworksList.size() == 0) {
log("Nothing in the available network list queue.");
return;
}
List<QualifiedNetworks> networksList = mAvailableNetworksList.remove();
logl("updateAvailableNetworks: " + networksList);
for (QualifiedNetworks networks : networksList) {
if (areNetworksValid(networks)) {
if (isHandoverNeeded(networks)) {
// If handover is needed, perform the handover works. For now we only pick the
// first element because it's the most preferred. In the future we should also
// consider the rest in the list, for example, the first one violates
// carrier/user policy.
int targetTransport = ACCESS_NETWORK_TRANSPORT_TYPE_MAP.get(
networks.qualifiedNetworks[0]);
logl("Handover needed for APN type: "
+ ApnSetting.getApnTypeString(networks.apnType) + ", target transport: "
+ AccessNetworkConstants.transportTypeToString(targetTransport));
mPendingHandoverApns.put(networks.apnType, targetTransport);
mHandoverNeededEventRegistrants.notifyResult(
new HandoverParams(networks.apnType, targetTransport,
(success, fallback) -> {
// The callback for handover completed.
if (success) {
logl("Handover succeeded.");
} else {
logl("APN type "
+ ApnSetting.getApnTypeString(networks.apnType)
+ " handover to "
+ AccessNetworkConstants.transportTypeToString(
targetTransport) + " failed."
+ ", fallback=" + fallback);
}
if (success || !fallback) {
// If handover succeeds or failed without falling back
// to the original transport, we should move to the new
// transport (even if it is failed).
setCurrentTransport(networks.apnType, targetTransport);
}
mPendingHandoverApns.delete(networks.apnType);
// If there are still pending available network changes, we
// need to process the rest.
if (mAvailableNetworksList.size() > 0) {
sendEmptyMessage(EVENT_UPDATE_AVAILABLE_NETWORKS);
}
}));
}
mCurrentAvailableNetworks.put(networks.apnType, networks.qualifiedNetworks);
} else {
loge("Invalid networks received: " + networks);
}
}
// If there are still pending available network changes, we need to process the rest.
if (mAvailableNetworksList.size() > 0) {
sendEmptyMessage(EVENT_UPDATE_AVAILABLE_NETWORKS);
}
}
/**
* @return The available transports. Note that on legacy devices, the only available transport
* would be WWAN only. If the device is configured as AP-assisted mode, the available transport
* will always be WWAN and WLAN (even if the device is not camped on IWLAN).
* See {@link #isInLegacyMode()} for mode details.
*/
public synchronized @NonNull int[] getAvailableTransports() {
return mAvailableTransports;
}
/**
* @return {@code true} if the device operates in legacy mode, otherwise {@code false}.
*/
public boolean isInLegacyMode() {
// Get IWLAN operation mode from the system property. If the system property is missing or
// mis-configured the default behavior is tied to the IRadio version. For 1.4 or above, it's
// AP-assisted mode, for 1.3 or below, it's legacy mode. For IRadio 1.3 or below, no matter
// what the configuration is, it will always be legacy mode.
String mode = SystemProperties.get(SYSTEM_PROPERTIES_IWLAN_OPERATION_MODE);
return mode.equals(IWLAN_OPERATION_MODE_LEGACY)
|| mPhone.getHalVersion().less(RIL.RADIO_HAL_VERSION_1_4);
}
/**
* Get the transport based on the APN type.
*
* @param apnType APN type
* @return The transport type
*/
public int getCurrentTransport(@ApnType int apnType) {
// In legacy mode, always route to cellular.
if (isInLegacyMode()) {
return AccessNetworkConstants.TRANSPORT_TYPE_WWAN;
}
// If we can't find the corresponding transport, always route to cellular.
return mCurrentTransports.get(apnType) == null
? AccessNetworkConstants.TRANSPORT_TYPE_WWAN : mCurrentTransports.get(apnType);
}
/**
* Check if there is any APN type of network preferred on IWLAN.
*
* @return {@code true} if there is any APN preferred on IWLAN, otherwise {@code false}.
*/
public boolean isAnyApnPreferredOnIwlan() {
for (int i = 0; i < mCurrentAvailableNetworks.size(); i++) {
int[] networkList = mCurrentAvailableNetworks.valueAt(i);
if (networkList.length > 0 && networkList[0] == AccessNetworkType.IWLAN) {
return true;
}
}
return false;
}
/**
* Register for data handover needed event
*
* @param h The handler of the event
* @param what The id of the event
*/
public void registerForHandoverNeededEvent(Handler h, int what) {
if (h != null) {
mHandoverNeededEventRegistrants.addUnique(h, what, null);
}
}
/**
* Unregister for data handover needed event
*
* @param h The handler
*/
public void unregisterForHandoverNeededEvent(Handler h) {
mHandoverNeededEventRegistrants.remove(h);
}
/**
* Dump the state of transport manager
*
* @param fd File descriptor
* @param printwriter Print writer
* @param args Arguments
*/
public void dump(FileDescriptor fd, PrintWriter printwriter, String[] args) {
IndentingPrintWriter pw = new IndentingPrintWriter(printwriter, " ");
pw.println("TransportManager:");
pw.increaseIndent();
pw.println("mAvailableTransports=[" + Arrays.stream(mAvailableTransports)
.mapToObj(type -> AccessNetworkConstants.transportTypeToString(type))
.collect(Collectors.joining(",")) + "]");
pw.println("mCurrentAvailableNetworks=" + mCurrentAvailableNetworks);
pw.println("mAvailableNetworksList=" + mAvailableNetworksList);
pw.println("mPendingHandoverApns=" + mPendingHandoverApns);
pw.println("mCurrentTransports=" + mCurrentTransports);
pw.println("isInLegacy=" + isInLegacyMode());
pw.println("IWLAN operation mode="
+ SystemProperties.get(SYSTEM_PROPERTIES_IWLAN_OPERATION_MODE));
if (mAccessNetworksManager != null) {
mAccessNetworksManager.dump(fd, pw, args);
}
pw.println("Local logs=");
pw.increaseIndent();
mLocalLog.dump(fd, pw, args);
pw.decreaseIndent();
pw.decreaseIndent();
pw.flush();
}
private void logl(String s) {
log(s);
mLocalLog.log(s);
}
private void log(String s) {
Rlog.d(TAG, s);
}
private void loge(String s) {
Rlog.e(TAG, s);
}
}