blob: eff43408f7fd68ae4422b3af4008cf73862e7c6e [file] [log] [blame]
/*
* 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();
}
}