blob: 217e1e86b0931253aa20ff0d229a65f1ef9d7598 [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.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.net.ConnectivityManager;
import android.net.IConnectivityManager;
import android.net.NetworkInfo;
import android.net.TrafficStats;
import android.net.wifi.WifiManager;
import android.os.AsyncResult;
import android.os.Message;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemClock;
import android.os.SystemProperties;
import android.preference.PreferenceManager;
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.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.DataConnectionTracker;
import com.android.internal.telephony.EventLogTags;
import com.android.internal.telephony.gsm.ApnSetting;
import com.android.internal.telephony.Phone;
import com.android.internal.telephony.RetryManager;
import com.android.internal.telephony.ServiceStateTracker;
import java.util.ArrayList;
/**
* {@hide}
*/
public final class CdmaDataConnectionTracker extends DataConnectionTracker {
protected final String LOG_TAG = "CDMA";
private CDMAPhone mCdmaPhone;
// Indicates baseband will not auto-attach
private boolean noAutoAttach = false;
private boolean mIsScreenOn = true;
//useful for debugging
boolean failNextConnect = false;
/**
* dataConnectionList holds all the Data connection
*/
private ArrayList<DataConnection> dataConnectionList;
/** Currently active CdmaDataConnection */
private CdmaDataConnection mActiveDataConnection;
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 int POLL_CONNECTION_MILLIS = 5 * 1000;
private static final String INTENT_RECONNECT_ALARM =
"com.android.internal.telephony.cdma-reconnect";
private static final String INTENT_RECONNECT_ALARM_EXTRA_REASON = "reason";
/**
* 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 };
// if we have no active Apn this is null
protected ApnSetting mActiveApn;
// Possibly promoate to base class, the only difference is
// the INTENT_RECONNECT_ALARM action is a different string.
// Do consider technology changes if it is promoted.
BroadcastReceiver mIntentReceiver = new BroadcastReceiver ()
{
@Override
public void onReceive(Context context, Intent intent)
{
String action = intent.getAction();
if (action.equals(Intent.ACTION_SCREEN_ON)) {
mIsScreenOn = true;
stopNetStatPoll();
startNetStatPoll();
} else if (action.equals(Intent.ACTION_SCREEN_OFF)) {
mIsScreenOn = false;
stopNetStatPoll();
startNetStatPoll();
} else if (action.equals((INTENT_RECONNECT_ALARM))) {
Log.d(LOG_TAG, "Data reconnect alarm. Previous state was " + state);
String reason = intent.getStringExtra(INTENT_RECONNECT_ALARM_EXTRA_REASON);
if (state == State.FAILED) {
cleanUpConnection(false, reason);
}
trySetupData(reason);
} else if (action.equals(WifiManager.NETWORK_STATE_CHANGED_ACTION)) {
final android.net.NetworkInfo networkInfo = (NetworkInfo)
intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO);
mIsWifiConnected = (networkInfo != null && networkInfo.isConnected());
} else if (action.equals(WifiManager.WIFI_STATE_CHANGED_ACTION)) {
final boolean enabled = intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE,
WifiManager.WIFI_STATE_UNKNOWN) == WifiManager.WIFI_STATE_ENABLED;
if (!enabled) {
// when wifi got disabeled, the NETWORK_STATE_CHANGED_ACTION
// quit and wont report disconnected til next enalbing.
mIsWifiConnected = false;
}
}
}
};
/* 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.mRuimRecords.registerForRecordsLoaded(this, EVENT_RECORDS_LOADED, null);
p.mCM.registerForNVReady(this, EVENT_NV_READY, null);
p.mCM.registerForDataStateChanged (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.registerForCdmaDataConnectionAttached(this, EVENT_TRY_SETUP_DATA, null);
p.mSST.registerForCdmaDataConnectionDetached(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);
IntentFilter filter = new IntentFilter();
filter.addAction(INTENT_RECONNECT_ALARM);
filter.addAction(Intent.ACTION_SCREEN_ON);
filter.addAction(Intent.ACTION_SCREEN_OFF);
filter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
filter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION);
// TODO: Why is this registering the phone as the receiver of the intent
// and not its own handler?
p.getContext().registerReceiver(mIntentReceiver, filter, null, p);
mDataConnectionTracker = this;
createAllDataConnectionList();
// This preference tells us 1) initial condition for "dataEnabled",
// and 2) whether the RIL will setup the baseband to auto-PS attach.
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(phone.getContext());
boolean dataEnabledSetting = true;
try {
dataEnabledSetting = IConnectivityManager.Stub.asInterface(ServiceManager.
getService(Context.CONNECTIVITY_SERVICE)).getMobileDataEnabled();
} catch (Exception e) {
// nothing to do - use the old behavior and leave data on
}
dataEnabled[APN_DEFAULT_ID] =
!sp.getBoolean(CDMAPhone.DATA_DISABLED_ON_BOOT_KEY, false) &&
dataEnabledSetting;
if (dataEnabled[APN_DEFAULT_ID]) {
enabledCount++;
}
noAutoAttach = !dataEnabled[APN_DEFAULT_ID];
if (!mRetryMgr.configure(SystemProperties.get("ro.cdma.data_retry_config"))) {
if (!mRetryMgr.configure(DEFAULT_DATA_RETRY_CONFIG)) {
// Should never happen, log an error and default to a simple linear sequence.
Log.e(LOG_TAG, "Could not configure using DEFAULT_DATA_RETRY_CONFIG="
+ DEFAULT_DATA_RETRY_CONFIG);
mRetryMgr.configure(20, 2000, 1000);
}
}
}
public void dispose() {
// Unregister from all events
phone.mCM.unregisterForAvailable(this);
phone.mCM.unregisterForOffOrNotAvailable(this);
mCdmaPhone.mRuimRecords.unregisterForRecordsLoaded(this);
phone.mCM.unregisterForNVReady(this);
phone.mCM.unregisterForDataStateChanged(this);
mCdmaPhone.mCT.unregisterForVoiceCallEnded(this);
mCdmaPhone.mCT.unregisterForVoiceCallStarted(this);
mCdmaPhone.mSST.unregisterForCdmaDataConnectionAttached(this);
mCdmaPhone.mSST.unregisterForCdmaDataConnectionDetached(this);
mCdmaPhone.mSST.unregisterForRoamingOn(this);
mCdmaPhone.mSST.unregisterForRoamingOff(this);
phone.mCM.unregisterForCdmaOtaProvision(this);
phone.getContext().unregisterReceiver(this.mIntentReceiver);
destroyAllDataConnectionList();
}
protected void finalize() {
if(DBG) Log.d(LOG_TAG, "CdmaDataConnectionTracker finalized");
}
protected void setState(State s) {
if (DBG) log ("setState: " + s);
if (state != s) {
EventLog.writeEvent(EventLogTags.CDMA_DATA_STATE_CHANGE,
state.toString(), s.toString());
state = s;
}
}
@Override
protected boolean isApnTypeActive(String type) {
return mActiveApn != null && mActiveApn.canHandleType(type);
}
@Override
protected boolean isApnTypeAvailable(String type) {
for (String s : mSupportedApnTypes) {
if (TextUtils.equals(type, s)) {
return true;
}
}
return false;
}
protected String[] getActiveApnTypes() {
String[] result;
if (mActiveApn != null) {
result = mActiveApn.types;
} else {
result = new String[1];
result[0] = Phone.APN_TYPE_DEFAULT;
}
return result;
}
protected String getActiveApnString() {
return null;
}
/**
* The data connection is expected to be setup while device
* 1. has ruim card or non-volatile data store
* 2. registered to data connection service
* 3. user doesn't explicitly disable data service
* 4. wifi is not on
*
* @return false while no data connection if all above requirements are met.
*/
public boolean isDataConnectionAsDesired() {
boolean roaming = phone.getServiceState().getRoaming();
if (((phone.mCM.getRadioState() == CommandsInterface.RadioState.NV_READY) ||
mCdmaPhone.mRuimRecords.getRecordsLoaded()) &&
(mCdmaPhone.mSST.getCurrentCdmaDataConnectionState() ==
ServiceState.STATE_IN_SERVICE) &&
(!roaming || getDataOnRoamingEnabled()) &&
!mIsWifiConnected ) {
return (state == State.CONNECTED);
}
return true;
}
private boolean isDataAllowed() {
boolean roaming = phone.getServiceState().getRoaming();
return getAnyDataEnabled() && (!roaming || getDataOnRoamingEnabled()) && mMasterDataEnabled;
}
private boolean trySetupData(String reason) {
if (DBG) log("***trySetupData due to " + (reason == null ? "(unspecified)" : reason));
if (phone.getSimulatedRadioControl() != null) {
// Assume data is connected on the simulator
// FIXME this can be improved
setState(State.CONNECTED);
phone.notifyDataConnection(reason);
Log.i(LOG_TAG, "(fix?) We're on the simulator; assuming data is connected");
return true;
}
int psState = mCdmaPhone.mSST.getCurrentCdmaDataConnectionState();
boolean roaming = phone.getServiceState().getRoaming();
boolean desiredPowerState = mCdmaPhone.mSST.getDesiredPowerState();
if ((state == State.IDLE || state == State.SCANNING)
&& (psState == ServiceState.STATE_IN_SERVICE)
&& ((phone.mCM.getRadioState() == CommandsInterface.RadioState.NV_READY) ||
mCdmaPhone.mRuimRecords.getRecordsLoaded())
&& (mCdmaPhone.mSST.isConcurrentVoiceAndData() ||
phone.getState() == Phone.State.IDLE )
&& isDataAllowed()
&& desiredPowerState
&& !mPendingRestartRadio
&& !mCdmaPhone.needsOtaServiceProvisioning()) {
return setupData(reason);
} else {
if (DBG) {
log("trySetupData: Not ready for data: " +
" dataState=" + state +
" PS state=" + psState +
" radio state=" + phone.mCM.getRadioState() +
" ruim=" + mCdmaPhone.mRuimRecords.getRecordsLoaded() +
" concurrentVoice&Data=" + mCdmaPhone.mSST.isConcurrentVoiceAndData() +
" phoneState=" + phone.getState() +
" dataEnabled=" + getAnyDataEnabled() +
" roaming=" + roaming +
" dataOnRoamingEnable=" + getDataOnRoamingEnabled() +
" desiredPowerState=" + desiredPowerState +
" PendingRestartRadio=" + mPendingRestartRadio +
" MasterDataEnabled=" + mMasterDataEnabled +
" needsOtaServiceProvisioning=" + mCdmaPhone.needsOtaServiceProvisioning());
}
return false;
}
}
/**
* If tearDown is true, this only tears down a CONNECTED session. Presently,
* there is no mechanism for abandoning an INITING/CONNECTING session,
* but would likely involve cancelling pending async requests or
* setting a flag or new state to ignore them when they came in
* @param tearDown true if the underlying DataConnection should be
* disconnected.
* @param reason 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) phone.getContext().getSystemService(Context.ALARM_SERVICE);
am.cancel(mReconnectIntent);
mReconnectIntent = null;
}
setState(State.DISCONNECTING);
boolean notificationDeferred = false;
for (DataConnection conn : dataConnectionList) {
if(conn != null) {
if (tearDown) {
if (DBG) log("cleanUpConnection: teardown, call conn.disconnect");
conn.disconnect(obtainMessage(EVENT_DISCONNECT_DONE, reason));
notificationDeferred = true;
} else {
if (DBG) log("cleanUpConnection: !tearDown, call conn.resetSynchronously");
conn.resetSynchronously();
notificationDeferred = false;
}
}
}
stopNetStatPoll();
if (!notificationDeferred) {
if (DBG) log("cleanupConnection: !notificationDeferred");
gotoIdleAndNotifyDataConnection(reason);
}
}
private CdmaDataConnection findFreeDataConnection() {
for (DataConnection connBase : dataConnectionList) {
CdmaDataConnection conn = (CdmaDataConnection) connBase;
if (conn.isInactive()) {
return conn;
}
}
return null;
}
private boolean setupData(String reason) {
CdmaDataConnection conn = findFreeDataConnection();
if (conn == null) {
if (DBG) log("setupData: No free CdmaDataConnectionfound!");
return false;
}
mActiveDataConnection = conn;
String[] types;
if (mRequestedApnType.equals(Phone.APN_TYPE_DUN)) {
types = new String[1];
types[0] = Phone.APN_TYPE_DUN;
} else {
types = mDefaultApnTypes;
}
mActiveApn = new ApnSetting(0, "", "", "", "", "", "", "", "", "", "", 0, types);
Message msg = obtainMessage();
msg.what = EVENT_DATA_SETUP_COMPLETE;
msg.obj = reason;
conn.connect(msg, mActiveApn);
setState(State.INITING);
phone.notifyDataConnection(reason);
return true;
}
private void notifyDefaultData(String reason) {
setState(State.CONNECTED);
phone.notifyDataConnection(reason);
startNetStatPoll();
mRetryMgr.resetRetryCount();
}
private void resetPollStats() {
txPkts = -1;
rxPkts = -1;
sentSinceLastRecv = 0;
netStatPollPeriod = POLL_NETSTAT_MILLIS;
mNoRecvPollCount = 0;
}
protected void startNetStatPoll() {
if (state == State.CONNECTED && netStatPollEnabled == false) {
Log.d(LOG_TAG, "[DataConnection] Start poll NetStat");
resetPollStats();
netStatPollEnabled = true;
mPollNetStat.run();
}
}
protected void stopNetStatPoll() {
netStatPollEnabled = false;
removeCallbacks(mPollNetStat);
Log.d(LOG_TAG, "[DataConnection] Stop poll NetStat");
}
protected void restartRadio() {
if (DBG) log("Cleanup connection and wait " +
(TIME_DELAYED_TO_RESTART_RADIO / 1000) + "s to restart radio");
cleanUpConnection(true, Phone.REASON_RADIO_TURNED_OFF);
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 = txPkts;
preRxPkts = rxPkts;
txPkts = TrafficStats.getMobileTxPackets();
rxPkts = TrafficStats.getMobileRxPackets();
//Log.d(LOG_TAG, "rx " + String.valueOf(rxPkts) + " tx " + String.valueOf(txPkts));
if (netStatPollEnabled && (preTxPkts > 0 || preRxPkts > 0)) {
sent = txPkts - preTxPkts;
received = rxPkts - preRxPkts;
if ( sent > 0 && received > 0 ) {
sentSinceLastRecv = 0;
newActivity = Activity.DATAINANDOUT;
} else if (sent > 0 && received == 0) {
if (phone.getState() == Phone.State.IDLE) {
sentSinceLastRecv += sent;
} else {
sentSinceLastRecv = 0;
}
newActivity = Activity.DATAOUT;
} else if (sent == 0 && received > 0) {
sentSinceLastRecv = 0;
newActivity = Activity.DATAIN;
} else if (sent == 0 && received == 0) {
newActivity = (activity == Activity.DORMANT) ? activity : Activity.NONE;
} else {
sentSinceLastRecv = 0;
newActivity = (activity == Activity.DORMANT) ? activity : Activity.NONE;
}
if (activity != newActivity) {
activity = newActivity;
phone.notifyDataActivity();
}
}
if (sentSinceLastRecv >= NUMBER_SENT_PACKETS_OF_HANG) {
// Packets sent without ack exceeded threshold.
if (mNoRecvPollCount == 0) {
EventLog.writeEvent(
EventLogTags.PDP_RADIO_RESET_COUNTDOWN_TRIGGERED,
sentSinceLastRecv);
}
if (mNoRecvPollCount < NO_RECV_POLL_LIMIT) {
mNoRecvPollCount++;
// Slow down the poll interval to let things happen
netStatPollPeriod = POLL_NETSTAT_SLOW_MILLIS;
} else {
if (DBG) log("Sent " + String.valueOf(sentSinceLastRecv) +
" pkts since last received");
// We've exceeded the threshold. Restart the radio.
netStatPollEnabled = false;
stopNetStatPoll();
restartRadio();
EventLog.writeEvent(EventLogTags.PDP_RADIO_RESET, NO_RECV_POLL_LIMIT);
}
} else {
mNoRecvPollCount = 0;
netStatPollPeriod = POLL_NETSTAT_MILLIS;
}
if (netStatPollEnabled) {
mDataConnectionTracker.postDelayed(this, netStatPollPeriod);
}
}
};
/**
* 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) {
if (state == 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 = mRetryMgr.getRetryTimer();
Log.d(LOG_TAG, "Data Connection activate failed. Scheduling next attempt for "
+ (nextReconnectDelay / 1000) + "s");
AlarmManager am =
(AlarmManager) phone.getContext().getSystemService(Context.ALARM_SERVICE);
Intent intent = new Intent(INTENT_RECONNECT_ALARM);
intent.putExtra(INTENT_RECONNECT_ALARM_EXTRA_REASON, reason);
mReconnectIntent = PendingIntent.getBroadcast(
phone.getContext(), 0, intent, 0);
am.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
SystemClock.elapsedRealtime() + nextReconnectDelay,
mReconnectIntent);
mRetryMgr.increaseRetryCount();
if (!shouldPostNotification(lastFailCauseCode)) {
Log.d(LOG_TAG,"NOT Posting Data Connection Unavailable notification "
+ "-- likely transient error");
} else {
notifyNoData(lastFailCauseCode);
}
}
}
private void notifyNoData(FailCause lastFailCauseCode) {
setState(State.FAILED);
}
private void gotoIdleAndNotifyDataConnection(String reason) {
if (DBG) log("gotoIdleAndNotifyDataConnection: reason=" + reason);
setState(State.IDLE);
phone.notifyDataConnection(reason);
mActiveApn = null;
}
protected void onRecordsLoaded() {
if (state == State.FAILED) {
cleanUpConnection(false, null);
}
sendMessage(obtainMessage(EVENT_TRY_SETUP_DATA, Phone.REASON_SIM_LOADED));
}
protected void onNVReady() {
if (state == State.FAILED) {
cleanUpConnection(false, null);
}
sendMessage(obtainMessage(EVENT_TRY_SETUP_DATA));
}
/**
* @override com.android.intenral.telephony.DataConnectionTracker
*/
@Override
protected void onEnableNewApn() {
cleanUpConnection(true, Phone.REASON_APN_SWITCHED);
}
/**
* @override com.android.internal.telephony.DataConnectionTracker
*/
protected boolean onTrySetupData(String reason) {
return trySetupData(reason);
}
/**
* @override com.android.internal.telephony.DataConnectionTracker
*/
protected void onRoamingOff() {
trySetupData(Phone.REASON_ROAMING_OFF);
}
/**
* @override com.android.internal.telephony.DataConnectionTracker
*/
protected void onRoamingOn() {
if (getDataOnRoamingEnabled()) {
trySetupData(Phone.REASON_ROAMING_ON);
} else {
if (DBG) log("Tear down data connection on roaming.");
cleanUpConnection(true, Phone.REASON_ROAMING_ON);
}
}
/**
* @override com.android.internal.telephony.DataConnectionTracker
*/
protected void onRadioAvailable() {
if (phone.getSimulatedRadioControl() != null) {
// Assume data is connected on the simulator
// FIXME this can be improved
setState(State.CONNECTED);
phone.notifyDataConnection(null);
Log.i(LOG_TAG, "We're on the simulator; assuming data is connected");
}
if (state != State.IDLE) {
cleanUpConnection(true, null);
}
}
/**
* @override com.android.internal.telephony.DataConnectionTracker
*/
protected void onRadioOffOrNotAvailable() {
mRetryMgr.resetRetryCount();
if (phone.getSimulatedRadioControl() != null) {
// Assume data is connected on the simulator
// FIXME this can be improved
Log.i(LOG_TAG, "We're on the simulator; assuming radio off is meaningless");
} else {
if (DBG) log("Radio is off and clean up all connection");
cleanUpConnection(false, Phone.REASON_RADIO_TURNED_OFF);
}
}
/**
* @override com.android.internal.telephony.DataConnectionTracker
*/
protected void onDataSetupComplete(AsyncResult ar) {
String reason = null;
if (ar.userObj instanceof String) {
reason = (String) ar.userObj;
}
if (ar.exception == null) {
// 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;
}
startDelayedRetry(cause, reason);
}
}
/**
* Called when EVENT_DISCONNECT_DONE is received.
*/
protected void onDisconnectDone(AsyncResult ar) {
if(DBG) log("EVENT_DISCONNECT_DONE");
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();
}
phone.notifyDataConnection(reason);
mActiveApn = null;
if (retryAfterDisconnected(reason)) {
trySetupData(reason);
}
}
/**
* Called when EVENT_RESET_DONE is received so goto
* IDLE state and send notifications to those interested.
*/
@Override
protected void onResetDone(AsyncResult ar) {
if (DBG) log("EVENT_RESET_DONE");
String reason = null;
if (ar.userObj instanceof String) {
reason = (String) ar.userObj;
}
gotoIdleAndNotifyDataConnection(reason);
}
/**
* @override com.android.internal.telephony.DataConnectionTracker
*/
protected void onVoiceCallStarted() {
if (state == State.CONNECTED && !mCdmaPhone.mSST.isConcurrentVoiceAndData()) {
stopNetStatPoll();
phone.notifyDataConnection(Phone.REASON_VOICE_CALL_STARTED);
}
}
/**
* @override com.android.internal.telephony.DataConnectionTracker
*/
protected void onVoiceCallEnded() {
if (state == State.CONNECTED) {
if (!mCdmaPhone.mSST.isConcurrentVoiceAndData()) {
startNetStatPoll();
phone.notifyDataConnection(Phone.REASON_VOICE_CALL_ENDED);
} else {
// clean slate after call end.
resetPollStats();
}
} else {
mRetryMgr.resetRetryCount();
// in case data setup was attempted when we were on a voice call
trySetupData(Phone.REASON_VOICE_CALL_ENDED);
}
}
/**
* @override com.android.internal.telephony.DataConnectionTracker
*/
protected void onCleanUpConnection(boolean tearDown, String reason) {
cleanUpConnection(tearDown, reason);
}
private void createAllDataConnectionList() {
dataConnectionList = new ArrayList<DataConnection>();
CdmaDataConnection dataConn;
for (int i = 0; i < DATA_CONNECTION_POOL_SIZE; i++) {
dataConn = CdmaDataConnection.makeDataConnection(mCdmaPhone);
dataConnectionList.add(dataConn);
}
}
private void destroyAllDataConnectionList() {
if(dataConnectionList != null) {
dataConnectionList.removeAll(dataConnectionList);
}
}
private void onCdmaDataDetached() {
if (state == State.CONNECTED) {
startNetStatPoll();
phone.notifyDataConnection(Phone.REASON_CDMA_DATA_DETACHED);
} else {
if (state == State.FAILED) {
cleanUpConnection(false, Phone.REASON_CDMA_DATA_DETACHED);
mRetryMgr.resetRetryCount();
CdmaCellLocation loc = (CdmaCellLocation)(phone.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:
mRetryMgr.resetRetryCount();
break;
default:
break;
}
}
}
}
private void onRestartRadio() {
if (mPendingRestartRadio) {
Log.d(LOG_TAG, "************TURN OFF RADIO**************");
phone.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)(phone.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 (state == 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.i(LOG_TAG, "onDataStateChanged: No active connection"
+ "state is CONNECTED, disconnecting/cleanup");
writeEventLogCdmaDataDrop();
cleanUpConnection(true, null);
return;
}
switch (connectionState) {
case DATA_CONNECTION_ACTIVE_PH_LINK_UP:
Log.v(LOG_TAG, "onDataStateChanged: active=LINK_ACTIVE && CONNECTED, ignore");
activity = Activity.NONE;
phone.notifyDataActivity();
startNetStatPoll();
break;
case DATA_CONNECTION_ACTIVE_PH_LINK_DOWN:
Log.v(LOG_TAG, "onDataStateChanged active=LINK_DOWN && CONNECTED, dormant");
activity = Activity.DORMANT;
phone.notifyDataActivity();
stopNetStatPoll();
break;
default:
Log.v(LOG_TAG, "onDataStateChanged: IGNORE unexpected DataCallState.active="
+ connectionState);
}
} else {
// TODO: Do we need to do anything?
Log.i(LOG_TAG, "onDataStateChanged: not connected, state=" + state + " ignoring");
}
}
protected String getInterfaceName(String apnType) {
if (mActiveDataConnection != null) {
return mActiveDataConnection.getInterface();
}
return null;
}
protected String getIpAddress(String apnType) {
if (mActiveDataConnection != null) {
return mActiveDataConnection.getIpAddress();
}
return null;
}
protected String getGateway(String apnType) {
if (mActiveDataConnection != null) {
return mActiveDataConnection.getGatewayAddress();
}
return null;
}
protected String[] getDnsServers(String apnType) {
if (mActiveDataConnection != null) {
return mActiveDataConnection.getDnsServers();
}
return null;
}
public ArrayList<DataConnection> getAllDataConnections() {
return dataConnectionList;
}
private void startDelayedRetry(FailCause cause, String reason) {
notifyNoData(cause);
reconnectAfterFail(cause, reason);
}
public void handleMessage (Message msg) {
if (!phone.mIsTheCurrentActivePhone) {
Log.d(LOG_TAG, "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;
}
}
protected void log(String s) {
Log.d(LOG_TAG, "[CdmaDataConnectionTracker] " + s);
}
}