blob: d455cb43c6c3fa76faa9df1bef2924cce113c17a [file] [log] [blame]
/*
* Copyright (C) 2015 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;
import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
import static android.telephony.PhoneStateListener.LISTEN_PHONE_CAPABILITY_CHANGE;
import static android.telephony.PhoneStateListener.LISTEN_PRECISE_CALL_STATE;
import static android.telephony.SubscriptionManager.DEFAULT_SUBSCRIPTION_ID;
import static android.telephony.SubscriptionManager.INVALID_PHONE_INDEX;
import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
import static android.telephony.TelephonyManager.SET_OPPORTUNISTIC_SUB_INVALID_PARAMETER;
import static android.telephony.TelephonyManager.SET_OPPORTUNISTIC_SUB_SUCCESS;
import static android.telephony.TelephonyManager.SET_OPPORTUNISTIC_SUB_VALIDATION_FAILED;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.net.ConnectivityManager;
import android.net.ConnectivityManager.NetworkCallback;
import android.net.MatchAllNetworkSpecifier;
import android.net.Network;
import android.net.NetworkCapabilities;
import android.net.NetworkFactory;
import android.net.NetworkRequest;
import android.net.NetworkSpecifier;
import android.net.StringNetworkSpecifier;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.os.Registrant;
import android.os.RegistrantList;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.telephony.PhoneCapability;
import android.telephony.PhoneStateListener;
import android.telephony.PreciseCallState;
import android.telephony.Rlog;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
import android.util.LocalLog;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.telephony.dataconnection.DcRequest;
import com.android.internal.telephony.metrics.TelephonyMetrics;
import com.android.internal.telephony.nano.TelephonyProto.TelephonyEvent;
import com.android.internal.telephony.nano.TelephonyProto.TelephonyEvent.DataSwitch;
import com.android.internal.telephony.nano.TelephonyProto.TelephonyEvent.OnDemandDataSwitch;
import com.android.internal.util.IndentingPrintWriter;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.List;
/**
* Utility singleton to monitor subscription changes and incoming NetworkRequests
* and determine which phone/phones are active.
*
* Manages the ALLOW_DATA calls to modems and notifies phones about changes to
* the active phones. Note we don't wait for data attach (which may not happen anyway).
*/
public class PhoneSwitcher extends Handler {
private static final String LOG_TAG = "PhoneSwitcher";
private static final boolean VDBG = false;
private static final int DEFAULT_NETWORK_CHANGE_TIMEOUT_MS = 5000;
private final List<DcRequest> mPrioritizedDcRequests = new ArrayList<DcRequest>();
private final RegistrantList mActivePhoneRegistrants;
private final SubscriptionController mSubscriptionController;
private final int[] mPhoneSubscriptions;
private final CommandsInterface[] mCommandsInterfaces;
private final Context mContext;
private final PhoneState[] mPhoneStates;
private final int mNumPhones;
private final Phone[] mPhones;
private final LocalLog mLocalLog;
@VisibleForTesting
public final PhoneStateListener mPhoneStateListener;
private final CellularNetworkValidator mValidator;
private final CellularNetworkValidator.ValidationCallback mValidationCallback =
(validated, subId) -> Message.obtain(PhoneSwitcher.this,
EVENT_NETWORK_VALIDATION_DONE, subId, validated ? 1 : 0).sendToTarget();
private int mMaxActivePhones;
private static PhoneSwitcher sPhoneSwitcher = null;
// Default subscription ID from user setting.
private int mDefaultDataSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
// If mPreferredDataSubId is an active subscription, it overrides
// mDefaultDataSubId and decides:
// 1. In modem layer, which subscription is preferred to have data traffic on.
// 2. In TelephonyNetworkFactory, which subscription will apply default network requests, which
// are requests without specifying a subId.
private int mPreferredDataSubId = SubscriptionManager.DEFAULT_SUBSCRIPTION_ID;
@VisibleForTesting
// Corresponding phoneId after considering mPreferredDataSubId and mDefaultDataSubId above.
protected int mPreferredDataPhoneId = SubscriptionManager.INVALID_PHONE_INDEX;
private int mPhoneIdInCall = SubscriptionManager.INVALID_PHONE_INDEX;
private ISetOpportunisticDataCallback mSetOpptSubCallback;
private static final int EVENT_DEFAULT_SUBSCRIPTION_CHANGED = 101;
private static final int EVENT_SUBSCRIPTION_CHANGED = 102;
private static final int EVENT_REQUEST_NETWORK = 103;
private static final int EVENT_RELEASE_NETWORK = 104;
private static final int EVENT_EMERGENCY_TOGGLE = 105;
private static final int EVENT_RADIO_CAPABILITY_CHANGED = 106;
private static final int EVENT_CHANGE_PREFERRED_SUBSCRIPTION = 107;
private static final int EVENT_RADIO_AVAILABLE = 108;
private static final int EVENT_PHONE_IN_CALL_CHANGED = 109;
private static final int EVENT_NETWORK_VALIDATION_DONE = 110;
private static final int EVENT_REMOVE_DEFAULT_NETWORK_CHANGE_CALLBACK = 111;
// Depending on version of IRadioConfig, we need to send either RIL_REQUEST_ALLOW_DATA if it's
// 1.0, or RIL_REQUEST_SET_PREFERRED_DATA if it's 1.1 or later. So internally mHalCommandToUse
// will be either HAL_COMMAND_ALLOW_DATA or HAL_COMMAND_ALLOW_DATA or HAL_COMMAND_UNKNOWN.
private static final int HAL_COMMAND_UNKNOWN = 0;
private static final int HAL_COMMAND_ALLOW_DATA = 1;
private static final int HAL_COMMAND_PREFERRED_DATA = 2;
private int mHalCommandToUse = HAL_COMMAND_UNKNOWN;
private RadioConfig mRadioConfig;
private final static int MAX_LOCAL_LOG_LINES = 30;
// Default timeout value of network validation in millisecond.
private final static int DEFAULT_VALIDATION_EXPIRATION_TIME = 2000;
private Boolean mHasRegisteredDefaultNetworkChangeCallback = false;
private ConnectivityManager mConnectivityManager;
private final ConnectivityManager.NetworkCallback mDefaultNetworkCallback =
new NetworkCallback() {
@Override
public void onAvailable(Network network) {
if (mConnectivityManager.getNetworkCapabilities(network)
.hasTransport(TRANSPORT_CELLULAR)) {
logDataSwitchEvent(
TelephonyEvent.EventState.EVENT_STATE_END,
TelephonyEvent.DataSwitch.Reason.DATA_SWITCH_REASON_UNKNOWN);
}
removeDefaultNetworkChangeCallback();
}
};
/**
* Method to get singleton instance.
*/
public static PhoneSwitcher getInstance() {
return sPhoneSwitcher;
}
/**
* Method to create singleton instance.
*/
public static PhoneSwitcher make(int maxActivePhones, int numPhones, Context context,
SubscriptionController subscriptionController, Looper looper, ITelephonyRegistry tr,
CommandsInterface[] cis, Phone[] phones) {
if (sPhoneSwitcher == null) {
sPhoneSwitcher = new PhoneSwitcher(maxActivePhones, numPhones, context,
subscriptionController, looper, tr, cis, phones);
}
return sPhoneSwitcher;
}
/** This constructor is only used for testing purpose. */
@VisibleForTesting
public PhoneSwitcher(int numPhones, Looper looper) {
super(looper);
mMaxActivePhones = 0;
mSubscriptionController = null;
mCommandsInterfaces = null;
mContext = null;
mPhoneStates = null;
mPhones = null;
mLocalLog = null;
mActivePhoneRegistrants = null;
mNumPhones = numPhones;
mPhoneSubscriptions = new int[numPhones];
mRadioConfig = RadioConfig.getInstance(mContext);
mPhoneStateListener = new PhoneStateListener(looper) {
public void onPhoneCapabilityChanged(PhoneCapability capability) {
onPhoneCapabilityChangedInternal(capability);
}
};
mValidator = CellularNetworkValidator.getInstance();
}
@VisibleForTesting
public PhoneSwitcher(int maxActivePhones, int numPhones, Context context,
SubscriptionController subscriptionController, Looper looper, ITelephonyRegistry tr,
CommandsInterface[] cis, Phone[] phones) {
super(looper);
mContext = context;
mNumPhones = numPhones;
mPhones = phones;
mPhoneSubscriptions = new int[numPhones];
mMaxActivePhones = maxActivePhones;
mLocalLog = new LocalLog(MAX_LOCAL_LOG_LINES);
mSubscriptionController = subscriptionController;
mRadioConfig = RadioConfig.getInstance(mContext);
mPhoneStateListener = new PhoneStateListener(looper) {
@Override
public void onPhoneCapabilityChanged(PhoneCapability capability) {
onPhoneCapabilityChangedInternal(capability);
}
@Override
public void onPreciseCallStateChanged(PreciseCallState callState) {
int oldPhoneIdInCall = mPhoneIdInCall;
// If there's no active call, the value will become INVALID_PHONE_INDEX
// and internet data will be switched back to system selected or user selected
// subscription.
mPhoneIdInCall = SubscriptionManager.INVALID_PHONE_INDEX;
for (Phone phone : mPhones) {
if (isCallActive(phone) || isCallActive(phone.getImsPhone())) {
mPhoneIdInCall = phone.getPhoneId();
break;
}
}
if (mPhoneIdInCall != oldPhoneIdInCall) {
log("mPhoneIdInCall changed from" + oldPhoneIdInCall
+ " to " + mPhoneIdInCall);
Message msg = PhoneSwitcher.this.obtainMessage(EVENT_PHONE_IN_CALL_CHANGED);
msg.sendToTarget();
}
}
};
mValidator = CellularNetworkValidator.getInstance();
TelephonyManager telephonyManager =
(TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
telephonyManager.listen(mPhoneStateListener, LISTEN_PHONE_CAPABILITY_CHANGE
| LISTEN_PRECISE_CALL_STATE);
mActivePhoneRegistrants = new RegistrantList();
mPhoneStates = new PhoneState[numPhones];
for (int i = 0; i < numPhones; i++) {
mPhoneStates[i] = new PhoneState();
if (mPhones[i] != null) {
mPhones[i].registerForEmergencyCallToggle(this, EVENT_EMERGENCY_TOGGLE, null);
}
}
mCommandsInterfaces = cis;
mCommandsInterfaces[0].registerForAvailable(this, EVENT_RADIO_AVAILABLE, null);
try {
tr.addOnSubscriptionsChangedListener(context.getOpPackageName(),
mSubscriptionsChangedListener);
} catch (RemoteException e) {
}
mConnectivityManager =
(ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
mContext.registerReceiver(mDefaultDataChangedReceiver,
new IntentFilter(TelephonyIntents.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED));
NetworkCapabilities netCap = new NetworkCapabilities();
netCap.addTransportType(TRANSPORT_CELLULAR);
netCap.addCapability(NetworkCapabilities.NET_CAPABILITY_MMS);
netCap.addCapability(NetworkCapabilities.NET_CAPABILITY_SUPL);
netCap.addCapability(NetworkCapabilities.NET_CAPABILITY_DUN);
netCap.addCapability(NetworkCapabilities.NET_CAPABILITY_FOTA);
netCap.addCapability(NetworkCapabilities.NET_CAPABILITY_IMS);
netCap.addCapability(NetworkCapabilities.NET_CAPABILITY_CBS);
netCap.addCapability(NetworkCapabilities.NET_CAPABILITY_IA);
netCap.addCapability(NetworkCapabilities.NET_CAPABILITY_RCS);
netCap.addCapability(NetworkCapabilities.NET_CAPABILITY_XCAP);
netCap.addCapability(NetworkCapabilities.NET_CAPABILITY_EIMS);
netCap.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED);
netCap.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET);
netCap.addCapability(NetworkCapabilities.NET_CAPABILITY_MCX);
netCap.setNetworkSpecifier(new MatchAllNetworkSpecifier());
NetworkFactory networkFactory = new PhoneSwitcherNetworkRequestListener(looper, context,
netCap, this);
// we want to see all requests
networkFactory.setScoreFilter(101);
networkFactory.register();
log("PhoneSwitcher started");
}
private final BroadcastReceiver mDefaultDataChangedReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
Message msg = PhoneSwitcher.this.obtainMessage(EVENT_DEFAULT_SUBSCRIPTION_CHANGED);
msg.sendToTarget();
}
};
private final IOnSubscriptionsChangedListener mSubscriptionsChangedListener =
new IOnSubscriptionsChangedListener.Stub() {
@Override
public void onSubscriptionsChanged() {
Message msg = PhoneSwitcher.this.obtainMessage(EVENT_SUBSCRIPTION_CHANGED);
msg.sendToTarget();
}
};
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case EVENT_SUBSCRIPTION_CHANGED: {
onEvaluate(REQUESTS_UNCHANGED, "subChanged");
break;
}
case EVENT_DEFAULT_SUBSCRIPTION_CHANGED: {
logDataSwitchEvent(TelephonyEvent.EventState.EVENT_STATE_START,
DataSwitch.Reason.DATA_SWITCH_REASON_MANUAL);
onEvaluate(REQUESTS_UNCHANGED, "defaultChanged");
registerDefaultNetworkChangeCallback();
break;
}
case EVENT_REQUEST_NETWORK: {
onRequestNetwork((NetworkRequest)msg.obj);
break;
}
case EVENT_RELEASE_NETWORK: {
onReleaseNetwork((NetworkRequest)msg.obj);
break;
}
case EVENT_EMERGENCY_TOGGLE: {
onEvaluate(REQUESTS_CHANGED, "emergencyToggle");
break;
}
case EVENT_RADIO_CAPABILITY_CHANGED: {
resendRilCommands(msg);
break;
}
case EVENT_CHANGE_PREFERRED_SUBSCRIPTION: {
int subId = msg.arg1;
boolean needValidation = (msg.arg2 == 1);
ISetOpportunisticDataCallback callback =
(ISetOpportunisticDataCallback) msg.obj;
if (SubscriptionManager.isUsableSubscriptionId(subId)) {
setOpportunisticDataSubscription(subId, needValidation, callback);
} else {
unsetOpportunisticDataSubscription(callback);
}
break;
}
case EVENT_RADIO_AVAILABLE: {
updateHalCommandToUse();
onEvaluate(REQUESTS_UNCHANGED, "EVENT_RADIO_AVAILABLE");
break;
}
case EVENT_PHONE_IN_CALL_CHANGED: {
logDataSwitchEvent(TelephonyEvent.EventState.EVENT_STATE_START,
DataSwitch.Reason.DATA_SWITCH_REASON_IN_CALL);
onEvaluate(REQUESTS_UNCHANGED, "EVENT_PHONE_IN_CALL_CHANGED");
registerDefaultNetworkChangeCallback();
break;
}
case EVENT_NETWORK_VALIDATION_DONE: {
int subId = msg.arg1;
boolean passed = (msg.arg2 == 1);
onValidationDone(subId, passed);
break;
}
case EVENT_REMOVE_DEFAULT_NETWORK_CHANGE_CALLBACK: {
removeDefaultNetworkChangeCallback();
break;
}
}
}
private boolean isEmergency() {
for (Phone p : mPhones) {
if (p == null) continue;
if (p.isInEcm() || p.isInEmergencyCall()) return true;
}
return false;
}
private static class PhoneSwitcherNetworkRequestListener extends NetworkFactory {
private final PhoneSwitcher mPhoneSwitcher;
public PhoneSwitcherNetworkRequestListener (Looper l, Context c,
NetworkCapabilities nc, PhoneSwitcher ps) {
super(l, c, "PhoneSwitcherNetworkRequstListener", nc);
mPhoneSwitcher = ps;
}
@Override
protected void needNetworkFor(NetworkRequest networkRequest, int score) {
if (VDBG) log("needNetworkFor " + networkRequest + ", " + score);
Message msg = mPhoneSwitcher.obtainMessage(EVENT_REQUEST_NETWORK);
msg.obj = networkRequest;
msg.sendToTarget();
}
@Override
protected void releaseNetworkFor(NetworkRequest networkRequest) {
if (VDBG) log("releaseNetworkFor " + networkRequest);
Message msg = mPhoneSwitcher.obtainMessage(EVENT_RELEASE_NETWORK);
msg.obj = networkRequest;
msg.sendToTarget();
}
}
private void onRequestNetwork(NetworkRequest networkRequest) {
final DcRequest dcRequest = new DcRequest(networkRequest, mContext);
if (!mPrioritizedDcRequests.contains(dcRequest)) {
collectRequestNetworkMetrics(networkRequest);
mPrioritizedDcRequests.add(dcRequest);
Collections.sort(mPrioritizedDcRequests);
onEvaluate(REQUESTS_CHANGED, "netRequest");
}
}
private void onReleaseNetwork(NetworkRequest networkRequest) {
final DcRequest dcRequest = new DcRequest(networkRequest, mContext);
if (mPrioritizedDcRequests.remove(dcRequest)) {
onEvaluate(REQUESTS_CHANGED, "netReleased");
collectReleaseNetworkMetrics(networkRequest);
}
}
private void removeDefaultNetworkChangeCallback() {
synchronized (mHasRegisteredDefaultNetworkChangeCallback) {
if (mHasRegisteredDefaultNetworkChangeCallback) {
mHasRegisteredDefaultNetworkChangeCallback = false;
removeMessages(EVENT_REMOVE_DEFAULT_NETWORK_CHANGE_CALLBACK);
mConnectivityManager.unregisterNetworkCallback(mDefaultNetworkCallback);
}
}
}
private void registerDefaultNetworkChangeCallback() {
removeDefaultNetworkChangeCallback();
synchronized (mHasRegisteredDefaultNetworkChangeCallback) {
mHasRegisteredDefaultNetworkChangeCallback = true;
mConnectivityManager.registerDefaultNetworkCallback(mDefaultNetworkCallback);
sendMessageDelayed(
obtainMessage(EVENT_REMOVE_DEFAULT_NETWORK_CHANGE_CALLBACK),
DEFAULT_NETWORK_CHANGE_TIMEOUT_MS);
}
}
private void collectRequestNetworkMetrics(NetworkRequest networkRequest) {
// Request network for MMS will temporary disable the network on default data subscription,
// this only happen on multi-sim device.
if (mNumPhones > 1 && networkRequest.networkCapabilities.hasCapability(
NetworkCapabilities.NET_CAPABILITY_MMS)) {
OnDemandDataSwitch onDemandDataSwitch = new OnDemandDataSwitch();
onDemandDataSwitch.apn = TelephonyEvent.ApnType.APN_TYPE_MMS;
onDemandDataSwitch.state = TelephonyEvent.EventState.EVENT_STATE_START;
TelephonyMetrics.getInstance().writeOnDemandDataSwitch(onDemandDataSwitch);
}
}
private void collectReleaseNetworkMetrics(NetworkRequest networkRequest) {
// Release network for MMS will recover the network on default data subscription, this only
// happen on multi-sim device.
if (mNumPhones > 1 && networkRequest.networkCapabilities.hasCapability(
NetworkCapabilities.NET_CAPABILITY_MMS)) {
OnDemandDataSwitch onDemandDataSwitch = new OnDemandDataSwitch();
onDemandDataSwitch.apn = TelephonyEvent.ApnType.APN_TYPE_MMS;
onDemandDataSwitch.state = TelephonyEvent.EventState.EVENT_STATE_END;
TelephonyMetrics.getInstance().writeOnDemandDataSwitch(onDemandDataSwitch);
}
}
private static final boolean REQUESTS_CHANGED = true;
private static final boolean REQUESTS_UNCHANGED = false;
/**
* Re-evaluate things.
* Do nothing if nothing's changed.
*
* Otherwise, go through the requests in priority order adding their phone
* until we've added up to the max allowed. Then go through shutting down
* phones that aren't in the active phone list. Finally, activate all
* phones in the active phone list.
*/
private void onEvaluate(boolean requestsChanged, String reason) {
StringBuilder sb = new StringBuilder(reason);
if (isEmergency()) {
log("onEvalute aborted due to Emergency");
return;
}
// If we use HAL_COMMAND_PREFERRED_DATA,
boolean diffDetected = mHalCommandToUse != HAL_COMMAND_PREFERRED_DATA && requestsChanged;
// Check if user setting of default data sub is changed.
final int dataSub = mSubscriptionController.getDefaultDataSubId();
if (dataSub != mDefaultDataSubId) {
sb.append(" default ").append(mDefaultDataSubId).append("->").append(dataSub);
mDefaultDataSubId = dataSub;
}
// Check if phoneId to subId mapping is changed.
for (int i = 0; i < mNumPhones; i++) {
int sub = mSubscriptionController.getSubIdUsingPhoneId(i);
if (sub != mPhoneSubscriptions[i]) {
sb.append(" phone[").append(i).append("] ").append(mPhoneSubscriptions[i]);
sb.append("->").append(sub);
mPhoneSubscriptions[i] = sub;
diffDetected = true;
}
}
// Check if phoneId for preferred data is changed.
int oldPreferredDataPhoneId = mPreferredDataPhoneId;
updatePhoneIdForDefaultNetworkRequests();
if (oldPreferredDataPhoneId != mPreferredDataPhoneId) {
sb.append(" preferred phoneId ").append(oldPreferredDataPhoneId)
.append("->").append(mPreferredDataPhoneId);
diffDetected = true;
}
if (diffDetected) {
log("evaluating due to " + sb.toString());
if (mHalCommandToUse == HAL_COMMAND_PREFERRED_DATA) {
// With HAL_COMMAND_PREFERRED_DATA, all phones are assumed to allow PS attach.
// So marking all phone as active.
for (int phoneId = 0; phoneId < mNumPhones; phoneId++) {
activate(phoneId);
}
if (SubscriptionManager.isUsableSubIdValue(mPreferredDataPhoneId)) {
mRadioConfig.setPreferredDataModem(mPreferredDataPhoneId, null);
}
} else {
List<Integer> newActivePhones = new ArrayList<Integer>();
/**
* If all phones can have PS attached, activate all.
* Otherwise, choose to activate phones according to requests. And
* if list is not full, add mPreferredDataPhoneId.
*/
if (mMaxActivePhones == mPhones.length) {
for (int i = 0; i < mMaxActivePhones; i++) {
newActivePhones.add(mPhones[i].getPhoneId());
}
} else {
for (DcRequest dcRequest : mPrioritizedDcRequests) {
int phoneIdForRequest = phoneIdForRequest(dcRequest.networkRequest);
if (phoneIdForRequest == INVALID_PHONE_INDEX) continue;
if (newActivePhones.contains(phoneIdForRequest)) continue;
newActivePhones.add(phoneIdForRequest);
if (newActivePhones.size() >= mMaxActivePhones) break;
}
if (newActivePhones.size() < mMaxActivePhones
&& newActivePhones.contains(mPreferredDataPhoneId)
&& SubscriptionManager.isUsableSubIdValue(mPreferredDataPhoneId)) {
newActivePhones.add(mPreferredDataPhoneId);
}
}
if (VDBG) {
log("default subId = " + mDefaultDataSubId);
log("preferred subId = " + mPreferredDataSubId);
for (int i = 0; i < mNumPhones; i++) {
log(" phone[" + i + "] using sub[" + mPhoneSubscriptions[i] + "]");
}
log(" newActivePhones:");
for (Integer i : newActivePhones) log(" " + i);
}
for (int phoneId = 0; phoneId < mNumPhones; phoneId++) {
if (!newActivePhones.contains(phoneId)) {
deactivate(phoneId);
}
}
// only activate phones up to the limit
for (int phoneId : newActivePhones) {
activate(phoneId);
}
}
notifyActiveDataSubIdChanged(mSubscriptionController.getSubIdUsingPhoneId(
mPreferredDataPhoneId));
// Notify all registrants.
mActivePhoneRegistrants.notifyRegistrants();
}
}
private static class PhoneState {
public volatile boolean active = false;
public long lastRequested = 0;
}
private void activate(int phoneId) {
switchPhone(phoneId, true);
}
private void deactivate(int phoneId) {
switchPhone(phoneId, false);
}
private void switchPhone(int phoneId, boolean active) {
PhoneState state = mPhoneStates[phoneId];
if (state.active == active) return;
state.active = active;
log((active ? "activate " : "deactivate ") + phoneId);
state.lastRequested = System.currentTimeMillis();
if (mHalCommandToUse == HAL_COMMAND_ALLOW_DATA || mHalCommandToUse == HAL_COMMAND_UNKNOWN) {
// Skip ALLOW_DATA for single SIM device
if (mNumPhones > 1) {
mCommandsInterfaces[phoneId].setDataAllowed(active, null);
}
}
}
/**
* Used when the modem may have been rebooted and we
* want to resend setDataAllowed or setPreferredDataSubscriptionId
*/
public void onRadioCapChanged(int phoneId) {
validatePhoneId(phoneId);
Message msg = obtainMessage(EVENT_RADIO_CAPABILITY_CHANGED);
msg.arg1 = phoneId;
msg.sendToTarget();
}
private void resendRilCommands(Message msg) {
final int phoneId = msg.arg1;
if (mHalCommandToUse == HAL_COMMAND_ALLOW_DATA || mHalCommandToUse == HAL_COMMAND_UNKNOWN) {
// Skip ALLOW_DATA for single SIM device
if (mNumPhones > 1) {
mCommandsInterfaces[phoneId].setDataAllowed(isPhoneActive(phoneId), null);
}
} else {
mRadioConfig.setPreferredDataModem(mPreferredDataPhoneId, null);
}
}
private void onPhoneCapabilityChangedInternal(PhoneCapability capability) {
int newMaxActivePhones = TelephonyManager.getDefault()
.getNumberOfModemsWithSimultaneousDataConnections();
if (mMaxActivePhones != newMaxActivePhones) {
mMaxActivePhones = newMaxActivePhones;
log("Max active phones changed to " + mMaxActivePhones);
onEvaluate(REQUESTS_UNCHANGED, "phoneCfgChanged");
}
}
private int phoneIdForRequest(NetworkRequest netRequest) {
int subId = getSubIdFromNetworkRequest(netRequest);
if (subId == DEFAULT_SUBSCRIPTION_ID) return mPreferredDataPhoneId;
if (subId == INVALID_SUBSCRIPTION_ID) return INVALID_PHONE_INDEX;
int preferredDataSubId = SubscriptionManager.isValidPhoneId(mPreferredDataPhoneId)
? mPhoneSubscriptions[mPreferredDataPhoneId] : INVALID_SUBSCRIPTION_ID;
// Currently we assume multi-SIM devices will only support one Internet PDN connection. So
// if Internet PDN is established on the non-preferred phone, it will interrupt
// Internet connection on the preferred phone. So we only accept Internet request with
// preferred data subscription or no specified subscription.
if (netRequest.networkCapabilities.hasCapability(
NetworkCapabilities.NET_CAPABILITY_INTERNET)
&& subId != preferredDataSubId && subId != mValidator.getSubIdInValidation()) {
// Returning INVALID_PHONE_INDEX will result in netRequest not being handled.
return INVALID_PHONE_INDEX;
}
// Try to find matching phone ID. If it doesn't exist, we'll end up returning INVALID.
int phoneId = INVALID_PHONE_INDEX;
for (int i = 0; i < mNumPhones; i++) {
if (mPhoneSubscriptions[i] == subId) {
phoneId = i;
break;
}
}
return phoneId;
}
private int getSubIdFromNetworkRequest(NetworkRequest networkRequest) {
NetworkSpecifier specifier = networkRequest.networkCapabilities.getNetworkSpecifier();
if (specifier == null) {
return DEFAULT_SUBSCRIPTION_ID;
}
int subId;
if (specifier instanceof StringNetworkSpecifier) {
try {
subId = Integer.parseInt(((StringNetworkSpecifier) specifier).specifier);
} catch (NumberFormatException e) {
Rlog.e(LOG_TAG, "NumberFormatException on "
+ ((StringNetworkSpecifier) specifier).specifier);
return INVALID_SUBSCRIPTION_ID;
}
} else {
return INVALID_SUBSCRIPTION_ID;
}
return subId;
}
private int getSubIdForDefaultNetworkRequests() {
if (mSubscriptionController.isActiveSubId(mPreferredDataSubId)) {
return mPreferredDataSubId;
} else {
return mDefaultDataSubId;
}
}
// This updates mPreferredDataPhoneId which decides which phone should
// handle default network requests.
private void updatePhoneIdForDefaultNetworkRequests() {
if (SubscriptionManager.isValidPhoneId(mPhoneIdInCall)) {
// If a phone is in call and user enabled its mobile data, we
// should switch internet connection to it. Because the other modem
// will lose data connection anyway.
// TODO: validate network first.
mPreferredDataPhoneId = mPhoneIdInCall;
} else {
int subId = getSubIdForDefaultNetworkRequests();
int phoneId = SubscriptionManager.INVALID_PHONE_INDEX;
if (SubscriptionManager.isUsableSubIdValue(subId)) {
for (int i = 0; i < mNumPhones; i++) {
if (mPhoneSubscriptions[i] == subId) {
phoneId = i;
break;
}
}
}
mPreferredDataPhoneId = phoneId;
}
}
public boolean shouldApplyNetworkRequest(NetworkRequest networkRequest, int phoneId) {
validatePhoneId(phoneId);
// In any case, if phone state is inactive, don't apply the network request.
if (!isPhoneActive(phoneId)) return false;
int phoneIdToHandle = phoneIdForRequest(networkRequest);
return phoneId == phoneIdToHandle;
}
@VisibleForTesting
protected boolean isPhoneActive(int phoneId) {
return mPhoneStates[phoneId].active;
}
/**
* If preferred phone changes, or phone activation status changes, registrants
* will be notified.
*/
public void registerForActivePhoneSwitch(Handler h, int what, Object o) {
Registrant r = new Registrant(h, what, o);
mActivePhoneRegistrants.add(r);
r.notifyRegistrant();
}
public void unregisterForActivePhoneSwitch(Handler h) {
mActivePhoneRegistrants.remove(h);
}
@VisibleForTesting
protected void validatePhoneId(int phoneId) {
if (phoneId < 0 || phoneId >= mNumPhones) {
throw new IllegalArgumentException("Invalid PhoneId");
}
}
/**
* Set opportunistic data subscription. It's an indication to switch Internet data to this
* subscription. It has to be an active subscription, and PhoneSwitcher will try to validate
* it first if needed.
*/
private void setOpportunisticDataSubscription(int subId, boolean needValidation,
ISetOpportunisticDataCallback callback) {
if (!mSubscriptionController.isActiveSubId(subId)) {
log("Can't switch data to inactive subId " + subId);
sendSetOpptCallbackHelper(callback, SET_OPPORTUNISTIC_SUB_INVALID_PARAMETER);
return;
}
if (mValidator.isValidating()
&& (!needValidation || subId != mValidator.getSubIdInValidation())) {
mValidator.stopValidation();
}
if (subId == mPreferredDataSubId) {
sendSetOpptCallbackHelper(callback, SET_OPPORTUNISTIC_SUB_SUCCESS);
return;
}
// If validation feature is not supported, set it directly. Otherwise,
// start validation on the subscription first.
if (CellularNetworkValidator.isValidationFeatureSupported() && needValidation) {
logDataSwitchEvent(TelephonyEvent.EventState.EVENT_STATE_START,
DataSwitch.Reason.DATA_SWITCH_REASON_CBRS);
mSetOpptSubCallback = callback;
mValidator.validate(subId, DEFAULT_VALIDATION_EXPIRATION_TIME,
false, mValidationCallback);
} else {
setPreferredSubscription(subId);
sendSetOpptCallbackHelper(callback, SET_OPPORTUNISTIC_SUB_SUCCESS);
}
}
/**
* Unset opportunistic data subscription. It's an indication to switch Internet data back
* from opportunistic subscription to primary subscription.
*/
private void unsetOpportunisticDataSubscription(ISetOpportunisticDataCallback callback) {
if (mValidator.isValidating()) {
mValidator.stopValidation();
}
// Set mPreferredDataSubId back to DEFAULT_SUBSCRIPTION_ID. This will trigger
// data switch to mDefaultDataSubId.
setPreferredSubscription(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID);
sendSetOpptCallbackHelper(callback, SET_OPPORTUNISTIC_SUB_SUCCESS);
}
private void sendSetOpptCallbackHelper(ISetOpportunisticDataCallback callback, int result) {
if (callback == null) return;
try {
callback.onComplete(result);
} catch (RemoteException exception) {
log("RemoteException " + exception);
}
}
/**
* Set opportunistic data subscription.
*/
private void setPreferredSubscription(int subId) {
if (mPreferredDataSubId != subId) {
mPreferredDataSubId = subId;
logDataSwitchEvent(TelephonyEvent.EventState.EVENT_STATE_START,
DataSwitch.Reason.DATA_SWITCH_REASON_CBRS);
onEvaluate(REQUESTS_UNCHANGED, "preferredDataSubscriptionIdChanged");
registerDefaultNetworkChangeCallback();
}
}
private void onValidationDone(int subId, boolean passed) {
log("Network validation " + (passed ? "passed" : "failed")
+ " on subId " + subId);
if (passed) setPreferredSubscription(subId);
// Trigger callback if needed
sendSetOpptCallbackHelper(mSetOpptSubCallback, passed ? SET_OPPORTUNISTIC_SUB_SUCCESS
: SET_OPPORTUNISTIC_SUB_VALIDATION_FAILED);
mSetOpptSubCallback = null;
}
// TODO b/123598154: rename preferredDataSub to opportunisticSubId.
public void trySetPreferredSubscription(int subId, boolean needValidation,
ISetOpportunisticDataCallback callback) {
log("Try set preferred subscription to subId " + subId
+ (needValidation ? " with " : " without ") + "validation");
PhoneSwitcher.this.obtainMessage(EVENT_CHANGE_PREFERRED_SUBSCRIPTION,
subId, needValidation ? 1 : 0, callback).sendToTarget();
}
private boolean isCallActive(Phone phone) {
if (phone == null) {
return false;
}
return (phone.getForegroundCall().getState() == Call.State.ACTIVE
|| phone.getForegroundCall().getState() == Call.State.ALERTING);
}
private void updateHalCommandToUse() {
mHalCommandToUse = mRadioConfig.isSetPreferredDataCommandSupported()
? HAL_COMMAND_PREFERRED_DATA : HAL_COMMAND_ALLOW_DATA;
}
public int getPreferredDataSubscriptionId() {
return mPreferredDataSubId;
}
private void log(String l) {
Rlog.d(LOG_TAG, l);
mLocalLog.log(l);
}
private void logDataSwitchEvent(int state, int reason) {
DataSwitch dataSwitch = new DataSwitch();
dataSwitch.state = state;
dataSwitch.reason = reason;
TelephonyMetrics.getInstance().writeDataSwitch(dataSwitch);
}
private void notifyActiveDataSubIdChanged(int activeDataSubId) {
ITelephonyRegistry tr = ITelephonyRegistry.Stub.asInterface(ServiceManager.getService(
"telephony.registry"));
try {
log("notifyActiveDataSubIdChanged to " + activeDataSubId);
tr.notifyActiveDataSubIdChanged(activeDataSubId);
} catch (RemoteException ex) {
// Should never happen because its always available.
}
}
public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
final IndentingPrintWriter pw = new IndentingPrintWriter(writer, " ");
pw.println("PhoneSwitcher:");
Calendar c = Calendar.getInstance();
for (int i = 0; i < mNumPhones; i++) {
PhoneState ps = mPhoneStates[i];
c.setTimeInMillis(ps.lastRequested);
pw.println("PhoneId(" + i + ") active=" + ps.active + ", lastRequest=" +
(ps.lastRequested == 0 ? "never" :
String.format("%tm-%td %tH:%tM:%tS.%tL", c, c, c, c, c, c)));
}
pw.increaseIndent();
mLocalLog.dump(fd, pw, args);
pw.decreaseIndent();
}
}