Loading src/java/com/android/internal/telephony/TransportManager.java +121 −15 Original line number Diff line number Diff line Loading @@ -21,10 +21,12 @@ import android.annotation.NonNull; 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.telephony.Phone; Loading @@ -51,6 +53,7 @@ import java.util.stream.Collectors; 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 { Loading Loading @@ -108,6 +111,26 @@ public class TransportManager extends Handler { */ 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; 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; mAccessNetworksManager = new AccessNetworksManager(phone); Loading @@ -116,6 +139,8 @@ public class TransportManager extends Handler { EVENT_QUALIFIED_NETWORKS_CHANGED); 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 Loading @@ -140,10 +165,81 @@ public class TransportManager extends Handler { } } 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)) { mCurrentAvailableNetworks.put(networks.apnType, networks.qualifiedNetworks); 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]); 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)); 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); } } } } Loading Loading @@ -175,7 +271,7 @@ public class TransportManager extends Handler { } /** * Get the transport based on the APN type * Get the transport based on the APN type. * * @param apnType APN type * @return The transport type Loading @@ -186,22 +282,30 @@ public class TransportManager extends Handler { return TransportType.WWAN; } // If we can't find the available networks, always route to cellular. if (!mCurrentAvailableNetworks.containsKey(apnType)) { 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); } int[] availableNetworks = mCurrentAvailableNetworks.get(apnType); // If the available networks list is empty, route to cellular. if (ArrayUtils.isEmpty(availableNetworks)) { return TransportType.WWAN; /** * 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); } } // TODO: For now we choose the first network because it's the most preferred one. In the // the future we should validate it to make sure this network does not violate carrier // preference and user preference. return ACCESS_NETWORK_TRANSPORT_TYPE_MAP.get(availableNetworks[0]); /** * Unregister for data handover needed event * * @param h The handler */ public void unregisterForHandoverNeededEvent(Handler h) { mHandoverNeededEventRegistrants.remove(h); } /** Loading @@ -218,6 +322,8 @@ public class TransportManager extends Handler { 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()); mAccessNetworksManager.dump(fd, pw, args); Loading src/java/com/android/internal/telephony/dataconnection/AccessNetworksManager.java +1 −0 Original line number Diff line number Diff line Loading @@ -106,6 +106,7 @@ public class AccessNetworksManager { */ public static class QualifiedNetworks { public final @ApnType int apnType; // The qualified netowrks in preferred order. Each network is a AccessNetworkType. public final int[] qualifiedNetworks; public QualifiedNetworks(@ApnType int apnType, int[] qualifiedNetworks) { this.apnType = apnType; Loading src/java/com/android/internal/telephony/dataconnection/ApnContext.java +23 −9 Original line number Diff line number Diff line Loading @@ -21,6 +21,7 @@ import android.net.ConnectivityManager; import android.net.NetworkCapabilities; import android.net.NetworkConfig; import android.net.NetworkRequest; import android.os.Message; import android.telephony.Rlog; import android.telephony.data.ApnSetting; import android.telephony.data.ApnSetting.ApnType; Loading @@ -39,6 +40,7 @@ import com.android.internal.util.IndentingPrintWriter; import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.ArrayList; import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; Loading Loading @@ -146,7 +148,6 @@ public class ApnContext { return mDataConnection; } /** * Set the associated data connection. * @param dc data connection Loading Loading @@ -413,15 +414,17 @@ public class ApnContext { } } public void requestNetwork(NetworkRequest networkRequest, @RequestNetworkType int type, LocalLog log) { public void requestNetwork(NetworkRequest networkRequest, @RequestNetworkType int type, Message onCompleteMsg, LocalLog log) { synchronized (mRefCountLock) { if (mLocalLogs.contains(log) || mNetworkRequests.contains(networkRequest)) { log.log("ApnContext.requestNetwork has duplicate add - " + mNetworkRequests.size()); } else { mLocalLogs.add(log); mNetworkRequests.add(networkRequest); mDcTracker.enableApn(ApnSetting.getApnTypesBitmaskFromString(mApnType), type); mDcTracker.enableApn(ApnSetting.getApnTypesBitmaskFromString(mApnType), type, onCompleteMsg); if (mDataConnection != null) { // New network request added. Should re-evaluate properties of // the data connection. For example, the score may change. mDataConnection.reevaluateDataConnectionProperties(); } } } Loading @@ -439,6 +442,11 @@ public class ApnContext { + networkRequest + ")"); } else { mNetworkRequests.remove(networkRequest); if (mDataConnection != null) { // New network request added. Should re-evaluate properties of // the data connection. For example, the score may change. mDataConnection.reevaluateDataConnectionProperties(); } log.log("ApnContext.releaseNetwork left with " + mNetworkRequests.size() + " requests."); if (mNetworkRequests.size() == 0 Loading Loading @@ -630,6 +638,12 @@ public class ApnContext { return apnType; } public List<NetworkRequest> getNetworkRequests() { synchronized (mRefCountLock) { return new ArrayList<NetworkRequest>(mNetworkRequests); } } @Override public synchronized String toString() { // We don't print mDataConnection because its recursive. Loading src/java/com/android/internal/telephony/dataconnection/DataConnection.java +68 −2 Original line number Diff line number Diff line Loading @@ -30,6 +30,7 @@ import android.net.NetworkAgent; import android.net.NetworkCapabilities; import android.net.NetworkInfo; import android.net.NetworkMisc; import android.net.NetworkRequest; import android.net.NetworkUtils; import android.net.ProxyInfo; import android.net.RouteInfo; Loading Loading @@ -108,6 +109,18 @@ public class DataConnection extends StateMachine { private static final String NETWORK_TYPE = "MOBILE"; // The data connection providing default Internet connection will have a higher score of 50. // Other connections will have a slightly lower score of 45. The intention is other connections // will not cause ConnectivityService to tear down default internet connection. For example, // to validate Internet connection on non-default data SIM, we'll set up a temporary Internet // connection on that data SIM. In this case, score of 45 is assigned so ConnectivityService // will not replace the default Internet connection with it. private static final int DEFAULT_INTERNET_CONNECTION_SCORE = 50; private static final int OTHER_CONNECTION_SCORE = 45; // The score we report to connectivity service private int mScore; // The data connection controller private DcController mDcController; Loading Loading @@ -240,8 +253,10 @@ public class DataConnection extends StateMachine { static final int EVENT_LINK_CAPACITY_CHANGED = BASE + 23; static final int EVENT_RESET = BASE + 24; static final int EVENT_REEVALUATE_RESTRICTED_STATE = BASE + 25; static final int EVENT_REEVALUATE_DATA_CONNECTION_PROPERTIES = BASE + 26; private static final int CMD_TO_STRING_COUNT = EVENT_REEVALUATE_RESTRICTED_STATE - BASE + 1; private static final int CMD_TO_STRING_COUNT = EVENT_REEVALUATE_DATA_CONNECTION_PROPERTIES - BASE + 1; private static String[] sCmdToString = new String[CMD_TO_STRING_COUNT]; static { Loading Loading @@ -275,6 +290,8 @@ public class DataConnection extends StateMachine { sCmdToString[EVENT_RESET - BASE] = "EVENT_RESET"; sCmdToString[EVENT_REEVALUATE_RESTRICTED_STATE - BASE] = "EVENT_REEVALUATE_RESTRICTED_STATE"; sCmdToString[EVENT_REEVALUATE_DATA_CONNECTION_PROPERTIES - BASE] = "EVENT_REEVALUATE_DATA_CONNECTION_PROPERTIES"; } // Convert cmd to string or null if unknown static String cmdToString(int cmd) { Loading Loading @@ -1814,9 +1831,10 @@ public class DataConnection extends StateMachine { loge("Cannot find the data connection for handover."); } } else { mScore = calculateScore(); mNetworkAgent = new DcNetworkAgent(getHandler().getLooper(), mPhone.getContext(), "DcNetworkAgent", mNetworkInfo, getNetworkCapabilities(), mLinkProperties, 50, misc); mScore, misc); } if (mDataServiceManager.getTransportType() == TransportType.WWAN) { mPhone.mCi.registerForNattKeepaliveStatus( Loading Loading @@ -2123,6 +2141,12 @@ public class DataConnection extends StateMachine { retVal = HANDLED; break; } case EVENT_REEVALUATE_DATA_CONNECTION_PROPERTIES: { // Update other properties like link properties if needed in future. updateScore(); retVal = HANDLED; break; } default: if (VDBG) { log("DcActiveState not handled msg.what=" + getWhatToString(msg.what)); Loading Loading @@ -2536,6 +2560,15 @@ public class DataConnection extends StateMachine { if (DBG) log("reevaluate restricted state"); } /** * Re-evaluate the data connection properties. For example, it will recalculate data connection * score and update through network agent it if changed. */ void reevaluateDataConnectionProperties() { sendMessage(EVENT_REEVALUATE_DATA_CONNECTION_PROPERTIES); if (DBG) log("reevaluate data connection properties"); } /** * @return The parameters used for initiating a data connection. */ Loading Loading @@ -2743,6 +2776,38 @@ public class DataConnection extends StateMachine { }, null); } /** * Re-calculate score and update through network agent if it changes. */ private void updateScore() { int oldScore = mScore; mScore = calculateScore(); if (oldScore != mScore) { log("Updating score from " + oldScore + " to " + mScore); mNetworkAgent.sendNetworkScore(mScore); } } private int calculateScore() { int score = OTHER_CONNECTION_SCORE; // If it's serving a network request that asks NET_CAPABILITY_INTERNET and doesn't have // specify a subId, this dataConnection is considered to be default Internet data // connection. In this case we assign a slightly higher score of 50. The intention is // it will not be replaced by other data connections accidentally in DSDS usecase. for (ApnContext apnContext : mApnContexts.keySet()) { for (NetworkRequest networkRequest : apnContext.getNetworkRequests()) { if (networkRequest.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) && networkRequest.networkCapabilities.getNetworkSpecifier() == null) { score = DEFAULT_INTERNET_CONNECTION_SCORE; break; } } } return score; } /** * Dump the current state. * Loading Loading @@ -2781,6 +2846,7 @@ public class DataConnection extends StateMachine { pw.println("mUnmeteredUseOnly=" + mUnmeteredUseOnly); pw.println("mInstanceNumber=" + mInstanceNumber); pw.println("mAc=" + mAc); pw.println("mScore=" + mScore); pw.println("Network capabilities changed history:"); pw.increaseIndent(); mNetCapsLocalLog.dump(fd, pw, args); Loading src/java/com/android/internal/telephony/dataconnection/DcTracker.java +77 −17 Original line number Diff line number Diff line Loading @@ -111,6 +111,7 @@ import java.util.ArrayList; import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.PriorityQueue; import java.util.Set; Loading Loading @@ -180,6 +181,12 @@ public class DcTracker extends Handler { */ public static final int RELEASE_TYPE_HANDOVER = 3; /** The extras for request network completion message */ static final String DATA_COMPLETE_MSG_EXTRA_NETWORK_REQUEST = "extra_network_request"; static final String DATA_COMPLETE_MSG_EXTRA_TRANSPORT_TYPE = "extra_transport_type"; static final String DATA_COMPLETE_MSG_EXTRA_REQUEST_TYPE = "extra_request_type"; static final String DATA_COMPLETE_MSG_EXTRA_SUCCESS = "extra_success"; private final String mLogTag; public AtomicBoolean isCleanupRequired = new AtomicBoolean(false); Loading Loading @@ -628,6 +635,13 @@ public class DcTracker extends Handler { private DataStallRecoveryHandler mDsRecoveryHandler; /** * Request network completion message map. Key is the APN type, value is the list of completion * messages to be sent. Using a list because there might be multiple network requests for * the same APN type. */ private final Map<Integer, List<Message>> mRequestNetworkCompletionMsgs = new HashMap<>(); //***** Constructor public DcTracker(Phone phone, int transportType) { super(); Loading Loading @@ -853,13 +867,13 @@ public class DcTracker extends Handler { } public void requestNetwork(NetworkRequest networkRequest, @RequestNetworkType int type, LocalLog log) { Message onCompleteMsg, LocalLog log) { final int apnType = ApnContext.getApnTypeFromNetworkRequest(networkRequest); final ApnContext apnContext = mApnContextsByType.get(apnType); log.log("DcTracker.requestNetwork for " + networkRequest + " found " + apnContext + ", type=" + requestTypeToString(type)); if (apnContext != null) { apnContext.requestNetwork(networkRequest, type, log); apnContext.requestNetwork(networkRequest, type, onCompleteMsg, log); } } Loading Loading @@ -2269,20 +2283,44 @@ public class DcTracker extends Handler { return null; } public void enableApn(@ApnSetting.ApnType int apnType, @RequestNetworkType int requestType) { sendMessage(obtainMessage(DctConstants.EVENT_ENABLE_APN, apnType, requestType)); private void addRequestNetworkCompleteMsg(Message onCompleteMsg, @ApnSetting.ApnType int apnType) { if (onCompleteMsg != null) { List<Message> messageList = mRequestNetworkCompletionMsgs.get(apnType); if (messageList == null) messageList = new ArrayList<>(); messageList.add(onCompleteMsg); mRequestNetworkCompletionMsgs.put(apnType, messageList); } } private void sendRequestNetworkCompleteMsg(Message message, boolean success, int transport, @RequestNetworkType int requestType) { if (message == null) return; Bundle b = message.getData(); b.putBoolean(DATA_COMPLETE_MSG_EXTRA_SUCCESS, success); b.putInt(DATA_COMPLETE_MSG_EXTRA_REQUEST_TYPE, requestType); b.putInt(DATA_COMPLETE_MSG_EXTRA_TRANSPORT_TYPE, transport); message.sendToTarget(); } private void onEnableApn(@ApnSetting.ApnType int apnType, @RequestNetworkType int requestType) { public void enableApn(@ApnSetting.ApnType int apnType, @RequestNetworkType int requestType, Message onCompleteMsg) { sendMessage(obtainMessage(DctConstants.EVENT_ENABLE_APN, apnType, requestType, onCompleteMsg)); } private void onEnableApn(@ApnSetting.ApnType int apnType, @RequestNetworkType int requestType, Message onCompleteMsg) { ApnContext apnContext = mApnContextsByType.get(apnType); if (apnContext == null) { loge("onEnableApn(" + apnType + "): NO ApnContext"); sendRequestNetworkCompleteMsg(onCompleteMsg, false, mTransportType, requestType); return; } boolean trySetup = false; String str = "onEnableApn: apnType=" + ApnSetting.getApnTypeString(apnType) + ", request type=" + requestType; + ", request type=" + requestTypeToString(requestType); if (DBG) log(str); apnContext.requestLog(str); Loading @@ -2292,6 +2330,7 @@ public class DcTracker extends Handler { str = "onEnableApn: dependency is not met."; if (DBG) log(str); apnContext.requestLog(str); sendRequestNetworkCompleteMsg(onCompleteMsg, false, mTransportType, requestType); return; } Loading @@ -2299,11 +2338,22 @@ public class DcTracker extends Handler { DctConstants.State state = apnContext.getState(); switch(state) { case CONNECTING: if (DBG) log("onEnableApn: 'CONNECTING' so return"); apnContext.requestLog("onEnableApn state=CONNECTING, so return"); addRequestNetworkCompleteMsg(onCompleteMsg, apnType); return; case CONNECTED: if (DBG) log("onEnableApn: 'CONNECTED' so return"); apnContext.requestLog("onEnableApn state=CONNECTED, so return"); sendRequestNetworkCompleteMsg(onCompleteMsg, true, mTransportType, requestType); return; case DISCONNECTING: // We're "READY" and active so just return if (DBG) log("onEnableApn: 'ready' so return"); apnContext.requestLog("onEnableApn state=" + state + ", so return"); if (DBG) log("onEnableApn: 'DISCONNECTING' so return"); apnContext.requestLog("onEnableApn state=DISCONNECTING, so return"); sendRequestNetworkCompleteMsg(onCompleteMsg, false, mTransportType, requestType); return; case IDLE: // fall through: this is unexpected but if it happens cleanup and try setup Loading @@ -2311,7 +2361,6 @@ public class DcTracker extends Handler { case RETRYING: // We're "READY" but not active so disconnect (cleanup = true) and // connect (trySetup = true) to be sure we retry the connection. trySetup = true; apnContext.setReason(Phone.REASON_DATA_ENABLED); break; } Loading @@ -2324,12 +2373,14 @@ public class DcTracker extends Handler { if (apnContext.getState() == DctConstants.State.FAILED) { apnContext.setState(DctConstants.State.IDLE); } trySetup = true; } apnContext.setEnabled(true); if (trySetup) { apnContext.resetErrorCodeRetries(); trySetupData(apnContext, requestType); if (trySetupData(apnContext, requestType)) { addRequestNetworkCompleteMsg(onCompleteMsg, apnType); } else { sendRequestNetworkCompleteMsg(onCompleteMsg, false, mTransportType, requestType); } } Loading @@ -2347,7 +2398,7 @@ public class DcTracker extends Handler { boolean cleanup = false; String str = "onDisableApn: apnType=" + ApnSetting.getApnTypeString(apnType) + ", release type=" + releaseType; + ", release type=" + releaseTypeToString(releaseType); if (DBG) log(str); apnContext.requestLog(str); Loading Loading @@ -2672,6 +2723,15 @@ public class DcTracker extends Handler { */ private void onDataSetupComplete(ApnContext apnContext, boolean success, int cause, @RequestNetworkType int requestType) { int apnType = ApnSetting.getApnTypesBitmaskFromString(apnContext.getApnType()); List<Message> messageList = mRequestNetworkCompletionMsgs.get(apnType); if (messageList != null) { for (Message msg : messageList) { sendRequestNetworkCompleteMsg(msg, success, mTransportType, requestType); } messageList.clear(); } if (success) { DataConnection dataConnection = apnContext.getDataConnection(); Loading Loading @@ -3512,7 +3572,7 @@ public class DcTracker extends Handler { break; case DctConstants.EVENT_ENABLE_APN: onEnableApn(msg.arg1, msg.arg2); onEnableApn(msg.arg1, msg.arg2, (Message) msg.obj); break; case DctConstants.EVENT_DISABLE_APN: Loading Loading
src/java/com/android/internal/telephony/TransportManager.java +121 −15 Original line number Diff line number Diff line Loading @@ -21,10 +21,12 @@ import android.annotation.NonNull; 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.telephony.Phone; Loading @@ -51,6 +53,7 @@ import java.util.stream.Collectors; 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 { Loading Loading @@ -108,6 +111,26 @@ public class TransportManager extends Handler { */ 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; 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; mAccessNetworksManager = new AccessNetworksManager(phone); Loading @@ -116,6 +139,8 @@ public class TransportManager extends Handler { EVENT_QUALIFIED_NETWORKS_CHANGED); 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 Loading @@ -140,10 +165,81 @@ public class TransportManager extends Handler { } } 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)) { mCurrentAvailableNetworks.put(networks.apnType, networks.qualifiedNetworks); 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]); 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)); 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); } } } } Loading Loading @@ -175,7 +271,7 @@ public class TransportManager extends Handler { } /** * Get the transport based on the APN type * Get the transport based on the APN type. * * @param apnType APN type * @return The transport type Loading @@ -186,22 +282,30 @@ public class TransportManager extends Handler { return TransportType.WWAN; } // If we can't find the available networks, always route to cellular. if (!mCurrentAvailableNetworks.containsKey(apnType)) { 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); } int[] availableNetworks = mCurrentAvailableNetworks.get(apnType); // If the available networks list is empty, route to cellular. if (ArrayUtils.isEmpty(availableNetworks)) { return TransportType.WWAN; /** * 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); } } // TODO: For now we choose the first network because it's the most preferred one. In the // the future we should validate it to make sure this network does not violate carrier // preference and user preference. return ACCESS_NETWORK_TRANSPORT_TYPE_MAP.get(availableNetworks[0]); /** * Unregister for data handover needed event * * @param h The handler */ public void unregisterForHandoverNeededEvent(Handler h) { mHandoverNeededEventRegistrants.remove(h); } /** Loading @@ -218,6 +322,8 @@ public class TransportManager extends Handler { 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()); mAccessNetworksManager.dump(fd, pw, args); Loading
src/java/com/android/internal/telephony/dataconnection/AccessNetworksManager.java +1 −0 Original line number Diff line number Diff line Loading @@ -106,6 +106,7 @@ public class AccessNetworksManager { */ public static class QualifiedNetworks { public final @ApnType int apnType; // The qualified netowrks in preferred order. Each network is a AccessNetworkType. public final int[] qualifiedNetworks; public QualifiedNetworks(@ApnType int apnType, int[] qualifiedNetworks) { this.apnType = apnType; Loading
src/java/com/android/internal/telephony/dataconnection/ApnContext.java +23 −9 Original line number Diff line number Diff line Loading @@ -21,6 +21,7 @@ import android.net.ConnectivityManager; import android.net.NetworkCapabilities; import android.net.NetworkConfig; import android.net.NetworkRequest; import android.os.Message; import android.telephony.Rlog; import android.telephony.data.ApnSetting; import android.telephony.data.ApnSetting.ApnType; Loading @@ -39,6 +40,7 @@ import com.android.internal.util.IndentingPrintWriter; import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.ArrayList; import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; Loading Loading @@ -146,7 +148,6 @@ public class ApnContext { return mDataConnection; } /** * Set the associated data connection. * @param dc data connection Loading Loading @@ -413,15 +414,17 @@ public class ApnContext { } } public void requestNetwork(NetworkRequest networkRequest, @RequestNetworkType int type, LocalLog log) { public void requestNetwork(NetworkRequest networkRequest, @RequestNetworkType int type, Message onCompleteMsg, LocalLog log) { synchronized (mRefCountLock) { if (mLocalLogs.contains(log) || mNetworkRequests.contains(networkRequest)) { log.log("ApnContext.requestNetwork has duplicate add - " + mNetworkRequests.size()); } else { mLocalLogs.add(log); mNetworkRequests.add(networkRequest); mDcTracker.enableApn(ApnSetting.getApnTypesBitmaskFromString(mApnType), type); mDcTracker.enableApn(ApnSetting.getApnTypesBitmaskFromString(mApnType), type, onCompleteMsg); if (mDataConnection != null) { // New network request added. Should re-evaluate properties of // the data connection. For example, the score may change. mDataConnection.reevaluateDataConnectionProperties(); } } } Loading @@ -439,6 +442,11 @@ public class ApnContext { + networkRequest + ")"); } else { mNetworkRequests.remove(networkRequest); if (mDataConnection != null) { // New network request added. Should re-evaluate properties of // the data connection. For example, the score may change. mDataConnection.reevaluateDataConnectionProperties(); } log.log("ApnContext.releaseNetwork left with " + mNetworkRequests.size() + " requests."); if (mNetworkRequests.size() == 0 Loading Loading @@ -630,6 +638,12 @@ public class ApnContext { return apnType; } public List<NetworkRequest> getNetworkRequests() { synchronized (mRefCountLock) { return new ArrayList<NetworkRequest>(mNetworkRequests); } } @Override public synchronized String toString() { // We don't print mDataConnection because its recursive. Loading
src/java/com/android/internal/telephony/dataconnection/DataConnection.java +68 −2 Original line number Diff line number Diff line Loading @@ -30,6 +30,7 @@ import android.net.NetworkAgent; import android.net.NetworkCapabilities; import android.net.NetworkInfo; import android.net.NetworkMisc; import android.net.NetworkRequest; import android.net.NetworkUtils; import android.net.ProxyInfo; import android.net.RouteInfo; Loading Loading @@ -108,6 +109,18 @@ public class DataConnection extends StateMachine { private static final String NETWORK_TYPE = "MOBILE"; // The data connection providing default Internet connection will have a higher score of 50. // Other connections will have a slightly lower score of 45. The intention is other connections // will not cause ConnectivityService to tear down default internet connection. For example, // to validate Internet connection on non-default data SIM, we'll set up a temporary Internet // connection on that data SIM. In this case, score of 45 is assigned so ConnectivityService // will not replace the default Internet connection with it. private static final int DEFAULT_INTERNET_CONNECTION_SCORE = 50; private static final int OTHER_CONNECTION_SCORE = 45; // The score we report to connectivity service private int mScore; // The data connection controller private DcController mDcController; Loading Loading @@ -240,8 +253,10 @@ public class DataConnection extends StateMachine { static final int EVENT_LINK_CAPACITY_CHANGED = BASE + 23; static final int EVENT_RESET = BASE + 24; static final int EVENT_REEVALUATE_RESTRICTED_STATE = BASE + 25; static final int EVENT_REEVALUATE_DATA_CONNECTION_PROPERTIES = BASE + 26; private static final int CMD_TO_STRING_COUNT = EVENT_REEVALUATE_RESTRICTED_STATE - BASE + 1; private static final int CMD_TO_STRING_COUNT = EVENT_REEVALUATE_DATA_CONNECTION_PROPERTIES - BASE + 1; private static String[] sCmdToString = new String[CMD_TO_STRING_COUNT]; static { Loading Loading @@ -275,6 +290,8 @@ public class DataConnection extends StateMachine { sCmdToString[EVENT_RESET - BASE] = "EVENT_RESET"; sCmdToString[EVENT_REEVALUATE_RESTRICTED_STATE - BASE] = "EVENT_REEVALUATE_RESTRICTED_STATE"; sCmdToString[EVENT_REEVALUATE_DATA_CONNECTION_PROPERTIES - BASE] = "EVENT_REEVALUATE_DATA_CONNECTION_PROPERTIES"; } // Convert cmd to string or null if unknown static String cmdToString(int cmd) { Loading Loading @@ -1814,9 +1831,10 @@ public class DataConnection extends StateMachine { loge("Cannot find the data connection for handover."); } } else { mScore = calculateScore(); mNetworkAgent = new DcNetworkAgent(getHandler().getLooper(), mPhone.getContext(), "DcNetworkAgent", mNetworkInfo, getNetworkCapabilities(), mLinkProperties, 50, misc); mScore, misc); } if (mDataServiceManager.getTransportType() == TransportType.WWAN) { mPhone.mCi.registerForNattKeepaliveStatus( Loading Loading @@ -2123,6 +2141,12 @@ public class DataConnection extends StateMachine { retVal = HANDLED; break; } case EVENT_REEVALUATE_DATA_CONNECTION_PROPERTIES: { // Update other properties like link properties if needed in future. updateScore(); retVal = HANDLED; break; } default: if (VDBG) { log("DcActiveState not handled msg.what=" + getWhatToString(msg.what)); Loading Loading @@ -2536,6 +2560,15 @@ public class DataConnection extends StateMachine { if (DBG) log("reevaluate restricted state"); } /** * Re-evaluate the data connection properties. For example, it will recalculate data connection * score and update through network agent it if changed. */ void reevaluateDataConnectionProperties() { sendMessage(EVENT_REEVALUATE_DATA_CONNECTION_PROPERTIES); if (DBG) log("reevaluate data connection properties"); } /** * @return The parameters used for initiating a data connection. */ Loading Loading @@ -2743,6 +2776,38 @@ public class DataConnection extends StateMachine { }, null); } /** * Re-calculate score and update through network agent if it changes. */ private void updateScore() { int oldScore = mScore; mScore = calculateScore(); if (oldScore != mScore) { log("Updating score from " + oldScore + " to " + mScore); mNetworkAgent.sendNetworkScore(mScore); } } private int calculateScore() { int score = OTHER_CONNECTION_SCORE; // If it's serving a network request that asks NET_CAPABILITY_INTERNET and doesn't have // specify a subId, this dataConnection is considered to be default Internet data // connection. In this case we assign a slightly higher score of 50. The intention is // it will not be replaced by other data connections accidentally in DSDS usecase. for (ApnContext apnContext : mApnContexts.keySet()) { for (NetworkRequest networkRequest : apnContext.getNetworkRequests()) { if (networkRequest.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) && networkRequest.networkCapabilities.getNetworkSpecifier() == null) { score = DEFAULT_INTERNET_CONNECTION_SCORE; break; } } } return score; } /** * Dump the current state. * Loading Loading @@ -2781,6 +2846,7 @@ public class DataConnection extends StateMachine { pw.println("mUnmeteredUseOnly=" + mUnmeteredUseOnly); pw.println("mInstanceNumber=" + mInstanceNumber); pw.println("mAc=" + mAc); pw.println("mScore=" + mScore); pw.println("Network capabilities changed history:"); pw.increaseIndent(); mNetCapsLocalLog.dump(fd, pw, args); Loading
src/java/com/android/internal/telephony/dataconnection/DcTracker.java +77 −17 Original line number Diff line number Diff line Loading @@ -111,6 +111,7 @@ import java.util.ArrayList; import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.PriorityQueue; import java.util.Set; Loading Loading @@ -180,6 +181,12 @@ public class DcTracker extends Handler { */ public static final int RELEASE_TYPE_HANDOVER = 3; /** The extras for request network completion message */ static final String DATA_COMPLETE_MSG_EXTRA_NETWORK_REQUEST = "extra_network_request"; static final String DATA_COMPLETE_MSG_EXTRA_TRANSPORT_TYPE = "extra_transport_type"; static final String DATA_COMPLETE_MSG_EXTRA_REQUEST_TYPE = "extra_request_type"; static final String DATA_COMPLETE_MSG_EXTRA_SUCCESS = "extra_success"; private final String mLogTag; public AtomicBoolean isCleanupRequired = new AtomicBoolean(false); Loading Loading @@ -628,6 +635,13 @@ public class DcTracker extends Handler { private DataStallRecoveryHandler mDsRecoveryHandler; /** * Request network completion message map. Key is the APN type, value is the list of completion * messages to be sent. Using a list because there might be multiple network requests for * the same APN type. */ private final Map<Integer, List<Message>> mRequestNetworkCompletionMsgs = new HashMap<>(); //***** Constructor public DcTracker(Phone phone, int transportType) { super(); Loading Loading @@ -853,13 +867,13 @@ public class DcTracker extends Handler { } public void requestNetwork(NetworkRequest networkRequest, @RequestNetworkType int type, LocalLog log) { Message onCompleteMsg, LocalLog log) { final int apnType = ApnContext.getApnTypeFromNetworkRequest(networkRequest); final ApnContext apnContext = mApnContextsByType.get(apnType); log.log("DcTracker.requestNetwork for " + networkRequest + " found " + apnContext + ", type=" + requestTypeToString(type)); if (apnContext != null) { apnContext.requestNetwork(networkRequest, type, log); apnContext.requestNetwork(networkRequest, type, onCompleteMsg, log); } } Loading Loading @@ -2269,20 +2283,44 @@ public class DcTracker extends Handler { return null; } public void enableApn(@ApnSetting.ApnType int apnType, @RequestNetworkType int requestType) { sendMessage(obtainMessage(DctConstants.EVENT_ENABLE_APN, apnType, requestType)); private void addRequestNetworkCompleteMsg(Message onCompleteMsg, @ApnSetting.ApnType int apnType) { if (onCompleteMsg != null) { List<Message> messageList = mRequestNetworkCompletionMsgs.get(apnType); if (messageList == null) messageList = new ArrayList<>(); messageList.add(onCompleteMsg); mRequestNetworkCompletionMsgs.put(apnType, messageList); } } private void sendRequestNetworkCompleteMsg(Message message, boolean success, int transport, @RequestNetworkType int requestType) { if (message == null) return; Bundle b = message.getData(); b.putBoolean(DATA_COMPLETE_MSG_EXTRA_SUCCESS, success); b.putInt(DATA_COMPLETE_MSG_EXTRA_REQUEST_TYPE, requestType); b.putInt(DATA_COMPLETE_MSG_EXTRA_TRANSPORT_TYPE, transport); message.sendToTarget(); } private void onEnableApn(@ApnSetting.ApnType int apnType, @RequestNetworkType int requestType) { public void enableApn(@ApnSetting.ApnType int apnType, @RequestNetworkType int requestType, Message onCompleteMsg) { sendMessage(obtainMessage(DctConstants.EVENT_ENABLE_APN, apnType, requestType, onCompleteMsg)); } private void onEnableApn(@ApnSetting.ApnType int apnType, @RequestNetworkType int requestType, Message onCompleteMsg) { ApnContext apnContext = mApnContextsByType.get(apnType); if (apnContext == null) { loge("onEnableApn(" + apnType + "): NO ApnContext"); sendRequestNetworkCompleteMsg(onCompleteMsg, false, mTransportType, requestType); return; } boolean trySetup = false; String str = "onEnableApn: apnType=" + ApnSetting.getApnTypeString(apnType) + ", request type=" + requestType; + ", request type=" + requestTypeToString(requestType); if (DBG) log(str); apnContext.requestLog(str); Loading @@ -2292,6 +2330,7 @@ public class DcTracker extends Handler { str = "onEnableApn: dependency is not met."; if (DBG) log(str); apnContext.requestLog(str); sendRequestNetworkCompleteMsg(onCompleteMsg, false, mTransportType, requestType); return; } Loading @@ -2299,11 +2338,22 @@ public class DcTracker extends Handler { DctConstants.State state = apnContext.getState(); switch(state) { case CONNECTING: if (DBG) log("onEnableApn: 'CONNECTING' so return"); apnContext.requestLog("onEnableApn state=CONNECTING, so return"); addRequestNetworkCompleteMsg(onCompleteMsg, apnType); return; case CONNECTED: if (DBG) log("onEnableApn: 'CONNECTED' so return"); apnContext.requestLog("onEnableApn state=CONNECTED, so return"); sendRequestNetworkCompleteMsg(onCompleteMsg, true, mTransportType, requestType); return; case DISCONNECTING: // We're "READY" and active so just return if (DBG) log("onEnableApn: 'ready' so return"); apnContext.requestLog("onEnableApn state=" + state + ", so return"); if (DBG) log("onEnableApn: 'DISCONNECTING' so return"); apnContext.requestLog("onEnableApn state=DISCONNECTING, so return"); sendRequestNetworkCompleteMsg(onCompleteMsg, false, mTransportType, requestType); return; case IDLE: // fall through: this is unexpected but if it happens cleanup and try setup Loading @@ -2311,7 +2361,6 @@ public class DcTracker extends Handler { case RETRYING: // We're "READY" but not active so disconnect (cleanup = true) and // connect (trySetup = true) to be sure we retry the connection. trySetup = true; apnContext.setReason(Phone.REASON_DATA_ENABLED); break; } Loading @@ -2324,12 +2373,14 @@ public class DcTracker extends Handler { if (apnContext.getState() == DctConstants.State.FAILED) { apnContext.setState(DctConstants.State.IDLE); } trySetup = true; } apnContext.setEnabled(true); if (trySetup) { apnContext.resetErrorCodeRetries(); trySetupData(apnContext, requestType); if (trySetupData(apnContext, requestType)) { addRequestNetworkCompleteMsg(onCompleteMsg, apnType); } else { sendRequestNetworkCompleteMsg(onCompleteMsg, false, mTransportType, requestType); } } Loading @@ -2347,7 +2398,7 @@ public class DcTracker extends Handler { boolean cleanup = false; String str = "onDisableApn: apnType=" + ApnSetting.getApnTypeString(apnType) + ", release type=" + releaseType; + ", release type=" + releaseTypeToString(releaseType); if (DBG) log(str); apnContext.requestLog(str); Loading Loading @@ -2672,6 +2723,15 @@ public class DcTracker extends Handler { */ private void onDataSetupComplete(ApnContext apnContext, boolean success, int cause, @RequestNetworkType int requestType) { int apnType = ApnSetting.getApnTypesBitmaskFromString(apnContext.getApnType()); List<Message> messageList = mRequestNetworkCompletionMsgs.get(apnType); if (messageList != null) { for (Message msg : messageList) { sendRequestNetworkCompleteMsg(msg, success, mTransportType, requestType); } messageList.clear(); } if (success) { DataConnection dataConnection = apnContext.getDataConnection(); Loading Loading @@ -3512,7 +3572,7 @@ public class DcTracker extends Handler { break; case DctConstants.EVENT_ENABLE_APN: onEnableApn(msg.arg1, msg.arg2); onEnableApn(msg.arg1, msg.arg2, (Message) msg.obj); break; case DctConstants.EVENT_DISABLE_APN: Loading