| /* |
| * 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.Context; |
| import android.content.res.Resources; |
| import android.net.NetworkConfig; |
| 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.util.IndentingPrintWriter; |
| |
| import java.io.FileDescriptor; |
| import java.io.PrintWriter; |
| import java.util.ArrayList; |
| import java.util.concurrent.atomic.AtomicBoolean; |
| import java.util.concurrent.atomic.AtomicInteger; |
| |
| /** |
| * Maintain the Apn context |
| */ |
| public class ApnContext { |
| |
| public final String LOG_TAG; |
| |
| protected static final boolean DBG = false; |
| |
| private final Context mContext; |
| |
| private final String mApnType; |
| |
| private DctConstants.State mState; |
| |
| private ArrayList<ApnSetting> mWaitingApns = null; |
| |
| /** |
| * Used to check if conditions (new RAT) are resulting in a new list which warrants a retry. |
| * Set in the last trySetupData call. |
| */ |
| private ArrayList<ApnSetting> mOriginalWaitingApns = null; |
| |
| public final int priority; |
| |
| /** A zero indicates that all waiting APNs had a permanent error */ |
| private AtomicInteger mWaitingApnsPermanentFailureCountDown; |
| |
| 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 DcTrackerBase 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); |
| |
| public ApnContext(Context context, String apnType, String logTag, NetworkConfig config, |
| DcTrackerBase tracker) { |
| mContext = context; |
| mApnType = apnType; |
| mState = DctConstants.State.IDLE; |
| setReason(Phone.REASON_DATA_ENABLED); |
| mDataEnabled = new AtomicBoolean(false); |
| mDependencyMet = new AtomicBoolean(config.dependencyMet); |
| mWaitingApnsPermanentFailureCountDown = new AtomicInteger(0); |
| priority = config.priority; |
| LOG_TAG = logTag; |
| mDcTracker = tracker; |
| } |
| |
| public String getApnType() { |
| return mApnType; |
| } |
| |
| public synchronized DcAsyncChannel getDcAc() { |
| return mDcAc; |
| } |
| |
| public synchronized void setDataConnectionAc(DcAsyncChannel dcac) { |
| if (DBG) { |
| log("setDataConnectionAc: old dcac=" + mDcAc + " new dcac=" + dcac |
| + " this=" + this); |
| } |
| mDcAc = dcac; |
| } |
| |
| public synchronized void releaseDataConnection(String reason) { |
| if (mDcAc != null) { |
| mDcAc.tearDown(this, reason, null); |
| mDcAc = null; |
| } |
| setState(DctConstants.State.IDLE); |
| } |
| |
| public synchronized PendingIntent getReconnectIntent() { |
| return mReconnectAlarmIntent; |
| } |
| |
| public synchronized void setReconnectIntent(PendingIntent intent) { |
| mReconnectAlarmIntent = intent; |
| } |
| |
| public synchronized ApnSetting getApnSetting() { |
| if (DBG) log("getApnSetting: apnSetting=" + mApnSetting); |
| return mApnSetting; |
| } |
| |
| public synchronized void setApnSetting(ApnSetting apnSetting) { |
| if (DBG) log("setApnSetting: apnSetting=" + apnSetting); |
| mApnSetting = apnSetting; |
| } |
| |
| public synchronized void setWaitingApns(ArrayList<ApnSetting> waitingApns) { |
| mWaitingApns = waitingApns; |
| mOriginalWaitingApns = new ArrayList<ApnSetting>(waitingApns); |
| mWaitingApnsPermanentFailureCountDown.set(mWaitingApns.size()); |
| } |
| |
| public int getWaitingApnsPermFailCount() { |
| return mWaitingApnsPermanentFailureCountDown.get(); |
| } |
| |
| public void decWaitingApnsPermFailCount() { |
| mWaitingApnsPermanentFailureCountDown.decrementAndGet(); |
| } |
| |
| public synchronized ApnSetting getNextWaitingApn() { |
| ArrayList<ApnSetting> list = mWaitingApns; |
| ApnSetting apn = null; |
| |
| if (list != null) { |
| if (!list.isEmpty()) { |
| apn = list.get(0); |
| } |
| } |
| return apn; |
| } |
| |
| public synchronized void removeWaitingApn(ApnSetting apn) { |
| if (mWaitingApns != null) { |
| mWaitingApns.remove(apn); |
| } |
| } |
| |
| public synchronized ArrayList<ApnSetting> getOriginalWaitingApns() { |
| return mOriginalWaitingApns; |
| } |
| |
| public synchronized ArrayList<ApnSetting> getWaitingApns() { |
| return mWaitingApns; |
| } |
| |
| public synchronized void setConcurrentVoiceAndDataAllowed(boolean allowed) { |
| mConcurrentVoiceAndDataAllowed = allowed; |
| } |
| |
| public synchronized boolean isConcurrentVoiceAndDataAllowed() { |
| return mConcurrentVoiceAndDataAllowed; |
| } |
| |
| public synchronized void setState(DctConstants.State s) { |
| if (DBG) { |
| log("setState: " + s + ", previous state:" + mState); |
| } |
| |
| mState = s; |
| |
| if (mState == DctConstants.State.FAILED) { |
| if (mWaitingApns != null) { |
| mWaitingApns.clear(); // when teardown the connection and set to IDLE |
| } |
| } |
| } |
| |
| public synchronized DctConstants.State getState() { |
| return mState; |
| } |
| |
| public boolean isDisconnected() { |
| DctConstants.State currentState = getState(); |
| return ((currentState == DctConstants.State.IDLE) || |
| currentState == DctConstants.State.FAILED); |
| } |
| |
| public synchronized void setReason(String reason) { |
| if (DBG) { |
| log("set reason as " + reason + ",current state " + mState); |
| } |
| mReason = reason; |
| } |
| |
| public synchronized String getReason() { |
| return mReason; |
| } |
| |
| public boolean isReady() { |
| return mDataEnabled.get() && mDependencyMet.get(); |
| } |
| |
| public boolean isConnectable() { |
| return isReady() && ((mState == DctConstants.State.IDLE) |
| || (mState == DctConstants.State.SCANNING) |
| || (mState == DctConstants.State.RETRYING) |
| || (mState == DctConstants.State.FAILED)); |
| } |
| |
| public boolean isConnectedOrConnecting() { |
| return isReady() && ((mState == DctConstants.State.CONNECTED) |
| || (mState == DctConstants.State.CONNECTING) |
| || (mState == DctConstants.State.SCANNING) |
| || (mState == DctConstants.State.RETRYING)); |
| } |
| |
| public void setEnabled(boolean enabled) { |
| if (DBG) { |
| log("set enabled as " + enabled + ", current state is " + mDataEnabled.get()); |
| } |
| mDataEnabled.set(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 = mContext.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<LocalLog>(); |
| |
| public void requestLog(String str) { |
| synchronized (mRefCountLock) { |
| for (LocalLog l : mLocalLogs) { |
| l.log(str); |
| } |
| } |
| } |
| |
| public void incRefCount(LocalLog log) { |
| synchronized (mRefCountLock) { |
| if (mRefCount == 0) { |
| // we wanted to leave the last in so it could actually capture the tear down |
| // of the network |
| requestLog("clearing log with size=" + mLocalLogs.size()); |
| mLocalLogs.clear(); |
| } |
| if (mLocalLogs.contains(log)) { |
| log.log("ApnContext.incRefCount has duplicate add - " + mRefCount); |
| } else { |
| mLocalLogs.add(log); |
| log.log("ApnContext.incRefCount - " + mRefCount); |
| } |
| if (mRefCount++ == 0) { |
| mDcTracker.setEnabled(mDcTracker.apnTypeToId(mApnType), true); |
| } |
| } |
| } |
| |
| public void decRefCount(LocalLog log) { |
| synchronized (mRefCountLock) { |
| // leave the last log alive to capture the actual tear down |
| if (mRefCount != 1) { |
| if (mLocalLogs.remove(log)) { |
| log.log("ApnContext.decRefCount - " + mRefCount); |
| } else { |
| log.log("ApnContext.decRefCount didn't find log - " + mRefCount); |
| } |
| } else { |
| log.log("ApnContext.decRefCount - 1"); |
| } |
| if (mRefCount-- == 1) { |
| mDcTracker.setEnabled(mDcTracker.apnTypeToId(mApnType), false); |
| } |
| if (mRefCount < 0) { |
| log.log("ApnContext.decRefCount went to " + mRefCount); |
| mRefCount = 0; |
| } |
| } |
| } |
| |
| private final SparseIntArray mRetriesLeftPerErrorCode = new SparseIntArray(); |
| |
| public void resetErrorCodeRetries() { |
| requestLog("ApnContext.resetErrorCodeRetries"); |
| if (DBG) log("ApnContext.resetErrorCodeRetries"); |
| |
| String[] config = Resources.getSystem().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(); |
| } |
| |
| @Override |
| public synchronized String toString() { |
| // We don't print mDataConnection because its recursive. |
| return "{mApnType=" + mApnType + " mState=" + getState() + " mWaitingApns={" + |
| mWaitingApns + "} mWaitingApnsPermanentFailureCountDown=" + |
| mWaitingApnsPermanentFailureCountDown + " 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 (mRefCount > 0) { |
| pw.increaseIndent(); |
| for (LocalLog l : mLocalLogs) { |
| l.dump(fd, pw, args); |
| } |
| pw.decreaseIndent(); |
| } |
| } |
| } |
| } |