blob: 1daf463d38bf32fba6e53b09f15f6777685c7ce5 [file] [log] [blame]
/*
* Copyright (C) 2006 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.internal.telephony.dataconnection;
import android.app.PendingIntent;
import android.content.res.Resources;
import android.net.ConnectivityManager;
import android.net.NetworkCapabilities;
import android.net.NetworkConfig;
import android.net.NetworkRequest;
import android.telephony.Rlog;
import android.text.TextUtils;
import android.util.LocalLog;
import android.util.SparseIntArray;
import com.android.internal.R;
import com.android.internal.telephony.DctConstants;
import com.android.internal.telephony.Phone;
import com.android.internal.telephony.PhoneConstants;
import com.android.internal.telephony.RetryManager;
import com.android.internal.util.IndentingPrintWriter;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
/**
* Maintain the Apn context
*/
public class ApnContext {
public final String LOG_TAG;
private final static String SLOG_TAG = "ApnContext";
protected static final boolean DBG = false;
private final Phone mPhone;
private final String mApnType;
private DctConstants.State mState;
public final int priority;
private ApnSetting mApnSetting;
DcAsyncChannel mDcAc;
String mReason;
PendingIntent mReconnectAlarmIntent;
/**
* user/app requested connection on this APN
*/
AtomicBoolean mDataEnabled;
private final Object mRefCountLock = new Object();
private int mRefCount = 0;
/**
* carrier requirements met
*/
AtomicBoolean mDependencyMet;
private final DcTracker mDcTracker;
/**
* Remember this as a change in this value to a more permissive state
* should cause us to retry even permanent failures
*/
private boolean mConcurrentVoiceAndDataAllowed;
/**
* used to track a single connection request so disconnects can get ignored if
* obsolete.
*/
private final AtomicInteger mConnectionGeneration = new AtomicInteger(0);
/**
* Retry manager that handles the APN retry and delays.
*/
private final RetryManager mRetryManager;
/**
* AonContext constructor
* @param phone phone object
* @param apnType APN type (e.g. default, supl, mms, etc...)
* @param logTag Tag for logging
* @param config Network configuration
* @param tracker Data call tracker
*/
public ApnContext(Phone phone, String apnType, String logTag, NetworkConfig config,
DcTracker tracker) {
mPhone = phone;
mApnType = apnType;
mState = DctConstants.State.IDLE;
setReason(Phone.REASON_DATA_ENABLED);
mDataEnabled = new AtomicBoolean(false);
mDependencyMet = new AtomicBoolean(config.dependencyMet);
priority = config.priority;
LOG_TAG = logTag;
mDcTracker = tracker;
mRetryManager = new RetryManager(phone, apnType);
}
/**
* Get the APN type
* @return The APN type
*/
public String getApnType() {
return mApnType;
}
/**
* Get the data call async channel.
* @return The data call async channel
*/
public synchronized DcAsyncChannel getDcAc() {
return mDcAc;
}
/**
* Set the data call async channel.
* @param dcac The data call async channel
*/
public synchronized void setDataConnectionAc(DcAsyncChannel dcac) {
if (DBG) {
log("setDataConnectionAc: old dcac=" + mDcAc + " new dcac=" + dcac
+ " this=" + this);
}
mDcAc = dcac;
}
/**
* Release data connection.
* @param reason The reason of releasing data connection
*/
public synchronized void releaseDataConnection(String reason) {
if (mDcAc != null) {
mDcAc.tearDown(this, reason, null);
mDcAc = null;
}
setState(DctConstants.State.IDLE);
}
/**
* Get the reconnect intent.
* @return The reconnect intent
*/
public synchronized PendingIntent getReconnectIntent() {
return mReconnectAlarmIntent;
}
/**
* Save the reconnect intent which can be used for cancelling later.
* @param intent The reconnect intent
*/
public synchronized void setReconnectIntent(PendingIntent intent) {
mReconnectAlarmIntent = intent;
}
/**
* Get the current APN setting.
* @return APN setting
*/
public synchronized ApnSetting getApnSetting() {
if (DBG) log("getApnSetting: apnSetting=" + mApnSetting);
return mApnSetting;
}
/**
* Set the APN setting.
* @param apnSetting APN setting
*/
public synchronized void setApnSetting(ApnSetting apnSetting) {
if (DBG) log("setApnSetting: apnSetting=" + apnSetting);
mApnSetting = apnSetting;
}
/**
* Set the list of APN candidates which will be used for data call setup later.
* @param waitingApns List of APN candidates
*/
public synchronized void setWaitingApns(ArrayList<ApnSetting> waitingApns) {
mRetryManager.setWaitingApns(waitingApns);
}
/**
* Get the next available APN to try.
* @return APN setting which will be used for data call setup. Return null if there is no
* APN can be retried.
*/
public ApnSetting getNextApnSetting() {
return mRetryManager.getNextApnSetting();
}
/**
* Save the modem suggested delay for retrying the current APN.
* This method is called when we get the suggested delay from RIL.
* @param delay The delay in milliseconds
*/
public void setModemSuggestedDelay(long delay) {
mRetryManager.setModemSuggestedDelay(delay);
}
/**
* Get the delay for trying the next APN setting if the current one failed.
* @param failFastEnabled True if fail fast mode enabled. In this case we'll use a shorter
* delay.
* @return The delay in milliseconds
*/
public long getDelayForNextApn(boolean failFastEnabled) {
return mRetryManager.getDelayForNextApn(failFastEnabled || isFastRetryReason());
}
/**
* Mark the current APN setting permanently failed, which means it will not be retried anymore.
* @param apn APN setting
*/
public void markApnPermanentFailed(ApnSetting apn) {
mRetryManager.markApnPermanentFailed(apn);
}
/**
* Get the list of waiting APNs.
* @return the list of waiting APNs
*/
public ArrayList<ApnSetting> getWaitingApns() {
return mRetryManager.getWaitingApns();
}
/**
* Save the state indicating concurrent voice/data allowed.
* @param allowed True if concurrent voice/data is allowed
*/
public synchronized void setConcurrentVoiceAndDataAllowed(boolean allowed) {
mConcurrentVoiceAndDataAllowed = allowed;
}
/**
* Get the state indicating concurrent voice/data allowed.
* @return True if concurrent voice/data is allowed
*/
public synchronized boolean isConcurrentVoiceAndDataAllowed() {
return mConcurrentVoiceAndDataAllowed;
}
/**
* Set the current data call state.
* @param s Current data call state
*/
public synchronized void setState(DctConstants.State s) {
if (DBG) {
log("setState: " + s + ", previous state:" + mState);
}
mState = s;
if (mState == DctConstants.State.FAILED) {
if (mRetryManager.getWaitingApns() != null) {
mRetryManager.getWaitingApns().clear(); // when teardown the connection and set to IDLE
}
}
}
/**
* Get the current data call state.
* @return The current data call state
*/
public synchronized DctConstants.State getState() {
return mState;
}
/**
* Check whether the data call is disconnected or not.
* @return True if the data call is disconnected
*/
public boolean isDisconnected() {
DctConstants.State currentState = getState();
return ((currentState == DctConstants.State.IDLE) ||
currentState == DctConstants.State.FAILED);
}
/**
* Set the reason for data call connection.
* @param reason Reason for data call connection
*/
public synchronized void setReason(String reason) {
if (DBG) {
log("set reason as " + reason + ",current state " + mState);
}
mReason = reason;
}
/**
* Get the reason for data call connection.
* @return The reason for data call connection
*/
public synchronized String getReason() {
return mReason;
}
/**
* Check if ready for data call connection
* @return True if ready, otherwise false.
*/
public boolean isReady() {
return mDataEnabled.get() && mDependencyMet.get();
}
/**
* Check if the data call is in the state which allow connecting.
* @return True if allowed, otherwise false.
*/
public boolean isConnectable() {
return isReady() && ((mState == DctConstants.State.IDLE)
|| (mState == DctConstants.State.SCANNING)
|| (mState == DctConstants.State.RETRYING)
|| (mState == DctConstants.State.FAILED));
}
/**
* Check if apn reason is fast retry reason which should apply shorter delay between apn re-try.
* @return True if it is fast retry reason, otherwise false.
*/
private boolean isFastRetryReason() {
return Phone.REASON_NW_TYPE_CHANGED.equals(mReason) ||
Phone.REASON_APN_CHANGED.equals(mReason);
}
/** Check if the data call is in connected or connecting state.
* @return True if the data call is in connected or connecting state
*/
public boolean isConnectedOrConnecting() {
return isReady() && ((mState == DctConstants.State.CONNECTED)
|| (mState == DctConstants.State.CONNECTING)
|| (mState == DctConstants.State.SCANNING)
|| (mState == DctConstants.State.RETRYING));
}
/**
* Set data call enabled/disabled state.
* @param enabled True if data call is enabled
*/
public void setEnabled(boolean enabled) {
if (DBG) {
log("set enabled as " + enabled + ", current state is " + mDataEnabled.get());
}
mDataEnabled.set(enabled);
}
/**
* Check if the data call is enabled or not.
* @return True if enabled
*/
public boolean isEnabled() {
return mDataEnabled.get();
}
public void setDependencyMet(boolean met) {
if (DBG) {
log("set mDependencyMet as " + met + " current state is " + mDependencyMet.get());
}
mDependencyMet.set(met);
}
public boolean getDependencyMet() {
return mDependencyMet.get();
}
public boolean isProvisioningApn() {
String provisioningApn = mPhone.getContext().getResources()
.getString(R.string.mobile_provisioning_apn);
if (!TextUtils.isEmpty(provisioningApn) &&
(mApnSetting != null) && (mApnSetting.apn != null)) {
return (mApnSetting.apn.equals(provisioningApn));
} else {
return false;
}
}
private final ArrayList<LocalLog> mLocalLogs = new ArrayList<>();
private final ArrayList<NetworkRequest> mNetworkRequests = new ArrayList<>();
private final ArrayDeque<LocalLog> mHistoryLogs = new ArrayDeque<>();
private final static int MAX_HISTORY_LOG_COUNT = 4;
public void requestLog(String str) {
synchronized (mRefCountLock) {
for (LocalLog l : mLocalLogs) {
l.log(str);
}
}
}
public void requestNetwork(NetworkRequest networkRequest, 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);
if (mNetworkRequests.size() == 1) {
mDcTracker.setEnabled(apnIdForApnName(mApnType), true);
}
}
}
}
public void releaseNetwork(NetworkRequest networkRequest, LocalLog log) {
synchronized (mRefCountLock) {
if (mLocalLogs.contains(log) == false) {
log.log("ApnContext.releaseNetwork can't find this log");
} else {
mLocalLogs.remove(log);
}
if (mNetworkRequests.contains(networkRequest) == false) {
log.log("ApnContext.releaseNetwork can't find this request ("
+ networkRequest + ")");
} else {
mNetworkRequests.remove(networkRequest);
log.log("ApnContext.releaseNetwork left with " + mNetworkRequests.size() +
" requests.");
if (mNetworkRequests.size() == 0) {
mDcTracker.setEnabled(apnIdForApnName(mApnType), false);
}
}
}
}
public List<NetworkRequest> getNetworkRequests() {
synchronized (mRefCountLock) {
return new ArrayList<NetworkRequest>(mNetworkRequests);
}
}
public boolean hasNoRestrictedRequests() {
synchronized (mRefCountLock) {
for (NetworkRequest nr : mNetworkRequests) {
if (nr.networkCapabilities.hasCapability(
NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED) == false) {
return false;
}
}
}
return true;
}
private final SparseIntArray mRetriesLeftPerErrorCode = new SparseIntArray();
public void resetErrorCodeRetries() {
requestLog("ApnContext.resetErrorCodeRetries");
if (DBG) log("ApnContext.resetErrorCodeRetries");
String[] config = mPhone.getContext().getResources().getStringArray(
com.android.internal.R.array.config_cell_retries_per_error_code);
synchronized (mRetriesLeftPerErrorCode) {
mRetriesLeftPerErrorCode.clear();
for (String c : config) {
String errorValue[] = c.split(",");
if (errorValue != null && errorValue.length == 2) {
int count = 0;
int errorCode = 0;
try {
errorCode = Integer.parseInt(errorValue[0]);
count = Integer.parseInt(errorValue[1]);
} catch (NumberFormatException e) {
log("Exception parsing config_retries_per_error_code: " + e);
continue;
}
if (count > 0 && errorCode > 0) {
mRetriesLeftPerErrorCode.put(errorCode, count);
}
} else {
log("Exception parsing config_retries_per_error_code: " + c);
}
}
}
}
public boolean restartOnError(int errorCode) {
boolean result = false;
int retriesLeft = 0;
synchronized(mRetriesLeftPerErrorCode) {
retriesLeft = mRetriesLeftPerErrorCode.get(errorCode);
switch (retriesLeft) {
case 0: {
// not set, never restart modem
break;
}
case 1: {
resetErrorCodeRetries();
result = true;
break;
}
default: {
mRetriesLeftPerErrorCode.put(errorCode, retriesLeft - 1);
result = false;
}
}
}
String str = "ApnContext.restartOnError(" + errorCode + ") found " + retriesLeft +
" and returned " + result;
if (DBG) log(str);
requestLog(str);
return result;
}
public int incAndGetConnectionGeneration() {
return mConnectionGeneration.incrementAndGet();
}
public int getConnectionGeneration() {
return mConnectionGeneration.get();
}
public long getInterApnDelay(boolean failFastEnabled) {
return mRetryManager.getInterApnDelay(failFastEnabled || isFastRetryReason());
}
public static int apnIdForType(int networkType) {
switch (networkType) {
case ConnectivityManager.TYPE_MOBILE:
return DctConstants.APN_DEFAULT_ID;
case ConnectivityManager.TYPE_MOBILE_MMS:
return DctConstants.APN_MMS_ID;
case ConnectivityManager.TYPE_MOBILE_SUPL:
return DctConstants.APN_SUPL_ID;
case ConnectivityManager.TYPE_MOBILE_DUN:
return DctConstants.APN_DUN_ID;
case ConnectivityManager.TYPE_MOBILE_FOTA:
return DctConstants.APN_FOTA_ID;
case ConnectivityManager.TYPE_MOBILE_IMS:
return DctConstants.APN_IMS_ID;
case ConnectivityManager.TYPE_MOBILE_CBS:
return DctConstants.APN_CBS_ID;
case ConnectivityManager.TYPE_MOBILE_IA:
return DctConstants.APN_IA_ID;
case ConnectivityManager.TYPE_MOBILE_EMERGENCY:
return DctConstants.APN_EMERGENCY_ID;
default:
return DctConstants.APN_INVALID_ID;
}
}
public static int apnIdForNetworkRequest(NetworkRequest nr) {
NetworkCapabilities nc = nr.networkCapabilities;
// For now, ignore the bandwidth stuff
if (nc.getTransportTypes().length > 0 &&
nc.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) == false) {
return DctConstants.APN_INVALID_ID;
}
// in the near term just do 1-1 matches.
// TODO - actually try to match the set of capabilities
int apnId = DctConstants.APN_INVALID_ID;
boolean error = false;
if (nc.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)) {
apnId = DctConstants.APN_DEFAULT_ID;
}
if (nc.hasCapability(NetworkCapabilities.NET_CAPABILITY_MMS)) {
if (apnId != DctConstants.APN_INVALID_ID) error = true;
apnId = DctConstants.APN_MMS_ID;
}
if (nc.hasCapability(NetworkCapabilities.NET_CAPABILITY_SUPL)) {
if (apnId != DctConstants.APN_INVALID_ID) error = true;
apnId = DctConstants.APN_SUPL_ID;
}
if (nc.hasCapability(NetworkCapabilities.NET_CAPABILITY_DUN)) {
if (apnId != DctConstants.APN_INVALID_ID) error = true;
apnId = DctConstants.APN_DUN_ID;
}
if (nc.hasCapability(NetworkCapabilities.NET_CAPABILITY_FOTA)) {
if (apnId != DctConstants.APN_INVALID_ID) error = true;
apnId = DctConstants.APN_FOTA_ID;
}
if (nc.hasCapability(NetworkCapabilities.NET_CAPABILITY_IMS)) {
if (apnId != DctConstants.APN_INVALID_ID) error = true;
apnId = DctConstants.APN_IMS_ID;
}
if (nc.hasCapability(NetworkCapabilities.NET_CAPABILITY_CBS)) {
if (apnId != DctConstants.APN_INVALID_ID) error = true;
apnId = DctConstants.APN_CBS_ID;
}
if (nc.hasCapability(NetworkCapabilities.NET_CAPABILITY_IA)) {
if (apnId != DctConstants.APN_INVALID_ID) error = true;
apnId = DctConstants.APN_IA_ID;
}
if (nc.hasCapability(NetworkCapabilities.NET_CAPABILITY_RCS)) {
if (apnId != DctConstants.APN_INVALID_ID) error = true;
Rlog.d(SLOG_TAG, "RCS APN type not yet supported");
}
if (nc.hasCapability(NetworkCapabilities.NET_CAPABILITY_XCAP)) {
if (apnId != DctConstants.APN_INVALID_ID) error = true;
Rlog.d(SLOG_TAG, "XCAP APN type not yet supported");
}
if (nc.hasCapability(NetworkCapabilities.NET_CAPABILITY_EIMS)) {
if (apnId != DctConstants.APN_INVALID_ID) error = true;
apnId = DctConstants.APN_MMS_ID;
}
if (error) {
// TODO: If this error condition is removed, the framework's handling of
// NET_CAPABILITY_NOT_RESTRICTED will need to be updated so requests for
// say FOTA and INTERNET are marked as restricted. This is not how
// NetworkCapabilities.maybeMarkCapabilitiesRestricted currently works.
Rlog.d(SLOG_TAG, "Multiple apn types specified in request - result is unspecified!");
}
if (apnId == DctConstants.APN_INVALID_ID) {
Rlog.d(SLOG_TAG, "Unsupported NetworkRequest in Telephony: nr=" + nr);
}
return apnId;
}
// TODO - kill The use of these strings
public static int apnIdForApnName(String type) {
switch (type) {
case PhoneConstants.APN_TYPE_DEFAULT:
return DctConstants.APN_DEFAULT_ID;
case PhoneConstants.APN_TYPE_MMS:
return DctConstants.APN_MMS_ID;
case PhoneConstants.APN_TYPE_SUPL:
return DctConstants.APN_SUPL_ID;
case PhoneConstants.APN_TYPE_DUN:
return DctConstants.APN_DUN_ID;
case PhoneConstants.APN_TYPE_HIPRI:
return DctConstants.APN_HIPRI_ID;
case PhoneConstants.APN_TYPE_IMS:
return DctConstants.APN_IMS_ID;
case PhoneConstants.APN_TYPE_FOTA:
return DctConstants.APN_FOTA_ID;
case PhoneConstants.APN_TYPE_CBS:
return DctConstants.APN_CBS_ID;
case PhoneConstants.APN_TYPE_IA:
return DctConstants.APN_IA_ID;
case PhoneConstants.APN_TYPE_EMERGENCY:
return DctConstants.APN_EMERGENCY_ID;
default:
return DctConstants.APN_INVALID_ID;
}
}
private static String apnNameForApnId(int id) {
switch (id) {
case DctConstants.APN_DEFAULT_ID:
return PhoneConstants.APN_TYPE_DEFAULT;
case DctConstants.APN_MMS_ID:
return PhoneConstants.APN_TYPE_MMS;
case DctConstants.APN_SUPL_ID:
return PhoneConstants.APN_TYPE_SUPL;
case DctConstants.APN_DUN_ID:
return PhoneConstants.APN_TYPE_DUN;
case DctConstants.APN_HIPRI_ID:
return PhoneConstants.APN_TYPE_HIPRI;
case DctConstants.APN_IMS_ID:
return PhoneConstants.APN_TYPE_IMS;
case DctConstants.APN_FOTA_ID:
return PhoneConstants.APN_TYPE_FOTA;
case DctConstants.APN_CBS_ID:
return PhoneConstants.APN_TYPE_CBS;
case DctConstants.APN_IA_ID:
return PhoneConstants.APN_TYPE_IA;
case DctConstants.APN_EMERGENCY_ID:
return PhoneConstants.APN_TYPE_EMERGENCY;
default:
Rlog.d(SLOG_TAG, "Unknown id (" + id + ") in apnIdToType");
return PhoneConstants.APN_TYPE_DEFAULT;
}
}
@Override
public synchronized String toString() {
// We don't print mDataConnection because its recursive.
return "{mApnType=" + mApnType + " mState=" + getState() + " mWaitingApns={" +
mRetryManager.getWaitingApns() + "}" + " mApnSetting={" + mApnSetting +
"} mReason=" + mReason + " mDataEnabled=" + mDataEnabled + " mDependencyMet=" +
mDependencyMet + "}";
}
private void log(String s) {
Rlog.d(LOG_TAG, "[ApnContext:" + mApnType + "] " + s);
}
public void dump(FileDescriptor fd, PrintWriter printWriter, String[] args) {
final IndentingPrintWriter pw = new IndentingPrintWriter(printWriter, " ");
synchronized (mRefCountLock) {
pw.println(toString());
if (mNetworkRequests.size() > 0) {
pw.println("NetworkRequests:");
pw.increaseIndent();
for (NetworkRequest nr : mNetworkRequests) {
pw.println(nr);
}
pw.decreaseIndent();
}
pw.increaseIndent();
for (LocalLog l : mLocalLogs) {
l.dump(fd, pw, args);
}
if (mHistoryLogs.size() > 0) pw.println("Historical Logs:");
for (LocalLog l : mHistoryLogs) {
l.dump(fd, pw, args);
}
pw.decreaseIndent();
pw.println("mRetryManager={" + mRetryManager.toString() + "}");
}
}
}