blob: b04835b5b9cc4c4682c7ea4f70d1d6f7e7e76741 [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.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.AsyncResult;
import android.os.Handler;
import android.os.Message;
import android.os.RegistrantList;
import android.telephony.AccessNetworkConstants.AccessNetworkType;
import android.telephony.AccessNetworkConstants.TransportType;
import android.telephony.CarrierConfigManager;
import android.telephony.Rlog;
import android.telephony.data.ApnSetting;
import android.telephony.data.ApnSetting.ApnType;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.telephony.Phone;
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.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.
*/
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.UNKNOWN, TransportType.WWAN);
ACCESS_NETWORK_TRANSPORT_TYPE_MAP.put(AccessNetworkType.GERAN, TransportType.WWAN);
ACCESS_NETWORK_TRANSPORT_TYPE_MAP.put(AccessNetworkType.UTRAN, TransportType.WWAN);
ACCESS_NETWORK_TRANSPORT_TYPE_MAP.put(AccessNetworkType.EUTRAN, TransportType.WWAN);
ACCESS_NETWORK_TRANSPORT_TYPE_MAP.put(AccessNetworkType.CDMA2000, TransportType.WWAN);
ACCESS_NETWORK_TRANSPORT_TYPE_MAP.put(AccessNetworkType.IWLAN, TransportType.WLAN);
}
private static final int EVENT_QUALIFIED_NETWORKS_CHANGED = 1;
public static final String SYSTEM_PROPERTIES_IWLAN_OPERATION_MODE =
"ro.telephony.iwlan_operation_mode";
@Retention(RetentionPolicy.SOURCE)
@IntDef(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 int IWLAN_OPERATION_MODE_DEFAULT = 0;
/**
* 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 int IWLAN_OPERATION_MODE_LEGACY = 1;
/**
* IWLAN application processor assisted mode. IWLAN is handled by the bound IWLAN data service
* and network service separately.
*/
public static final int IWLAN_OPERATION_MODE_AP_ASSISTED = 2;
private final Phone mPhone;
/** The available transports. Must be one or more of AccessNetworkConstants.TransportType.XXX */
private final int[] mAvailableTransports;
@Nullable
private AccessNetworksManager mAccessNetworksManager;
/**
* Available networks. The key is the APN type, and the value is the available network list in
* the preferred order.
*/
private final Map<Integer, int[]> mCurrentAvailableNetworks;
/**
* 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 registrants for listening data handover needed events.
*/
private final RegistrantList mHandoverNeededEventRegistrants;
/**
* Handover parameters
*/
@VisibleForTesting
public static final class HandoverParams {
public final @ApnType int apnType;
public final int targetTransport;
HandoverParams(int apnType, int targetTransport) {
this.apnType = apnType;
this.targetTransport = targetTransport;
}
}
public TransportManager(Phone phone) {
mPhone = phone;
mCurrentAvailableNetworks = new ConcurrentHashMap<>();
mCurrentTransports = new ConcurrentHashMap<>();
mHandoverNeededEventRegistrants = new RegistrantList();
if (isInLegacyMode()) {
// For legacy mode, WWAN is the only transport to handle all data connections, even
// the IWLAN ones.
mAvailableTransports = new int[]{TransportType.WWAN};
} else {
mAccessNetworksManager = new AccessNetworksManager(phone);
mAccessNetworksManager.registerForQualifiedNetworksChanged(this,
EVENT_QUALIFIED_NETWORKS_CHANGED);
mAvailableTransports = new int[]{TransportType.WWAN, TransportType.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;
updateAvailableNetworks(networks);
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 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;
}
// 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(
ACCESS_NETWORK_TRANSPORT_TYPE_MAP.get(currentNetworkList[0]));
}
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.
*
* @apnType The APN type
* @transport The transport. Must be WWAN or WLAN.
*/
public void setCurrentTransport(@ApnType int apnType, int transport) {
mCurrentTransports.put(apnType, transport);
}
private synchronized void updateAvailableNetworks(List<QualifiedNetworks> networksList) {
log("updateAvailableNetworks: " + networksList);
for (QualifiedNetworks networks : networksList) {
if (areNetworksValid(networks)) {
if (isHandoverNeeded(networks)) {
mCurrentAvailableNetworks.put(networks.apnType, networks.qualifiedNetworks);
// 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]);
log("Handover needed for APN type: "
+ ApnSetting.getApnTypeString(networks.apnType) + ", target transport: "
+ TransportType.toString(targetTransport));
mHandoverNeededEventRegistrants.notifyResult(
new HandoverParams(networks.apnType, targetTransport));
} else {
// If handover is not needed, immediately update the available networks and
// transport.
log("Handover not needed for APN type: "
+ ApnSetting.getApnTypeString(networks.apnType));
mCurrentAvailableNetworks.put(networks.apnType, networks.qualifiedNetworks);
int transport = TransportType.WWAN;
if (!ArrayUtils.isEmpty(networks.qualifiedNetworks)
&& ACCESS_NETWORK_TRANSPORT_TYPE_MAP.containsKey(
networks.qualifiedNetworks[0])) {
// 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.
transport = ACCESS_NETWORK_TRANSPORT_TYPE_MAP
.get(networks.qualifiedNetworks[0]);
}
setCurrentTransport(networks.apnType, transport);
}
}
}
}
/**
* @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 True if in IWLAN legacy mode. Operating in legacy mode means telephony will send
* all data requests to the default data service, which is the cellular data service.
* AP-assisted mode requires properly configuring the resource overlay
* 'config_wwan_data_service_package' (or the carrier config
* {@link CarrierConfigManager#KEY_CARRIER_DATA_SERVICE_WLAN_PACKAGE_OVERRIDE_STRING }) to
* the IWLAN data service package, 'config_wwan_network_service_package' (or the carrier config
* {@link CarrierConfigManager#KEY_CARRIER_NETWORK_SERVICE_WLAN_PACKAGE_OVERRIDE_STRING })
* to the IWLAN network service package, and 'config_qualified_networks_service_package' (or the
* carrier config
* {@link CarrierConfigManager#KEY_CARRIER_QUALIFIED_NETWORKS_SERVICE_PACKAGE_OVERRIDE_STRING})
* to the qualified networks service package.
*/
public boolean isInLegacyMode() {
return (mPhone.mCi.getIwlanOperationMode() == IWLAN_OPERATION_MODE_LEGACY);
}
/**
* 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 TransportType.WWAN;
}
// If we can't find the corresponding transport, always route to cellular.
return mCurrentTransports.get(apnType) == null
? TransportType.WWAN : mCurrentTransports.get(apnType);
}
/**
* 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 -> TransportType.toString(type))
.collect(Collectors.joining(",")) + "]");
pw.println("mCurrentAvailableNetworks=" + mCurrentAvailableNetworks);
pw.println("mCurrentTransports=" + mCurrentTransports);
pw.println("isInLegacy=" + isInLegacyMode());
pw.println("IWLAN operation mode=" + mPhone.mCi.getIwlanOperationMode());
if (mAccessNetworksManager != null) {
mAccessNetworksManager.dump(fd, pw, args);
}
pw.decreaseIndent();
pw.flush();
}
private void log(String s) {
Rlog.d(TAG, s);
}
private void loge(String s) {
Rlog.e(TAG, s);
}
}