blob: b3277e5ce22f300d495447c0c54c659c5eacd435 [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.cdma;
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.net.TrafficStats;
import android.os.AsyncResult;
import android.os.Message;
import android.os.SystemClock;
import android.os.SystemProperties;
import android.telephony.ServiceState;
import android.telephony.TelephonyManager;
import android.telephony.cdma.CdmaCellLocation;
import android.text.TextUtils;
import android.util.EventLog;
import android.util.Log;
import com.android.internal.telephony.ApnSetting;
import com.android.internal.telephony.CommandsInterface;
import com.android.internal.telephony.DataCallState;
import com.android.internal.telephony.DataConnection.FailCause;
import com.android.internal.telephony.DataConnection;
import com.android.internal.telephony.DataConnectionAc;
import com.android.internal.telephony.DataConnectionTracker;
import com.android.internal.telephony.EventLogTags;
import com.android.internal.telephony.RetryManager;
import com.android.internal.telephony.RILConstants;
import com.android.internal.telephony.Phone;
import com.android.internal.util.AsyncChannel;
import java.util.ArrayList;
/**
* {@hide}
*/
public final class CdmaDataConnectionTracker extends DataConnectionTracker {
protected final String LOG_TAG = "CDMA";
private CDMAPhone mCdmaPhone;
/** The DataConnection being setup */
private CdmaDataConnection mPendingDataConnection;
private boolean mPendingRestartRadio = false;
private static final int TIME_DELAYED_TO_RESTART_RADIO =
SystemProperties.getInt("ro.cdma.timetoradiorestart", 60000);
/**
* Pool size of CdmaDataConnection objects.
*/
private static final int DATA_CONNECTION_POOL_SIZE = 1;
private static final String INTENT_RECONNECT_ALARM =
"com.android.internal.telephony.cdma-reconnect";
private static final String INTENT_DATA_STALL_ALARM =
"com.android.internal.telephony.cdma-data-stall";
/**
* Constants for the data connection activity:
* physical link down/up
*/
private static final int DATA_CONNECTION_ACTIVE_PH_LINK_INACTIVE = 0;
private static final int DATA_CONNECTION_ACTIVE_PH_LINK_DOWN = 1;
private static final int DATA_CONNECTION_ACTIVE_PH_LINK_UP = 2;
private static final String[] mSupportedApnTypes = {
Phone.APN_TYPE_DEFAULT,
Phone.APN_TYPE_MMS,
Phone.APN_TYPE_DUN,
Phone.APN_TYPE_HIPRI };
private static final String[] mDefaultApnTypes = {
Phone.APN_TYPE_DEFAULT,
Phone.APN_TYPE_MMS,
Phone.APN_TYPE_HIPRI };
private String[] mDunApnTypes = {
Phone.APN_TYPE_DUN };
private static final int mDefaultApnId = DataConnectionTracker.APN_DEFAULT_ID;
/* Constructor */
CdmaDataConnectionTracker(CDMAPhone p) {
super(p);
mCdmaPhone = p;
p.mCM.registerForAvailable (this, EVENT_RADIO_AVAILABLE, null);
p.mCM.registerForOffOrNotAvailable(this, EVENT_RADIO_OFF_OR_NOT_AVAILABLE, null);
p.mIccRecords.registerForRecordsLoaded(this, EVENT_RECORDS_LOADED, null);
p.mCM.registerForNVReady(this, EVENT_NV_READY, null);
p.mCM.registerForDataNetworkStateChanged (this, EVENT_DATA_STATE_CHANGED, null);
p.mCT.registerForVoiceCallEnded (this, EVENT_VOICE_CALL_ENDED, null);
p.mCT.registerForVoiceCallStarted (this, EVENT_VOICE_CALL_STARTED, null);
p.mSST.registerForDataConnectionAttached(this, EVENT_TRY_SETUP_DATA, null);
p.mSST.registerForDataConnectionDetached(this, EVENT_CDMA_DATA_DETACHED, null);
p.mSST.registerForRoamingOn(this, EVENT_ROAMING_ON, null);
p.mSST.registerForRoamingOff(this, EVENT_ROAMING_OFF, null);
p.mCM.registerForCdmaOtaProvision(this, EVENT_CDMA_OTA_PROVISION, null);
mDataConnectionTracker = this;
createAllDataConnectionList();
broadcastMessenger();
Context c = mCdmaPhone.getContext();
String[] t = c.getResources().getStringArray(
com.android.internal.R.array.config_cdma_dun_supported_types);
if (t != null && t.length > 0) {
ArrayList<String> temp = new ArrayList<String>();
for(int i=0; i< t.length; i++) {
if (!Phone.APN_TYPE_DUN.equalsIgnoreCase(t[i])) {
temp.add(t[i]);
}
}
temp.add(0, Phone.APN_TYPE_DUN);
mDunApnTypes = temp.toArray(t);
}
}
@Override
public void dispose() {
cleanUpConnection(false, null);
super.dispose();
// Unregister from all events
mPhone.mCM.unregisterForAvailable(this);
mPhone.mCM.unregisterForOffOrNotAvailable(this);
mCdmaPhone.mIccRecords.unregisterForRecordsLoaded(this);
mPhone.mCM.unregisterForNVReady(this);
mPhone.mCM.unregisterForDataNetworkStateChanged(this);
mCdmaPhone.mCT.unregisterForVoiceCallEnded(this);
mCdmaPhone.mCT.unregisterForVoiceCallStarted(this);
mCdmaPhone.mSST.unregisterForDataConnectionAttached(this);
mCdmaPhone.mSST.unregisterForDataConnectionDetached(this);
mCdmaPhone.mSST.unregisterForRoamingOn(this);
mCdmaPhone.mSST.unregisterForRoamingOff(this);
mPhone.mCM.unregisterForCdmaOtaProvision(this);
destroyAllDataConnectionList();
}
@Override
protected void finalize() {
if(DBG) log("CdmaDataConnectionTracker finalized");
}
@Override
protected String getActionIntentReconnectAlarm() {
return INTENT_RECONNECT_ALARM;
}
@Override
protected String getActionIntentDataStallAlarm() {
return INTENT_DATA_STALL_ALARM;
}
@Override
protected void restartDataStallAlarm() {}
@Override
protected void setState(State s) {
if (DBG) log ("setState: " + s);
if (mState != s) {
EventLog.writeEvent(EventLogTags.CDMA_DATA_STATE_CHANGE,
mState.toString(), s.toString());
mState = s;
}
}
@Override
public synchronized State getState(String apnType) {
return mState;
}
@Override
protected boolean isApnTypeAvailable(String type) {
for (String s : mSupportedApnTypes) {
if (TextUtils.equals(type, s)) {
return true;
}
}
return false;
}
@Override
protected boolean isDataAllowed() {
final boolean internalDataEnabled;
synchronized (mDataEnabledLock) {
internalDataEnabled = mInternalDataEnabled;
}
int psState = mCdmaPhone.mSST.getCurrentDataConnectionState();
boolean roaming = (mPhone.getServiceState().getRoaming() && !getDataOnRoamingEnabled());
boolean desiredPowerState = mCdmaPhone.mSST.getDesiredPowerState();
boolean allowed =
(psState == ServiceState.STATE_IN_SERVICE ||
mAutoAttachOnCreation) &&
(mPhone.mCM.getNvState() == CommandsInterface.RadioState.NV_READY ||
mCdmaPhone.mIccRecords.getRecordsLoaded()) &&
(mCdmaPhone.mSST.isConcurrentVoiceAndDataAllowed() ||
mPhone.getState() == Phone.State.IDLE) &&
!roaming &&
internalDataEnabled &&
desiredPowerState &&
!mPendingRestartRadio &&
!mCdmaPhone.needsOtaServiceProvisioning();
if (!allowed && DBG) {
String reason = "";
if (!((psState == ServiceState.STATE_IN_SERVICE) || mAutoAttachOnCreation)) {
reason += " - psState= " + psState;
}
if (!(mPhone.mCM.getNvState() == CommandsInterface.RadioState.NV_READY ||
mCdmaPhone.mIccRecords.getRecordsLoaded())) {
reason += " - radioState= " + mPhone.mCM.getNvState() + " - RUIM not loaded";
}
if (!(mCdmaPhone.mSST.isConcurrentVoiceAndDataAllowed() ||
mPhone.getState() == Phone.State.IDLE)) {
reason += " - concurrentVoiceAndData not allowed and state= " + mPhone.getState();
}
if (roaming) reason += " - Roaming";
if (!internalDataEnabled) reason += " - mInternalDataEnabled= false";
if (!desiredPowerState) reason += " - desiredPowerState= false";
if (mPendingRestartRadio) reason += " - mPendingRestartRadio= true";
if (mCdmaPhone.needsOtaServiceProvisioning()) reason += " - needs Provisioning";
log("Data not allowed due to" + reason);
}
return allowed;
}
@Override
protected boolean isDataPossible(String apnType) {
boolean possible = isDataAllowed() && !(getAnyDataEnabled() &&
(mState == State.FAILED || mState == State.IDLE));
if (!possible && DBG && isDataAllowed()) {
log("Data not possible. No coverage: dataState = " + mState);
}
return possible;
}
private boolean trySetupData(String reason) {
if (DBG) log("***trySetupData due to " + (reason == null ? "(unspecified)" : reason));
if (mPhone.getSimulatedRadioControl() != null) {
// Assume data is connected on the simulator
// FIXME this can be improved
setState(State.CONNECTED);
notifyDataConnection(reason);
notifyOffApnsOfAvailability(reason);
log("(fix?) We're on the simulator; assuming data is connected");
return true;
}
int psState = mCdmaPhone.mSST.getCurrentDataConnectionState();
boolean roaming = mPhone.getServiceState().getRoaming();
boolean desiredPowerState = mCdmaPhone.mSST.getDesiredPowerState();
if ((mState == State.IDLE || mState == State.SCANNING) &&
isDataAllowed() && getAnyDataEnabled() && !isEmergency()) {
boolean retValue = setupData(reason);
notifyOffApnsOfAvailability(reason);
return retValue;
} else {
notifyOffApnsOfAvailability(reason);
return false;
}
}
/**
* Cleanup the CDMA data connection (only one is supported)
*
* @param tearDown true if the underlying DataConnection should be disconnected.
* @param reason for the clean up.
*/
private void cleanUpConnection(boolean tearDown, String reason) {
if (DBG) log("cleanUpConnection: reason: " + reason);
// Clear the reconnect alarm, if set.
if (mReconnectIntent != null) {
AlarmManager am =
(AlarmManager) mPhone.getContext().getSystemService(Context.ALARM_SERVICE);
am.cancel(mReconnectIntent);
mReconnectIntent = null;
}
setState(State.DISCONNECTING);
notifyOffApnsOfAvailability(reason);
boolean notificationDeferred = false;
for (DataConnection conn : mDataConnections.values()) {
if(conn != null) {
DataConnectionAc dcac =
mDataConnectionAsyncChannels.get(conn.getDataConnectionId());
if (tearDown) {
if (DBG) log("cleanUpConnection: teardown, call conn.disconnect");
conn.tearDown(reason, obtainMessage(EVENT_DISCONNECT_DONE,
conn.getDataConnectionId(), 0, reason));
notificationDeferred = true;
} else {
if (DBG) log("cleanUpConnection: !tearDown, call conn.resetSynchronously");
if (dcac != null) {
dcac.resetSync();
}
notificationDeferred = false;
}
}
}
stopNetStatPoll();
if (!notificationDeferred) {
if (DBG) log("cleanupConnection: !notificationDeferred");
gotoIdleAndNotifyDataConnection(reason);
}
}
private CdmaDataConnection findFreeDataConnection() {
for (DataConnectionAc dcac : mDataConnectionAsyncChannels.values()) {
if (dcac.isInactiveSync()) {
log("found free GsmDataConnection");
return (CdmaDataConnection) dcac.dataConnection;
}
}
log("NO free CdmaDataConnection");
return null;
}
private boolean setupData(String reason) {
CdmaDataConnection conn = findFreeDataConnection();
if (conn == null) {
if (DBG) log("setupData: No free CdmaDataConnection found!");
return false;
}
/** TODO: We probably want the connection being setup to a parameter passed around */
mPendingDataConnection = conn;
String[] types;
int apnId;
if (mRequestedApnType.equals(Phone.APN_TYPE_DUN)) {
types = mDunApnTypes;
apnId = DataConnectionTracker.APN_DUN_ID;
} else {
types = mDefaultApnTypes;
apnId = mDefaultApnId;
}
mActiveApn = new ApnSetting(apnId, "", "", "", "", "", "", "", "", "",
"", 0, types, "IP", "IP", true, 0);
if (DBG) log("call conn.bringUp mActiveApn=" + mActiveApn);
Message msg = obtainMessage();
msg.what = EVENT_DATA_SETUP_COMPLETE;
msg.obj = reason;
conn.bringUp(msg, mActiveApn);
setState(State.INITING);
notifyDataConnection(reason);
return true;
}
private void notifyDefaultData(String reason) {
setState(State.CONNECTED);
notifyDataConnection(reason);
startNetStatPoll();
mDataConnections.get(0).resetRetryCount();
}
private void resetPollStats() {
mTxPkts = -1;
mRxPkts = -1;
mSentSinceLastRecv = 0;
mNetStatPollPeriod = POLL_NETSTAT_MILLIS;
mNoRecvPollCount = 0;
}
@Override
protected void startNetStatPoll() {
if (mState == State.CONNECTED && mNetStatPollEnabled == false) {
log("[DataConnection] Start poll NetStat");
resetPollStats();
mNetStatPollEnabled = true;
mPollNetStat.run();
}
}
@Override
protected void stopNetStatPoll() {
mNetStatPollEnabled = false;
removeCallbacks(mPollNetStat);
log("[DataConnection] Stop poll NetStat");
}
@Override
protected void restartRadio() {
if (DBG) log("Cleanup connection and wait " +
(TIME_DELAYED_TO_RESTART_RADIO / 1000) + "s to restart radio");
cleanUpAllConnections(null);
sendEmptyMessageDelayed(EVENT_RESTART_RADIO, TIME_DELAYED_TO_RESTART_RADIO);
mPendingRestartRadio = true;
}
private Runnable mPollNetStat = new Runnable() {
public void run() {
long sent, received;
long preTxPkts = -1, preRxPkts = -1;
Activity newActivity;
preTxPkts = mTxPkts;
preRxPkts = mRxPkts;
mTxPkts = TrafficStats.getMobileTxPackets();
mRxPkts = TrafficStats.getMobileRxPackets();
//log("rx " + String.valueOf(rxPkts) + " tx " + String.valueOf(txPkts));
if (mNetStatPollEnabled && (preTxPkts > 0 || preRxPkts > 0)) {
sent = mTxPkts - preTxPkts;
received = mRxPkts - preRxPkts;
if ( sent > 0 && received > 0 ) {
mSentSinceLastRecv = 0;
newActivity = Activity.DATAINANDOUT;
} else if (sent > 0 && received == 0) {
if (mPhone.getState() == Phone.State.IDLE) {
mSentSinceLastRecv += sent;
} else {
mSentSinceLastRecv = 0;
}
newActivity = Activity.DATAOUT;
} else if (sent == 0 && received > 0) {
mSentSinceLastRecv = 0;
newActivity = Activity.DATAIN;
} else if (sent == 0 && received == 0) {
newActivity = (mActivity == Activity.DORMANT) ? mActivity : Activity.NONE;
} else {
mSentSinceLastRecv = 0;
newActivity = (mActivity == Activity.DORMANT) ? mActivity : Activity.NONE;
}
if (mActivity != newActivity && mIsScreenOn) {
mActivity = newActivity;
mPhone.notifyDataActivity();
}
}
if (mSentSinceLastRecv >= NUMBER_SENT_PACKETS_OF_HANG) {
// Packets sent without ack exceeded threshold.
if (mNoRecvPollCount == 0) {
EventLog.writeEvent(
EventLogTags.PDP_RADIO_RESET_COUNTDOWN_TRIGGERED,
mSentSinceLastRecv);
}
if (mNoRecvPollCount < NO_RECV_POLL_LIMIT) {
mNoRecvPollCount++;
// Slow down the poll interval to let things happen
mNetStatPollPeriod = POLL_NETSTAT_SLOW_MILLIS;
} else {
if (DBG) log("Sent " + String.valueOf(mSentSinceLastRecv) +
" pkts since last received");
// We've exceeded the threshold. Restart the radio.
mNetStatPollEnabled = false;
stopNetStatPoll();
restartRadio();
EventLog.writeEvent(EventLogTags.PDP_RADIO_RESET, NO_RECV_POLL_LIMIT);
}
} else {
mNoRecvPollCount = 0;
mNetStatPollPeriod = POLL_NETSTAT_MILLIS;
}
if (mNetStatPollEnabled) {
mDataConnectionTracker.postDelayed(this, mNetStatPollPeriod);
}
}
};
/**
* Returns true if the last fail cause is something that
* seems like it deserves an error notification.
* Transient errors are ignored
*/
private boolean
shouldPostNotification(FailCause cause) {
return (cause != FailCause.UNKNOWN);
}
/**
* Return true if data connection need to be setup after disconnected due to
* reason.
*
* @param reason the reason why data is disconnected
* @return true if try setup data connection is need for this reason
*/
private boolean retryAfterDisconnected(String reason) {
boolean retry = true;
if ( Phone.REASON_RADIO_TURNED_OFF.equals(reason) ) {
retry = false;
}
return retry;
}
private void reconnectAfterFail(FailCause lastFailCauseCode, String reason, int retryOverride) {
if (mState == State.FAILED) {
/**
* For now With CDMA we never try to reconnect on
* error and instead just continue to retry
* at the last time until the state is changed.
* TODO: Make this configurable?
*/
int nextReconnectDelay = retryOverride;
if (nextReconnectDelay < 0) {
nextReconnectDelay = mDataConnections.get(0).getRetryTimer();
mDataConnections.get(0).increaseRetryCount();
}
startAlarmForReconnect(nextReconnectDelay, reason);
if (!shouldPostNotification(lastFailCauseCode)) {
log("NOT Posting Data Connection Unavailable notification "
+ "-- likely transient error");
} else {
notifyNoData(lastFailCauseCode);
}
}
}
private void startAlarmForReconnect(int delay, String reason) {
log("Data Connection activate failed. Scheduling next attempt for "
+ (delay / 1000) + "s");
AlarmManager am =
(AlarmManager) mPhone.getContext().getSystemService(Context.ALARM_SERVICE);
Intent intent = new Intent(INTENT_RECONNECT_ALARM);
intent.putExtra(INTENT_RECONNECT_ALARM_EXTRA_REASON, reason);
mReconnectIntent = PendingIntent.getBroadcast(
mPhone.getContext(), 0, intent, 0);
am.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
SystemClock.elapsedRealtime() + delay, mReconnectIntent);
}
private void notifyNoData(FailCause lastFailCauseCode) {
setState(State.FAILED);
notifyOffApnsOfAvailability(null);
}
protected void gotoIdleAndNotifyDataConnection(String reason) {
if (DBG) log("gotoIdleAndNotifyDataConnection: reason=" + reason);
setState(State.IDLE);
notifyDataConnection(reason);
mActiveApn = null;
}
protected void onRecordsLoaded() {
if (mState == State.FAILED) {
cleanUpAllConnections(null);
}
sendMessage(obtainMessage(EVENT_TRY_SETUP_DATA, Phone.REASON_SIM_LOADED));
}
protected void onNVReady() {
if (mState == State.FAILED) {
cleanUpAllConnections(null);
}
sendMessage(obtainMessage(EVENT_TRY_SETUP_DATA));
}
/**
* @override com.android.internal.telephony.DataConnectionTracker
*/
@Override
protected void onEnableNewApn() {
// No mRequestedApnType check; only one connection is supported
cleanUpConnection(true, Phone.REASON_APN_SWITCHED);
}
/**
* @override com.android.internal.telephony.DataConnectionTracker
*/
@Override
protected boolean onTrySetupData(String reason) {
return trySetupData(reason);
}
/**
* @override com.android.internal.telephony.DataConnectionTracker
*/
@Override
protected void onRoamingOff() {
if (getDataOnRoamingEnabled() == false) {
notifyOffApnsOfAvailability(Phone.REASON_ROAMING_OFF);
trySetupData(Phone.REASON_ROAMING_OFF);
} else {
notifyDataConnection(Phone.REASON_ROAMING_OFF);
}
}
/**
* @override com.android.internal.telephony.DataConnectionTracker
*/
@Override
protected void onRoamingOn() {
if (getDataOnRoamingEnabled()) {
trySetupData(Phone.REASON_ROAMING_ON);
notifyDataConnection(Phone.REASON_ROAMING_ON);
} else {
if (DBG) log("Tear down data connection on roaming.");
cleanUpAllConnections(null);
notifyOffApnsOfAvailability(Phone.REASON_ROAMING_ON);
}
}
/**
* @override com.android.internal.telephony.DataConnectionTracker
*/
@Override
protected void onRadioAvailable() {
if (mPhone.getSimulatedRadioControl() != null) {
// Assume data is connected on the simulator
// FIXME this can be improved
setState(State.CONNECTED);
notifyDataConnection(null);
log("We're on the simulator; assuming data is connected");
}
notifyOffApnsOfAvailability(null);
if (mState != State.IDLE) {
cleanUpAllConnections(null);
}
}
/**
* @override com.android.internal.telephony.DataConnectionTracker
*/
@Override
protected void onRadioOffOrNotAvailable() {
mDataConnections.get(0).resetRetryCount();
if (mPhone.getSimulatedRadioControl() != null) {
// Assume data is connected on the simulator
// FIXME this can be improved
log("We're on the simulator; assuming radio off is meaningless");
} else {
if (DBG) log("Radio is off and clean up all connection");
cleanUpAllConnections(null);
}
}
/**
* @override com.android.internal.telephony.DataConnectionTracker
*/
@Override
protected void onDataSetupComplete(AsyncResult ar) {
String reason = null;
if (ar.userObj instanceof String) {
reason = (String) ar.userObj;
}
if (isDataSetupCompleteOk(ar)) {
// Everything is setup
notifyDefaultData(reason);
} else {
FailCause cause = (FailCause) (ar.result);
if(DBG) log("Data Connection setup failed " + cause);
// No try for permanent failure
if (cause.isPermanentFail()) {
notifyNoData(cause);
return;
}
int retryOverride = -1;
if (ar.exception instanceof DataConnection.CallSetupException) {
retryOverride =
((DataConnection.CallSetupException)ar.exception).getRetryOverride();
}
if (retryOverride == RILConstants.MAX_INT) {
if (DBG) log("No retry is suggested.");
} else {
startDelayedRetry(cause, reason, retryOverride);
}
}
}
/**
* Called when EVENT_DISCONNECT_DONE is received.
*/
@Override
protected void onDisconnectDone(int connId, AsyncResult ar) {
if(DBG) log("EVENT_DISCONNECT_DONE connId=" + connId);
String reason = null;
if (ar.userObj instanceof String) {
reason = (String) ar.userObj;
}
setState(State.IDLE);
// Since the pending request to turn off or restart radio will be processed here,
// remove the pending event to restart radio from the message queue.
if (mPendingRestartRadio) removeMessages(EVENT_RESTART_RADIO);
// Process the pending request to turn off radio in ServiceStateTracker first.
// If radio is turned off in ServiceStateTracker, ignore the pending event to restart radio.
CdmaServiceStateTracker ssTracker = mCdmaPhone.mSST;
if (ssTracker.processPendingRadioPowerOffAfterDataOff()) {
mPendingRestartRadio = false;
} else {
onRestartRadio();
}
notifyDataConnection(reason);
mActiveApn = null;
if (retryAfterDisconnected(reason)) {
// Wait a bit before trying, so we're not tying up RIL command channel.
startAlarmForReconnect(APN_DELAY_MILLIS, reason);
}
}
/**
* @override com.android.internal.telephony.DataConnectionTracker
*/
@Override
protected void onVoiceCallStarted() {
if (mState == State.CONNECTED && !mCdmaPhone.mSST.isConcurrentVoiceAndDataAllowed()) {
stopNetStatPoll();
notifyDataConnection(Phone.REASON_VOICE_CALL_STARTED);
notifyOffApnsOfAvailability(Phone.REASON_VOICE_CALL_STARTED);
}
}
/**
* @override com.android.internal.telephony.DataConnectionTracker
*/
@Override
protected void onVoiceCallEnded() {
if (mState == State.CONNECTED) {
if (!mCdmaPhone.mSST.isConcurrentVoiceAndDataAllowed()) {
startNetStatPoll();
notifyDataConnection(Phone.REASON_VOICE_CALL_ENDED);
} else {
// clean slate after call end.
resetPollStats();
}
notifyOffApnsOfAvailability(Phone.REASON_VOICE_CALL_ENDED);
} else {
mDataConnections.get(0).resetRetryCount();
// in case data setup was attempted when we were on a voice call
trySetupData(Phone.REASON_VOICE_CALL_ENDED);
}
}
@Override
protected void onCleanUpConnection(boolean tearDown, int apnId, String reason) {
// No apnId check; only one connection is supported
cleanUpConnection(tearDown, reason);
}
@Override
protected void onCleanUpAllConnections(String cause) {
// Only one CDMA connection is supported
cleanUpConnection(true, cause);
}
private void createAllDataConnectionList() {
CdmaDataConnection dataConn;
String retryConfig = SystemProperties.get("ro.cdma.data_retry_config");
for (int i = 0; i < DATA_CONNECTION_POOL_SIZE; i++) {
RetryManager rm = new RetryManager();
if (!rm.configure(retryConfig)) {
if (!rm.configure(DEFAULT_DATA_RETRY_CONFIG)) {
// Should never happen, log an error and default to a simple linear sequence.
log("Could not configure using DEFAULT_DATA_RETRY_CONFIG="
+ DEFAULT_DATA_RETRY_CONFIG);
rm.configure(20, 2000, 1000);
}
}
int id = mUniqueIdGenerator.getAndIncrement();
dataConn = CdmaDataConnection.makeDataConnection(mCdmaPhone, id, rm);
mDataConnections.put(id, dataConn);
DataConnectionAc dcac = new DataConnectionAc(dataConn, LOG_TAG);
int status = dcac.fullyConnectSync(mPhone.getContext(), this, dataConn.getHandler());
if (status == AsyncChannel.STATUS_SUCCESSFUL) {
log("Fully connected");
mDataConnectionAsyncChannels.put(dcac.dataConnection.getDataConnectionId(), dcac);
} else {
log("Could not connect to dcac.dataConnection=" + dcac.dataConnection +
" status=" + status);
}
}
}
private void destroyAllDataConnectionList() {
if(mDataConnections != null) {
mDataConnections.clear();
}
}
private void onCdmaDataDetached() {
if (mState == State.CONNECTED) {
startNetStatPoll();
notifyDataConnection(Phone.REASON_CDMA_DATA_DETACHED);
} else {
if (mState == State.FAILED) {
cleanUpConnection(false, Phone.REASON_CDMA_DATA_DETACHED);
mDataConnections.get(0).resetRetryCount();
CdmaCellLocation loc = (CdmaCellLocation)(mPhone.getCellLocation());
EventLog.writeEvent(EventLogTags.CDMA_DATA_SETUP_FAILED,
loc != null ? loc.getBaseStationId() : -1,
TelephonyManager.getDefault().getNetworkType());
}
trySetupData(Phone.REASON_CDMA_DATA_DETACHED);
}
}
private void onCdmaOtaProvision(AsyncResult ar) {
if (ar.exception != null) {
int [] otaPrivision = (int [])ar.result;
if ((otaPrivision != null) && (otaPrivision.length > 1)) {
switch (otaPrivision[0]) {
case Phone.CDMA_OTA_PROVISION_STATUS_COMMITTED:
case Phone.CDMA_OTA_PROVISION_STATUS_OTAPA_STOPPED:
mDataConnections.get(0).resetRetryCount();
break;
default:
break;
}
}
}
}
private void onRestartRadio() {
if (mPendingRestartRadio) {
log("************TURN OFF RADIO**************");
mPhone.mCM.setRadioPower(false, null);
/* Note: no need to call setRadioPower(true). Assuming the desired
* radio power state is still ON (as tracked by ServiceStateTracker),
* ServiceStateTracker will call setRadioPower when it receives the
* RADIO_STATE_CHANGED notification for the power off. And if the
* desired power state has changed in the interim, we don't want to
* override it with an unconditional power on.
*/
mPendingRestartRadio = false;
}
}
private void writeEventLogCdmaDataDrop() {
CdmaCellLocation loc = (CdmaCellLocation)(mPhone.getCellLocation());
EventLog.writeEvent(EventLogTags.CDMA_DATA_DROP,
loc != null ? loc.getBaseStationId() : -1,
TelephonyManager.getDefault().getNetworkType());
}
protected void onDataStateChanged(AsyncResult ar) {
ArrayList<DataCallState> dataCallStates = (ArrayList<DataCallState>)(ar.result);
if (ar.exception != null) {
// This is probably "radio not available" or something
// of that sort. If so, the whole connection is going
// to come down soon anyway
return;
}
if (mState == State.CONNECTED) {
boolean isActiveOrDormantConnectionPresent = false;
int connectionState = DATA_CONNECTION_ACTIVE_PH_LINK_INACTIVE;
// Check for an active or dormant connection element in
// the DATA_CALL_LIST array
for (int index = 0; index < dataCallStates.size(); index++) {
connectionState = dataCallStates.get(index).active;
if (connectionState != DATA_CONNECTION_ACTIVE_PH_LINK_INACTIVE) {
isActiveOrDormantConnectionPresent = true;
break;
}
}
if (!isActiveOrDormantConnectionPresent) {
// No active or dormant connection
log("onDataStateChanged: No active connection"
+ "state is CONNECTED, disconnecting/cleanup");
writeEventLogCdmaDataDrop();
cleanUpConnection(true, null);
return;
}
switch (connectionState) {
case DATA_CONNECTION_ACTIVE_PH_LINK_UP:
log("onDataStateChanged: active=LINK_ACTIVE && CONNECTED, ignore");
mActivity = Activity.NONE;
mPhone.notifyDataActivity();
startNetStatPoll();
break;
case DATA_CONNECTION_ACTIVE_PH_LINK_DOWN:
log("onDataStateChanged active=LINK_DOWN && CONNECTED, dormant");
mActivity = Activity.DORMANT;
mPhone.notifyDataActivity();
stopNetStatPoll();
break;
default:
log("onDataStateChanged: IGNORE unexpected DataCallState.active="
+ connectionState);
}
} else {
// TODO: Do we need to do anything?
log("onDataStateChanged: not connected, state=" + mState + " ignoring");
}
}
private void startDelayedRetry(FailCause cause, String reason, int retryOverride) {
notifyNoData(cause);
reconnectAfterFail(cause, reason, retryOverride);
}
@Override
public void handleMessage (Message msg) {
if (DBG) log("CdmaDCT handleMessage msg=" + msg);
if (!mPhone.mIsTheCurrentActivePhone || mIsDisposed) {
log("Ignore CDMA msgs since CDMA phone is inactive");
return;
}
switch (msg.what) {
case EVENT_RECORDS_LOADED:
onRecordsLoaded();
break;
case EVENT_NV_READY:
onNVReady();
break;
case EVENT_CDMA_DATA_DETACHED:
onCdmaDataDetached();
break;
case EVENT_DATA_STATE_CHANGED:
onDataStateChanged((AsyncResult) msg.obj);
break;
case EVENT_CDMA_OTA_PROVISION:
onCdmaOtaProvision((AsyncResult) msg.obj);
break;
case EVENT_RESTART_RADIO:
if (DBG) log("EVENT_RESTART_RADIO");
onRestartRadio();
break;
default:
// handle the message in the super class DataConnectionTracker
super.handleMessage(msg);
break;
}
}
@Override
public boolean isDisconnected() {
return ((mState == State.IDLE) || (mState == State.FAILED));
}
@Override
protected void log(String s) {
Log.d(LOG_TAG, "[CdmaDCT] " + s);
}
@Override
protected void loge(String s) {
Log.e(LOG_TAG, "[CdmaDCT] " + s);
}
}