| /* |
| * 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); |
| } |
| } |