| /* |
| * Copyright (C) 2016 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 static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID; |
| |
| import android.net.NetworkCapabilities; |
| import android.net.NetworkFactory; |
| import android.net.NetworkRequest; |
| import android.net.TelephonyNetworkSpecifier; |
| import android.os.AsyncResult; |
| import android.os.Bundle; |
| import android.os.Handler; |
| import android.os.Looper; |
| import android.os.Message; |
| import android.telephony.AccessNetworkConstants; |
| import android.telephony.Annotation.ApnType; |
| import android.telephony.SubscriptionManager; |
| import android.telephony.data.ApnSetting; |
| import android.util.LocalLog; |
| |
| import com.android.internal.annotations.VisibleForTesting; |
| import com.android.internal.telephony.Phone; |
| import com.android.internal.telephony.PhoneSwitcher; |
| import com.android.internal.telephony.SubscriptionController; |
| import com.android.internal.telephony.dataconnection.DcTracker.ReleaseNetworkType; |
| import com.android.internal.telephony.dataconnection.DcTracker.RequestNetworkType; |
| import com.android.internal.telephony.dataconnection.TransportManager.HandoverParams; |
| import com.android.internal.util.IndentingPrintWriter; |
| import com.android.telephony.Rlog; |
| |
| import java.io.FileDescriptor; |
| import java.io.PrintWriter; |
| import java.util.HashMap; |
| import java.util.Map; |
| |
| public class TelephonyNetworkFactory extends NetworkFactory { |
| public final String LOG_TAG; |
| protected static final boolean DBG = true; |
| |
| private static final int REQUEST_LOG_SIZE = 40; |
| |
| private static final int ACTION_NO_OP = 0; |
| private static final int ACTION_REQUEST = 1; |
| private static final int ACTION_RELEASE = 2; |
| |
| private static final int TELEPHONY_NETWORK_SCORE = 50; |
| |
| @VisibleForTesting |
| public static final int EVENT_ACTIVE_PHONE_SWITCH = 1; |
| @VisibleForTesting |
| public static final int EVENT_SUBSCRIPTION_CHANGED = 2; |
| private static final int EVENT_NETWORK_REQUEST = 3; |
| private static final int EVENT_NETWORK_RELEASE = 4; |
| private static final int EVENT_DATA_HANDOVER_NEEDED = 5; |
| private static final int EVENT_DATA_HANDOVER_COMPLETED = 6; |
| |
| private final PhoneSwitcher mPhoneSwitcher; |
| private final SubscriptionController mSubscriptionController; |
| private final LocalLog mLocalLog = new LocalLog(REQUEST_LOG_SIZE); |
| |
| // Key: network request. Value: the transport of DcTracker it applies to, |
| // AccessNetworkConstants.TRANSPORT_TYPE_INVALID if not applied. |
| private final Map<NetworkRequest, Integer> mNetworkRequests = new HashMap<>(); |
| |
| private final Map<Message, HandoverParams> mPendingHandovers = new HashMap<>(); |
| |
| private final Phone mPhone; |
| |
| private final TransportManager mTransportManager; |
| |
| private int mSubscriptionId; |
| |
| @VisibleForTesting |
| public final Handler mInternalHandler; |
| |
| |
| public TelephonyNetworkFactory(Looper looper, Phone phone) { |
| super(looper, phone.getContext(), "TelephonyNetworkFactory[" + phone.getPhoneId() |
| + "]", null); |
| mPhone = phone; |
| mTransportManager = mPhone.getTransportManager(); |
| mInternalHandler = new InternalHandler(looper); |
| |
| mSubscriptionController = SubscriptionController.getInstance(); |
| |
| setCapabilityFilter(makeNetworkFilter(mSubscriptionController, mPhone.getPhoneId())); |
| setScoreFilter(TELEPHONY_NETWORK_SCORE); |
| |
| mPhoneSwitcher = PhoneSwitcher.getInstance(); |
| LOG_TAG = "TelephonyNetworkFactory[" + mPhone.getPhoneId() + "]"; |
| |
| mPhoneSwitcher.registerForActivePhoneSwitch(mInternalHandler, EVENT_ACTIVE_PHONE_SWITCH, |
| null); |
| mTransportManager.registerForHandoverNeededEvent(mInternalHandler, |
| EVENT_DATA_HANDOVER_NEEDED); |
| |
| mSubscriptionId = INVALID_SUBSCRIPTION_ID; |
| SubscriptionManager.from(mPhone.getContext()).addOnSubscriptionsChangedListener( |
| mSubscriptionsChangedListener); |
| |
| register(); |
| } |
| |
| private final SubscriptionManager.OnSubscriptionsChangedListener mSubscriptionsChangedListener = |
| new SubscriptionManager.OnSubscriptionsChangedListener() { |
| @Override |
| public void onSubscriptionsChanged() { |
| mInternalHandler.sendEmptyMessage(EVENT_SUBSCRIPTION_CHANGED); |
| } |
| }; |
| |
| private NetworkCapabilities makeNetworkFilter(SubscriptionController subscriptionController, |
| int phoneId) { |
| final int subscriptionId = subscriptionController.getSubIdUsingPhoneId(phoneId); |
| return makeNetworkFilter(subscriptionId); |
| } |
| |
| private NetworkCapabilities makeNetworkFilter(int subscriptionId) { |
| NetworkCapabilities nc = new NetworkCapabilities(); |
| nc.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR); |
| nc.addCapability(NetworkCapabilities.NET_CAPABILITY_MMS); |
| nc.addCapability(NetworkCapabilities.NET_CAPABILITY_SUPL); |
| nc.addCapability(NetworkCapabilities.NET_CAPABILITY_DUN); |
| nc.addCapability(NetworkCapabilities.NET_CAPABILITY_FOTA); |
| nc.addCapability(NetworkCapabilities.NET_CAPABILITY_IMS); |
| nc.addCapability(NetworkCapabilities.NET_CAPABILITY_CBS); |
| nc.addCapability(NetworkCapabilities.NET_CAPABILITY_IA); |
| nc.addCapability(NetworkCapabilities.NET_CAPABILITY_RCS); |
| nc.addCapability(NetworkCapabilities.NET_CAPABILITY_XCAP); |
| nc.addCapability(NetworkCapabilities.NET_CAPABILITY_EIMS); |
| nc.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED); |
| nc.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET); |
| nc.setNetworkSpecifier(new TelephonyNetworkSpecifier.Builder() |
| .setSubscriptionId(subscriptionId).build()); |
| return nc; |
| } |
| |
| private class InternalHandler extends Handler { |
| public InternalHandler(Looper looper) { |
| super(looper); |
| } |
| |
| @Override |
| public void handleMessage(Message msg) { |
| switch (msg.what) { |
| case EVENT_ACTIVE_PHONE_SWITCH: { |
| onActivePhoneSwitch(); |
| break; |
| } |
| case EVENT_SUBSCRIPTION_CHANGED: { |
| onSubIdChange(); |
| break; |
| } |
| case EVENT_NETWORK_REQUEST: { |
| onNeedNetworkFor(msg); |
| break; |
| } |
| case EVENT_NETWORK_RELEASE: { |
| onReleaseNetworkFor(msg); |
| break; |
| } |
| case EVENT_DATA_HANDOVER_NEEDED: { |
| AsyncResult ar = (AsyncResult) msg.obj; |
| HandoverParams handoverParams = (HandoverParams) ar.result; |
| onDataHandoverNeeded(handoverParams.apnType, handoverParams.targetTransport, |
| handoverParams); |
| break; |
| } |
| case EVENT_DATA_HANDOVER_COMPLETED: { |
| Bundle bundle = msg.getData(); |
| int requestType = bundle.getInt(DcTracker.DATA_COMPLETE_MSG_EXTRA_REQUEST_TYPE); |
| if (requestType == DcTracker.REQUEST_TYPE_HANDOVER) { |
| NetworkRequest nr = bundle.getParcelable( |
| DcTracker.DATA_COMPLETE_MSG_EXTRA_NETWORK_REQUEST); |
| boolean success = bundle.getBoolean( |
| DcTracker.DATA_COMPLETE_MSG_EXTRA_SUCCESS); |
| int transport = bundle.getInt( |
| DcTracker.DATA_COMPLETE_MSG_EXTRA_TRANSPORT_TYPE); |
| boolean fallback = bundle.getBoolean( |
| DcTracker.DATA_COMPLETE_MSG_EXTRA_HANDOVER_FAILURE_FALLBACK); |
| HandoverParams handoverParams = mPendingHandovers.remove(msg); |
| if (handoverParams != null) { |
| onDataHandoverSetupCompleted(nr, success, transport, fallback, |
| handoverParams); |
| } else { |
| logl("Handover completed but cannot find handover entry!"); |
| } |
| } |
| break; |
| } |
| } |
| } |
| } |
| |
| private int getTransportTypeFromNetworkRequest(NetworkRequest networkRequest) { |
| int apnType = ApnContext.getApnTypeFromNetworkRequest(networkRequest); |
| return mTransportManager.getCurrentTransport(apnType); |
| } |
| |
| private void requestNetworkInternal(NetworkRequest networkRequest, |
| @RequestNetworkType int requestType, |
| int transport, Message onCompleteMsg) { |
| if (mPhone.getDcTracker(transport) != null) { |
| mPhone.getDcTracker(transport).requestNetwork(networkRequest, requestType, |
| onCompleteMsg); |
| } |
| } |
| |
| private void releaseNetworkInternal(NetworkRequest networkRequest, |
| @ReleaseNetworkType int releaseType, |
| int transport) { |
| if (mPhone.getDcTracker(transport) != null) { |
| mPhone.getDcTracker(transport).releaseNetwork(networkRequest, releaseType); |
| } |
| } |
| |
| private static int getAction(boolean wasActive, boolean isActive) { |
| if (!wasActive && isActive) { |
| return ACTION_REQUEST; |
| } else if (wasActive && !isActive) { |
| return ACTION_RELEASE; |
| } else { |
| return ACTION_NO_OP; |
| } |
| } |
| |
| // apply or revoke requests if our active-ness changes |
| private void onActivePhoneSwitch() { |
| for (HashMap.Entry<NetworkRequest, Integer> entry : mNetworkRequests.entrySet()) { |
| NetworkRequest networkRequest = entry.getKey(); |
| boolean applied = entry.getValue() != AccessNetworkConstants.TRANSPORT_TYPE_INVALID; |
| |
| boolean shouldApply = mPhoneSwitcher.shouldApplyNetworkRequest( |
| networkRequest, mPhone.getPhoneId()); |
| |
| int action = getAction(applied, shouldApply); |
| if (action == ACTION_NO_OP) continue; |
| |
| logl("onActivePhoneSwitch: " + ((action == ACTION_REQUEST) |
| ? "Requesting" : "Releasing") + " network request " + networkRequest); |
| int transportType = getTransportTypeFromNetworkRequest(networkRequest); |
| if (action == ACTION_REQUEST) { |
| requestNetworkInternal(networkRequest, DcTracker.REQUEST_TYPE_NORMAL, |
| getTransportTypeFromNetworkRequest(networkRequest), null); |
| } else if (action == ACTION_RELEASE) { |
| releaseNetworkInternal(networkRequest, DcTracker.RELEASE_TYPE_DETACH, |
| getTransportTypeFromNetworkRequest(networkRequest)); |
| } |
| |
| mNetworkRequests.put(networkRequest, |
| shouldApply ? transportType : AccessNetworkConstants.TRANSPORT_TYPE_INVALID); |
| } |
| } |
| |
| // watch for phone->subId changes, reapply new filter and let |
| // that flow through to apply/revoke of requests |
| private void onSubIdChange() { |
| final int newSubscriptionId = mSubscriptionController.getSubIdUsingPhoneId( |
| mPhone.getPhoneId()); |
| if (mSubscriptionId != newSubscriptionId) { |
| if (DBG) log("onSubIdChange " + mSubscriptionId + "->" + newSubscriptionId); |
| mSubscriptionId = newSubscriptionId; |
| setCapabilityFilter(makeNetworkFilter(mSubscriptionId)); |
| } |
| } |
| |
| @Override |
| public void needNetworkFor(NetworkRequest networkRequest, int score) { |
| Message msg = mInternalHandler.obtainMessage(EVENT_NETWORK_REQUEST); |
| msg.obj = networkRequest; |
| msg.sendToTarget(); |
| } |
| |
| private void onNeedNetworkFor(Message msg) { |
| NetworkRequest networkRequest = (NetworkRequest) msg.obj; |
| boolean shouldApply = mPhoneSwitcher.shouldApplyNetworkRequest( |
| networkRequest, mPhone.getPhoneId()); |
| |
| mNetworkRequests.put(networkRequest, shouldApply |
| ? getTransportTypeFromNetworkRequest(networkRequest) |
| : AccessNetworkConstants.TRANSPORT_TYPE_INVALID); |
| |
| logl("onNeedNetworkFor " + networkRequest + " shouldApply " + shouldApply); |
| |
| if (shouldApply) { |
| requestNetworkInternal(networkRequest, DcTracker.REQUEST_TYPE_NORMAL, |
| getTransportTypeFromNetworkRequest(networkRequest), null); |
| } |
| } |
| |
| @Override |
| public void releaseNetworkFor(NetworkRequest networkRequest) { |
| Message msg = mInternalHandler.obtainMessage(EVENT_NETWORK_RELEASE); |
| msg.obj = networkRequest; |
| msg.sendToTarget(); |
| } |
| |
| private void onReleaseNetworkFor(Message msg) { |
| NetworkRequest networkRequest = (NetworkRequest) msg.obj; |
| boolean applied = mNetworkRequests.get(networkRequest) |
| != AccessNetworkConstants.TRANSPORT_TYPE_INVALID; |
| |
| mNetworkRequests.remove(networkRequest); |
| |
| logl("onReleaseNetworkFor " + networkRequest + " applied " + applied); |
| |
| if (applied) { |
| int transport = getTransportTypeFromNetworkRequest(networkRequest); |
| releaseNetworkInternal(networkRequest, DcTracker.RELEASE_TYPE_NORMAL, transport); |
| } |
| } |
| |
| private void onDataHandoverNeeded(@ApnType int apnType, int targetTransport, |
| HandoverParams handoverParams) { |
| log("onDataHandoverNeeded: apnType=" + ApnSetting.getApnTypeString(apnType) |
| + ", target transport=" |
| + AccessNetworkConstants.transportTypeToString(targetTransport)); |
| if (mTransportManager.getCurrentTransport(apnType) == targetTransport) { |
| log("APN type " + ApnSetting.getApnTypeString(apnType) + " is already on " |
| + AccessNetworkConstants.transportTypeToString(targetTransport)); |
| return; |
| } |
| |
| boolean handoverPending = false; |
| for (HashMap.Entry<NetworkRequest, Integer> entry : mNetworkRequests.entrySet()) { |
| NetworkRequest networkRequest = entry.getKey(); |
| int currentTransport = entry.getValue(); |
| boolean applied = currentTransport != AccessNetworkConstants.TRANSPORT_TYPE_INVALID; |
| if (ApnContext.getApnTypeFromNetworkRequest(networkRequest) == apnType |
| && applied |
| && currentTransport != targetTransport) { |
| DcTracker dcTracker = mPhone.getDcTracker(currentTransport); |
| if (dcTracker != null) { |
| DataConnection dc = dcTracker.getDataConnectionByApnType( |
| ApnSetting.getApnTypeString(apnType)); |
| if (dc != null && (dc.isActive())) { |
| Message onCompleteMsg = mInternalHandler.obtainMessage( |
| EVENT_DATA_HANDOVER_COMPLETED); |
| onCompleteMsg.getData().putParcelable( |
| DcTracker.DATA_COMPLETE_MSG_EXTRA_NETWORK_REQUEST, networkRequest); |
| mPendingHandovers.put(onCompleteMsg, handoverParams); |
| requestNetworkInternal(networkRequest, DcTracker.REQUEST_TYPE_HANDOVER, |
| targetTransport, onCompleteMsg); |
| log("Requested handover " + ApnSetting.getApnTypeString(apnType) + " to " |
| + AccessNetworkConstants.transportTypeToString(targetTransport)); |
| handoverPending = true; |
| } else { |
| // Request is there, but no actual data connection. In this case, just move |
| // the request to the new transport. |
| log("The network request is on transport " + AccessNetworkConstants |
| .transportTypeToString(currentTransport) + ", but no live data " |
| + "connection. Just move the request to transport " |
| + AccessNetworkConstants.transportTypeToString(targetTransport) |
| + ", dc=" + dc); |
| entry.setValue(targetTransport); |
| releaseNetworkInternal(networkRequest, DcTracker.RELEASE_TYPE_NORMAL, |
| currentTransport); |
| requestNetworkInternal(networkRequest, DcTracker.REQUEST_TYPE_NORMAL, |
| targetTransport, null); |
| } |
| } else { |
| log("DcTracker on " + AccessNetworkConstants.transportTypeToString( |
| currentTransport) + " is not available."); |
| } |
| } |
| } |
| |
| if (!handoverPending) { |
| log("No handover request pending. Handover process is now completed"); |
| handoverParams.callback.onCompleted(true, false); |
| } |
| } |
| |
| private void onDataHandoverSetupCompleted(NetworkRequest networkRequest, boolean success, |
| int targetTransport, boolean fallback, |
| HandoverParams handoverParams) { |
| log("onDataHandoverSetupCompleted: " + networkRequest + ", success=" + success |
| + ", targetTransport=" |
| + AccessNetworkConstants.transportTypeToString(targetTransport) |
| + ", fallback=" + fallback); |
| |
| // At this point, handover setup has been completed on the target transport. |
| // If it succeeded, or it failed without falling back to the original transport, |
| // we should release the request from the original transport. |
| if (!fallback) { |
| int originTransport = (targetTransport == AccessNetworkConstants.TRANSPORT_TYPE_WWAN) |
| ? AccessNetworkConstants.TRANSPORT_TYPE_WLAN |
| : AccessNetworkConstants.TRANSPORT_TYPE_WWAN; |
| int releaseType = success |
| ? DcTracker.RELEASE_TYPE_HANDOVER |
| // If handover fails, we need to tear down the existing connection, so the |
| // new data connection can be re-established on the new transport. If we leave |
| // the existing data connection in current transport, then DCT and qualified |
| // network service will be out of sync. Specifying release type to detach |
| // the transport is moved to the other transport, but network request is still |
| // there, connectivity service will not call unwanted to tear down the network. |
| // We need explicitly tear down the data connection here so the new data |
| // connection can be re-established on the other transport. |
| : DcTracker.RELEASE_TYPE_DETACH; |
| releaseNetworkInternal(networkRequest, releaseType, originTransport); |
| mNetworkRequests.put(networkRequest, targetTransport); |
| } |
| |
| handoverParams.callback.onCompleted(success, fallback); |
| } |
| |
| protected void log(String s) { |
| Rlog.d(LOG_TAG, s); |
| } |
| |
| protected void logl(String s) { |
| log(s); |
| mLocalLog.log(s); |
| } |
| |
| public void dump(FileDescriptor fd, PrintWriter writer, String[] args) { |
| final IndentingPrintWriter pw = new IndentingPrintWriter(writer, " "); |
| pw.println("Network Requests:"); |
| pw.increaseIndent(); |
| for (HashMap.Entry<NetworkRequest, Integer> entry : mNetworkRequests.entrySet()) { |
| NetworkRequest nr = entry.getKey(); |
| int transport = entry.getValue(); |
| pw.println(nr + (transport != AccessNetworkConstants.TRANSPORT_TYPE_INVALID |
| ? (" applied on " + transport) : " not applied")); |
| } |
| mLocalLog.dump(fd, pw, args); |
| pw.decreaseIndent(); |
| } |
| } |