| /* |
| * 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.gsm; |
| |
| import android.app.AlarmManager; |
| import android.app.PendingIntent; |
| import android.content.ContentResolver; |
| import android.content.ContentValues; |
| import android.content.Context; |
| import android.content.Intent; |
| import android.database.ContentObserver; |
| import android.database.Cursor; |
| import android.net.ProxyProperties; |
| import android.net.TrafficStats; |
| import android.net.Uri; |
| import android.os.AsyncResult; |
| import android.os.Message; |
| import android.os.SystemClock; |
| import android.os.SystemProperties; |
| import android.provider.Settings; |
| import android.provider.Telephony; |
| import android.telephony.ServiceState; |
| import android.telephony.TelephonyManager; |
| import android.telephony.gsm.GsmCellLocation; |
| import android.util.EventLog; |
| import android.util.Log; |
| |
| import com.android.internal.R; |
| import com.android.internal.telephony.ApnSetting; |
| import com.android.internal.telephony.DataCallState; |
| import com.android.internal.telephony.DataConnection; |
| import com.android.internal.telephony.DataConnectionTracker; |
| import com.android.internal.telephony.Phone; |
| import com.android.internal.telephony.RetryManager; |
| import com.android.internal.telephony.EventLogTags; |
| import com.android.internal.telephony.DataConnection.FailCause; |
| |
| import java.io.IOException; |
| import java.net.InetAddress; |
| import java.net.InetSocketAddress; |
| import java.net.UnknownHostException; |
| import java.util.ArrayList; |
| import java.util.HashMap; |
| |
| /** |
| * {@hide} |
| */ |
| public final class GsmDataConnectionTracker extends DataConnectionTracker { |
| protected final String LOG_TAG = "GSM"; |
| |
| private GSMPhone mGsmPhone; |
| /** |
| * Handles changes to the APN db. |
| */ |
| private class ApnChangeObserver extends ContentObserver { |
| public ApnChangeObserver () { |
| super(mDataConnectionTracker); |
| } |
| |
| @Override |
| public void onChange(boolean selfChange) { |
| sendMessage(obtainMessage(EVENT_APN_CHANGED)); |
| } |
| } |
| |
| //***** Instance Variables |
| |
| private boolean mReregisterOnReconnectFailure = false; |
| private ContentResolver mResolver; |
| |
| // Count of PDP reset attempts; reset when we see incoming, |
| // call reRegisterNetwork, or pingTest succeeds. |
| private int mPdpResetCount = 0; |
| |
| /** Delay between APN attempts */ |
| protected static final int APN_DELAY_MILLIS = 5000; |
| |
| //useful for debugging |
| boolean mFailNextConnect = false; |
| |
| /** |
| * allApns holds all apns for this sim spn, retrieved from |
| * the Carrier DB. |
| * |
| * Create once after simcard info is loaded |
| */ |
| private ArrayList<ApnSetting> mAllApns = null; |
| |
| /** |
| * waitingApns holds all apns that are waiting to be connected |
| * |
| * It is a subset of allApns and has the same format |
| */ |
| private ArrayList<ApnSetting> mWaitingApns = null; |
| private int mWaitingApnsPermanentFailureCountDown = 0; |
| private ApnSetting mPreferredApn = null; |
| |
| /** The DataConnection being setup */ |
| private GsmDataConnection mPendingDataConnection; |
| |
| /** Convert an ApnType string to Id (TODO: Use "enumeration" instead of String for ApnType) */ |
| private HashMap<String, Integer> mApnToDataConnectionId = |
| new HashMap<String, Integer>(); |
| |
| /** Is packet service restricted by network */ |
| private boolean mIsPsRestricted = false; |
| |
| //***** Constants |
| |
| private static final int POLL_PDP_MILLIS = 5 * 1000; |
| |
| private static final String INTENT_RECONNECT_ALARM = "com.android.internal.telephony.gprs-reconnect"; |
| |
| static final Uri PREFERAPN_URI = Uri.parse("content://telephony/carriers/preferapn"); |
| static final String APN_ID = "apn_id"; |
| private boolean canSetPreferApn = false; |
| |
| /** Watches for changes to the APN db. */ |
| private ApnChangeObserver mApnObserver; |
| |
| //***** Constructor |
| |
| GsmDataConnectionTracker(GSMPhone p) { |
| super(p); |
| mGsmPhone = p; |
| |
| p.mCM.registerForAvailable (this, EVENT_RADIO_AVAILABLE, null); |
| p.mCM.registerForOffOrNotAvailable(this, EVENT_RADIO_OFF_OR_NOT_AVAILABLE, null); |
| p.mSIMRecords.registerForRecordsLoaded(this, EVENT_RECORDS_LOADED, 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.registerForGprsAttached(this, EVENT_GPRS_ATTACHED, null); |
| p.mSST.registerForGprsDetached(this, EVENT_GPRS_DETACHED, null); |
| p.mSST.registerForRoamingOn(this, EVENT_ROAMING_ON, null); |
| p.mSST.registerForRoamingOff(this, EVENT_ROAMING_OFF, null); |
| p.mSST.registerForPsRestrictedEnabled(this, EVENT_PS_RESTRICT_ENABLED, null); |
| p.mSST.registerForPsRestrictedDisabled(this, EVENT_PS_RESTRICT_DISABLED, null); |
| |
| mDataConnectionTracker = this; |
| mResolver = mPhone.getContext().getContentResolver(); |
| |
| mApnObserver = new ApnChangeObserver(); |
| p.getContext().getContentResolver().registerContentObserver( |
| Telephony.Carriers.CONTENT_URI, true, mApnObserver); |
| |
| /** Create the default connection */ |
| createDataConnection(Phone.APN_TYPE_DEFAULT); |
| broadcastMessenger(); |
| } |
| |
| @Override |
| public void dispose() { |
| super.dispose(); |
| |
| //Unregister for all events |
| mPhone.mCM.unregisterForAvailable(this); |
| mPhone.mCM.unregisterForOffOrNotAvailable(this); |
| mGsmPhone.mSIMRecords.unregisterForRecordsLoaded(this); |
| mPhone.mCM.unregisterForDataNetworkStateChanged(this); |
| mGsmPhone.mCT.unregisterForVoiceCallEnded(this); |
| mGsmPhone.mCT.unregisterForVoiceCallStarted(this); |
| mGsmPhone.mSST.unregisterForGprsAttached(this); |
| mGsmPhone.mSST.unregisterForGprsDetached(this); |
| mGsmPhone.mSST.unregisterForRoamingOn(this); |
| mGsmPhone.mSST.unregisterForRoamingOff(this); |
| mGsmPhone.mSST.unregisterForPsRestrictedEnabled(this); |
| mGsmPhone.mSST.unregisterForPsRestrictedDisabled(this); |
| |
| mPhone.getContext().getContentResolver().unregisterContentObserver(this.mApnObserver); |
| |
| destroyDataConnections(); |
| } |
| |
| @Override |
| protected void finalize() { |
| if(DBG) log("finalize"); |
| } |
| |
| @Override |
| protected String getActionIntentReconnectAlarm() { |
| return INTENT_RECONNECT_ALARM; |
| } |
| |
| @Override |
| protected void setState(State s) { |
| if (DBG) log ("setState: " + s); |
| if (mState != s) { |
| EventLog.writeEvent(EventLogTags.GSM_DATA_STATE_CHANGE, mState.toString(), s.toString()); |
| mState = s; |
| } |
| |
| if (mState == State.FAILED) { |
| if (mWaitingApns != null) |
| mWaitingApns.clear(); // when tear down the connection and set to IDLE |
| } |
| } |
| |
| /** |
| * The data connection is expected to be setup while device |
| * 1. has sim card |
| * 2. registered to gprs 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. |
| */ |
| @Override |
| public boolean isDataConnectionAsDesired() { |
| boolean roaming = mPhone.getServiceState().getRoaming(); |
| |
| if (mGsmPhone.mSIMRecords.getRecordsLoaded() && |
| mGsmPhone.mSST.getCurrentGprsState() == ServiceState.STATE_IN_SERVICE && |
| (!roaming || getDataOnRoamingEnabled()) && |
| !mIsWifiConnected && |
| !mIsPsRestricted ) { |
| return (mState == State.CONNECTED); |
| } |
| return true; |
| } |
| |
| @Override |
| protected boolean isApnTypeAvailable(String type) { |
| if (type.equals(Phone.APN_TYPE_DUN)) { |
| return (fetchDunApn() != null); |
| } |
| |
| if (mAllApns != null) { |
| for (ApnSetting apn : mAllApns) { |
| if (apn.canHandleType(type)) { |
| return true; |
| } |
| } |
| } |
| return false; |
| } |
| |
| //****** Called from ServiceStateTracker |
| /** |
| * Invoked when ServiceStateTracker observes a transition from GPRS |
| * attach to detach. |
| */ |
| protected void onGprsDetached() { |
| /* |
| * We presently believe it is unnecessary to tear down the PDP context |
| * when GPRS detaches, but we should stop the network polling. |
| */ |
| stopNetStatPoll(); |
| notifyDataConnection(Phone.REASON_GPRS_DETACHED); |
| } |
| |
| private void onGprsAttached() { |
| if (mState == State.CONNECTED) { |
| startNetStatPoll(); |
| notifyDataConnection(Phone.REASON_GPRS_ATTACHED); |
| } else { |
| if (mState == State.FAILED) { |
| cleanUpConnection(false, Phone.REASON_GPRS_ATTACHED); |
| mRetryMgr.resetRetryCount(); |
| } |
| trySetupData(Phone.REASON_GPRS_ATTACHED); |
| } |
| } |
| |
| @Override |
| protected boolean isDataAllowed() { |
| int gprsState = mGsmPhone.mSST.getCurrentGprsState(); |
| boolean desiredPowerState = mGsmPhone.mSST.getDesiredPowerState(); |
| |
| boolean allowed = |
| (gprsState == ServiceState.STATE_IN_SERVICE || mAutoAttachOnCreation) && |
| mGsmPhone.mSIMRecords.getRecordsLoaded() && |
| mPhone.getState() == Phone.State.IDLE && |
| mInternalDataEnabled && |
| (!mPhone.getServiceState().getRoaming() || getDataOnRoamingEnabled()) && |
| !mIsPsRestricted && |
| desiredPowerState; |
| if (!allowed && DBG) { |
| String reason = ""; |
| if (!((gprsState == ServiceState.STATE_IN_SERVICE) || mAutoAttachOnCreation)) { |
| reason += " - gprs= " + gprsState; |
| } |
| if (!mGsmPhone.mSIMRecords.getRecordsLoaded()) reason += " - SIM not loaded"; |
| if (mPhone.getState() != Phone.State.IDLE) { |
| reason += " - PhoneState= " + mPhone.getState(); |
| } |
| if (!mInternalDataEnabled) reason += " - mInternalDataEnabled= false"; |
| if (mPhone.getServiceState().getRoaming() && !getDataOnRoamingEnabled()) { |
| reason += " - Roaming and data roaming not enabled"; |
| } |
| if (mIsPsRestricted) reason += " - mIsPsRestricted= true"; |
| if (!desiredPowerState) reason += " - desiredPowerState= false"; |
| log("Data not allowed due to" + reason); |
| } |
| return allowed; |
| } |
| |
| private boolean trySetupData(String reason) { |
| if (DBG) log("***trySetupData due to " + (reason == null ? "(unspecified)" : reason)); |
| |
| log("[DSAC DEB] " + "trySetupData with mIsPsRestricted=" + mIsPsRestricted); |
| |
| if (mPhone.getSimulatedRadioControl() != null) { |
| // Assume data is connected on the simulator |
| // FIXME this can be improved |
| setState(State.CONNECTED); |
| notifyDataConnection(reason); |
| |
| log("(fix?) We're on the simulator; assuming data is connected"); |
| return true; |
| } |
| |
| int gprsState = mGsmPhone.mSST.getCurrentGprsState(); |
| boolean desiredPowerState = mGsmPhone.mSST.getDesiredPowerState(); |
| |
| if (((mState == State.IDLE) || (mState == State.SCANNING)) && |
| isDataAllowed() && getAnyDataEnabled()) { |
| |
| if (mState == State.IDLE) { |
| mWaitingApns = buildWaitingApns(mRequestedApnType); |
| mWaitingApnsPermanentFailureCountDown = mWaitingApns.size(); |
| if (mWaitingApns.isEmpty()) { |
| if (DBG) log("No APN found"); |
| notifyNoData(GsmDataConnection.FailCause.MISSING_UNKNOWN_APN); |
| notifyOffApnsOfAvailability(reason, false); |
| return false; |
| } else { |
| log ("Create from allApns : " + apnListToString(mAllApns)); |
| } |
| } |
| |
| if (DBG) { |
| log ("Setup waitngApns : " + apnListToString(mWaitingApns)); |
| } |
| boolean retValue = setupData(reason); |
| notifyOffApnsOfAvailability(reason, retValue); |
| return retValue; |
| } else { |
| notifyOffApnsOfAvailability(reason, false); |
| return false; |
| } |
| } |
| |
| /** |
| * Cleanup all connections. |
| * |
| * TODO: Cleanup only a specified connection passed as a parameter. |
| * |
| * @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("Clean up connection due to " + 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); |
| |
| boolean notificationDeferred = false; |
| for (DataConnection conn : mDataConnections.values()) { |
| if (tearDown) { |
| if (DBG) log("cleanUpConnection: teardown, call conn.disconnect"); |
| conn.disconnect(obtainMessage(EVENT_DISCONNECT_DONE, |
| conn.getDataConnectionId(), 0, 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); |
| } |
| } |
| |
| /** |
| * @param types comma delimited list of APN types |
| * @return array of APN types |
| */ |
| private String[] parseTypes(String types) { |
| String[] result; |
| // If unset, set to DEFAULT. |
| if (types == null || types.equals("")) { |
| result = new String[1]; |
| result[0] = Phone.APN_TYPE_ALL; |
| } else { |
| result = types.split(","); |
| } |
| return result; |
| } |
| |
| private ArrayList<ApnSetting> createApnList(Cursor cursor) { |
| ArrayList<ApnSetting> result = new ArrayList<ApnSetting>(); |
| if (cursor.moveToFirst()) { |
| do { |
| String[] types = parseTypes( |
| cursor.getString(cursor.getColumnIndexOrThrow(Telephony.Carriers.TYPE))); |
| ApnSetting apn = new ApnSetting( |
| cursor.getInt(cursor.getColumnIndexOrThrow(Telephony.Carriers._ID)), |
| cursor.getString(cursor.getColumnIndexOrThrow(Telephony.Carriers.NUMERIC)), |
| cursor.getString(cursor.getColumnIndexOrThrow(Telephony.Carriers.NAME)), |
| cursor.getString(cursor.getColumnIndexOrThrow(Telephony.Carriers.APN)), |
| cursor.getString(cursor.getColumnIndexOrThrow(Telephony.Carriers.PROXY)), |
| cursor.getString(cursor.getColumnIndexOrThrow(Telephony.Carriers.PORT)), |
| cursor.getString(cursor.getColumnIndexOrThrow(Telephony.Carriers.MMSC)), |
| cursor.getString(cursor.getColumnIndexOrThrow(Telephony.Carriers.MMSPROXY)), |
| cursor.getString(cursor.getColumnIndexOrThrow(Telephony.Carriers.MMSPORT)), |
| cursor.getString(cursor.getColumnIndexOrThrow(Telephony.Carriers.USER)), |
| cursor.getString(cursor.getColumnIndexOrThrow(Telephony.Carriers.PASSWORD)), |
| cursor.getInt(cursor.getColumnIndexOrThrow(Telephony.Carriers.AUTH_TYPE)), |
| types, |
| cursor.getString(cursor.getColumnIndexOrThrow(Telephony.Carriers.PROTOCOL)), |
| cursor.getString(cursor.getColumnIndexOrThrow( |
| Telephony.Carriers.ROAMING_PROTOCOL))); |
| result.add(apn); |
| } while (cursor.moveToNext()); |
| } |
| return result; |
| } |
| |
| private GsmDataConnection findFreeDataConnection() { |
| for (DataConnection dc : mDataConnections.values()) { |
| if (dc.isInactive()) { |
| log("found free GsmDataConnection"); |
| return (GsmDataConnection) dc; |
| } |
| } |
| log("NO free GsmDataConnection"); |
| return null; |
| } |
| |
| private boolean setupData(String reason) { |
| ApnSetting apn; |
| GsmDataConnection gdc; |
| |
| apn = getNextApn(); |
| if (apn == null) return false; |
| gdc = findFreeDataConnection(); |
| if (gdc == null) { |
| if (DBG) log("setupData: No free GsmDataConnection found!"); |
| return false; |
| } |
| mActiveApn = apn; |
| mPendingDataConnection = gdc; |
| |
| Message msg = obtainMessage(); |
| msg.what = EVENT_DATA_SETUP_COMPLETE; |
| msg.obj = reason; |
| gdc.connect(msg, apn); |
| |
| setState(State.INITING); |
| notifyDataConnection(reason); |
| return true; |
| } |
| |
| private boolean dataCallStatesHasCID (ArrayList<DataCallState> states, int cid) { |
| for (int i = 0, s = states.size() ; i < s ; i++) { |
| if (states.get(i).cid == cid) return true; |
| } |
| return false; |
| } |
| |
| private boolean dataCallStatesHasActiveCID (ArrayList<DataCallState> states, int cid) { |
| for (int i = 0, s = states.size() ; i < s ; i++) { |
| if ((states.get(i).cid == cid) && (states.get(i).active != 0)) { |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| /** |
| * Handles changes to the APN database. |
| */ |
| private void onApnChanged() { |
| boolean isConnected; |
| |
| isConnected = (mState != State.IDLE && mState != State.FAILED); |
| |
| // The "current" may no longer be valid. MMS depends on this to send properly. |
| mGsmPhone.updateCurrentCarrierInProvider(); |
| |
| // TODO: It'd be nice to only do this if the changed entrie(s) |
| // match the current operator. |
| createAllApnList(); |
| if (mState != State.DISCONNECTING) { |
| cleanUpConnection(isConnected, Phone.REASON_APN_CHANGED); |
| if (!isConnected) { |
| // reset reconnect timer |
| mRetryMgr.resetRetryCount(); |
| mReregisterOnReconnectFailure = false; |
| trySetupData(Phone.REASON_APN_CHANGED); |
| } |
| } |
| } |
| |
| /** |
| * @param explicitPoll if true, indicates that *we* polled for this |
| * update while state == CONNECTED rather than having it delivered |
| * via an unsolicited response (which could have happened at any |
| * previous state |
| */ |
| private void onDataStateChanged (AsyncResult ar, boolean explicitPoll) { |
| ArrayList<DataCallState> dataCallStates; |
| |
| 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) { |
| // The way things are supposed to work, the PDP list |
| // should not contain the CID after it disconnects. |
| // However, the way things really work, sometimes the PDP |
| // context is still listed with active = false, which |
| // makes it hard to distinguish an activating context from |
| // an activated-and-then deactivated one. |
| if (!dataCallStatesHasCID(dataCallStates, mCidActive)) { |
| // It looks like the PDP context has deactivated. |
| // Tear everything down and try to reconnect. |
| |
| log("PDP connection has dropped. Reconnecting"); |
| |
| // Add an event log when the network drops PDP |
| GsmCellLocation loc = ((GsmCellLocation)mPhone.getCellLocation()); |
| EventLog.writeEvent(EventLogTags.PDP_NETWORK_DROP, |
| loc != null ? loc.getCid() : -1, |
| TelephonyManager.getDefault().getNetworkType()); |
| |
| cleanUpConnection(true, null); |
| return; |
| } else if (!dataCallStatesHasActiveCID(dataCallStates, mCidActive)) { |
| // Here, we only consider this authoritative if we asked for the |
| // PDP list. If it was an unsolicited response, we poll again |
| // to make sure everyone agrees on the initial state. |
| |
| if (!explicitPoll) { |
| // We think it disconnected but aren't sure...poll from our side |
| mPhone.mCM.getPDPContextList( |
| this.obtainMessage(EVENT_GET_PDP_LIST_COMPLETE)); |
| } else { |
| log("PDP connection has dropped (active=false case). " |
| + " Reconnecting"); |
| |
| // Log the network drop on the event log. |
| GsmCellLocation loc = ((GsmCellLocation)mPhone.getCellLocation()); |
| EventLog.writeEvent(EventLogTags.PDP_NETWORK_DROP, |
| loc != null ? loc.getCid() : -1, |
| TelephonyManager.getDefault().getNetworkType()); |
| |
| cleanUpConnection(true, null); |
| } |
| } |
| } |
| } |
| |
| private void notifyDefaultData(String reason) { |
| setState(State.CONNECTED); |
| notifyDataConnection(reason); |
| startNetStatPoll(); |
| // reset reconnect timer |
| mRetryMgr.resetRetryCount(); |
| mReregisterOnReconnectFailure = false; |
| } |
| |
| private void gotoIdleAndNotifyDataConnection(String reason) { |
| if (DBG) log("gotoIdleAndNotifyDataConnection: reason=" + reason); |
| setState(State.IDLE); |
| notifyDataConnection(reason); |
| mActiveApn = null; |
| } |
| |
| private void resetPollStats() { |
| mTxPkts = -1; |
| mRxPkts = -1; |
| mSentSinceLastRecv = 0; |
| mNetStatPollPeriod = POLL_NETSTAT_MILLIS; |
| mNoRecvPollCount = 0; |
| } |
| |
| private void doRecovery() { |
| if (mState == State.CONNECTED) { |
| int maxPdpReset = Settings.Secure.getInt(mResolver, |
| Settings.Secure.PDP_WATCHDOG_MAX_PDP_RESET_FAIL_COUNT, |
| DEFAULT_MAX_PDP_RESET_FAIL); |
| if (mPdpResetCount < maxPdpReset) { |
| mPdpResetCount++; |
| EventLog.writeEvent(EventLogTags.PDP_RADIO_RESET, mSentSinceLastRecv); |
| cleanUpConnection(true, Phone.REASON_PDP_RESET); |
| } else { |
| mPdpResetCount = 0; |
| EventLog.writeEvent(EventLogTags.PDP_REREGISTER_NETWORK, mSentSinceLastRecv); |
| mGsmPhone.mSST.reRegisterNetwork(null); |
| } |
| // TODO: Add increasingly drastic recovery steps, eg, |
| // reset the radio, reset the device. |
| } |
| } |
| |
| @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() { |
| log("************TURN OFF RADIO**************"); |
| cleanUpConnection(true, Phone.REASON_RADIO_TURNED_OFF); |
| mGsmPhone.mSST.powerOffRadioSafely(); |
| /* 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. |
| */ |
| |
| int reset = Integer.parseInt(SystemProperties.get("net.ppp.reset-by-timeout", "0")); |
| SystemProperties.set("net.ppp.reset-by-timeout", String.valueOf(reset+1)); |
| } |
| |
| 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; |
| mPdpResetCount = 0; |
| } 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; |
| mPdpResetCount = 0; |
| } else if (sent == 0 && received == 0) { |
| newActivity = Activity.NONE; |
| } else { |
| mSentSinceLastRecv = 0; |
| newActivity = Activity.NONE; |
| } |
| |
| if (mActivity != newActivity && mIsScreenOn) { |
| mActivity = newActivity; |
| mPhone.notifyDataActivity(); |
| } |
| } |
| |
| int watchdogTrigger = Settings.Secure.getInt(mResolver, |
| Settings.Secure.PDP_WATCHDOG_TRIGGER_PACKET_COUNT, |
| NUMBER_SENT_PACKETS_OF_HANG); |
| |
| if (mSentSinceLastRecv >= watchdogTrigger) { |
| // we already have NUMBER_SENT_PACKETS sent without ack |
| if (mNoRecvPollCount == 0) { |
| EventLog.writeEvent(EventLogTags.PDP_RADIO_RESET_COUNTDOWN_TRIGGERED, |
| mSentSinceLastRecv); |
| } |
| |
| int noRecvPollLimit = Settings.Secure.getInt(mResolver, |
| Settings.Secure.PDP_WATCHDOG_ERROR_POLL_COUNT, NO_RECV_POLL_LIMIT); |
| |
| if (mNoRecvPollCount < noRecvPollLimit) { |
| // It's possible the PDP context went down and we weren't notified. |
| // Start polling the context list in an attempt to recover. |
| if (DBG) log("no DATAIN in a while; polling PDP"); |
| mPhone.mCM.getDataCallList(obtainMessage(EVENT_GET_PDP_LIST_COMPLETE)); |
| |
| mNoRecvPollCount++; |
| |
| // Slow down the poll interval to let things happen |
| mNetStatPollPeriod = Settings.Secure.getInt(mResolver, |
| Settings.Secure.PDP_WATCHDOG_ERROR_POLL_INTERVAL_MS, |
| POLL_NETSTAT_SLOW_MILLIS); |
| } else { |
| if (DBG) log("Sent " + String.valueOf(mSentSinceLastRecv) + |
| " pkts since last received start recovery process"); |
| stopNetStatPoll(); |
| sendMessage(obtainMessage(EVENT_START_RECOVERY)); |
| } |
| } else { |
| mNoRecvPollCount = 0; |
| if (mIsScreenOn) { |
| mNetStatPollPeriod = Settings.Secure.getInt(mResolver, |
| Settings.Secure.PDP_WATCHDOG_POLL_INTERVAL_MS, POLL_NETSTAT_MILLIS); |
| } else { |
| mNetStatPollPeriod = Settings.Secure.getInt(mResolver, |
| Settings.Secure.PDP_WATCHDOG_LONG_POLL_INTERVAL_MS, |
| POLL_NETSTAT_SCREEN_OFF_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(GsmDataConnection.FailCause cause) { |
| return (cause != GsmDataConnection.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 (mState == State.FAILED) { |
| /** TODO: Retrieve retry manager from connection itself */ |
| if (!mRetryMgr.isRetryNeeded()) { |
| if (!mRequestedApnType.equals(Phone.APN_TYPE_DEFAULT)) { |
| // if no more retries on a secondary APN attempt, tell the world and revert. |
| notifyDataConnection(Phone.REASON_APN_FAILED); |
| onEnableApn(apnTypeToId(mRequestedApnType), DISABLED); |
| return; |
| } |
| if (mReregisterOnReconnectFailure) { |
| // We've re-registered once now just retry forever. |
| mRetryMgr.retryForeverUsingLastTimeout(); |
| } else { |
| // Try to re-register to the network. |
| log("PDP activate failed, Reregistering to the network"); |
| mReregisterOnReconnectFailure = true; |
| mGsmPhone.mSST.reRegisterNetwork(null); |
| mRetryMgr.resetRetryCount(); |
| return; |
| } |
| } |
| |
| int nextReconnectDelay = mRetryMgr.getRetryTimer(); |
| log("PDP activate failed. Scheduling next attempt for " |
| + (nextReconnectDelay / 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() + nextReconnectDelay, |
| mReconnectIntent); |
| |
| mRetryMgr.increaseRetryCount(); |
| |
| if (!shouldPostNotification(lastFailCauseCode)) { |
| log("NOT Posting GPRS Unavailable notification " |
| + "-- likely transient error"); |
| } else { |
| notifyNoData(lastFailCauseCode); |
| } |
| } |
| } |
| |
| private void notifyNoData(GsmDataConnection.FailCause lastFailCauseCode) { |
| setState(State.FAILED); |
| } |
| |
| private void onRecordsLoaded() { |
| createAllApnList(); |
| if (mState == State.FAILED) { |
| cleanUpConnection(false, null); |
| } |
| sendMessage(obtainMessage(EVENT_TRY_SETUP_DATA, Phone.REASON_SIM_LOADED)); |
| } |
| |
| @Override |
| protected void onEnableNewApn() { |
| log("onEnableNewApn E"); |
| // change our retry manager to use the appropriate numbers for the new APN |
| if (mRequestedApnType.equals(Phone.APN_TYPE_DEFAULT)) { |
| log("onEnableNewApn default type"); |
| mRetryMgr = mPendingDataConnection.getRetryMgr(); |
| mRetryMgr.resetRetryCount(); |
| } else if (mApnToDataConnectionId.get(mRequestedApnType) == null) { |
| log("onEnableNewApn mRequestedApnType=" + mRequestedApnType + |
| " missing, make a new connection"); |
| int id = createDataConnection(mRequestedApnType); |
| mRetryMgr = mDataConnections.get(id).getRetryMgr(); |
| mRetryMgr.resetRetryCount(); |
| } else { |
| log("oneEnableNewApn connection already exists, nothing to setup"); |
| } |
| |
| // TODO: To support simultaneous PDP contexts, this should really only call |
| // cleanUpConnection if it needs to free up a GsmDataConnection. |
| cleanUpConnection(true, Phone.REASON_APN_SWITCHED); |
| log("onEnableNewApn X"); |
| } |
| |
| @Override |
| protected boolean onTrySetupData(String reason) { |
| return trySetupData(reason); |
| } |
| |
| @Override |
| protected void onRoamingOff() { |
| trySetupData(Phone.REASON_ROAMING_OFF); |
| } |
| |
| @Override |
| 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 |
| 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"); |
| } |
| |
| if (mState != State.IDLE) { |
| cleanUpConnection(true, null); |
| } |
| } |
| |
| @Override |
| protected void onRadioOffOrNotAvailable() { |
| // Make sure our reconnect delay starts at the initial value |
| // next time the radio comes on |
| mRetryMgr.resetRetryCount(); |
| mReregisterOnReconnectFailure = false; |
| |
| 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"); |
| // TODO: Should we reset mRequestedApnType to "default"? |
| cleanUpConnection(false, Phone.REASON_RADIO_TURNED_OFF); |
| } |
| } |
| |
| @Override |
| protected void onDataSetupComplete(AsyncResult ar) { |
| /** TODO: Which connection is completing should be a parameter */ |
| String reason = null; |
| if (ar.userObj instanceof String) { |
| reason = (String) ar.userObj; |
| } |
| |
| if (ar.exception == null) { |
| if(DBG) { |
| log(String.format("onDataSetupComplete: success apn=%s", mWaitingApns.get(0).apn)); |
| } |
| // TODO: We should clear LinkProperties/Capabilities when torn down or disconnected |
| mLinkProperties = getLinkProperties(mPendingDataConnection); |
| mLinkCapabilities = getLinkCapabilities(mPendingDataConnection); |
| |
| ApnSetting apn = mPendingDataConnection.getApn(); |
| if (apn.proxy != null && apn.proxy.length() != 0) { |
| try { |
| ProxyProperties proxy = new ProxyProperties(apn.proxy, |
| Integer.parseInt(apn.port), null); |
| mLinkProperties.setHttpProxy(proxy); |
| } catch (NumberFormatException e) { |
| loge("NumberFormatException making ProxyProperties (" + apn.port + |
| "): " + e); |
| } |
| } |
| |
| // everything is setup |
| if (isApnTypeActive(Phone.APN_TYPE_DEFAULT)) { |
| SystemProperties.set("gsm.defaultpdpcontext.active", "true"); |
| if (canSetPreferApn && mPreferredApn == null) { |
| log("PREFERRED APN is null"); |
| mPreferredApn = mActiveApn; |
| setPreferredApn(mPreferredApn.id); |
| } |
| } else { |
| SystemProperties.set("gsm.defaultpdpcontext.active", "false"); |
| } |
| notifyDefaultData(reason); |
| |
| // TODO: For simultaneous PDP support, we need to build another |
| // trigger another TRY_SETUP_DATA for the next APN type. (Note |
| // that the existing connection may service that type, in which |
| // case we should try the next type, etc. |
| } else { |
| GsmDataConnection.FailCause cause; |
| cause = (GsmDataConnection.FailCause) (ar.result); |
| if (DBG) { |
| String apnString; |
| try { |
| apnString = mWaitingApns.get(0).apn; |
| } catch (Exception e) { |
| apnString = "<unknown>"; |
| } |
| log(String.format("onDataSetupComplete: error apn=%s cause=%s", apnString, cause)); |
| } |
| if (cause.isEventLoggable()) { |
| // Log this failure to the Event Logs. |
| GsmCellLocation loc = ((GsmCellLocation)mPhone.getCellLocation()); |
| EventLog.writeEvent(EventLogTags.PDP_SETUP_FAIL, |
| cause.ordinal(), loc != null ? loc.getCid() : -1, |
| TelephonyManager.getDefault().getNetworkType()); |
| } |
| |
| // Count permanent failures and remove the APN we just tried |
| mWaitingApnsPermanentFailureCountDown -= cause.isPermanentFail() ? 1 : 0; |
| mWaitingApns.remove(0); |
| if (DBG) log(String.format("onDataSetupComplete: mWaitingApns.size=%d" + |
| " mWaitingApnsPermanenatFailureCountDown=%d", |
| mWaitingApns.size(), mWaitingApnsPermanentFailureCountDown)); |
| |
| // See if there are more APN's to try |
| if (mWaitingApns.isEmpty()) { |
| if (mWaitingApnsPermanentFailureCountDown == 0) { |
| if (DBG) log("onDataSetupComplete: Permanent failures stop retrying"); |
| notifyNoData(cause); |
| notifyDataConnection(Phone.REASON_APN_FAILED); |
| } else { |
| if (DBG) log("onDataSetupComplete: Not all permanent failures, retry"); |
| startDelayedRetry(cause, reason); |
| } |
| } else { |
| if (DBG) log("onDataSetupComplete: Try next APN"); |
| setState(State.SCANNING); |
| // Wait a bit before trying the next APN, so that |
| // we're not tying up the RIL command channel |
| sendMessageDelayed(obtainMessage(EVENT_TRY_SETUP_DATA, reason), APN_DELAY_MILLIS); |
| } |
| } |
| } |
| |
| /** |
| * 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); |
| notifyDataConnection(reason); |
| mActiveApn = null; |
| if (retryAfterDisconnected(reason)) { |
| trySetupData(reason); |
| } |
| } |
| |
| /** |
| * Called when EVENT_RESET_DONE is received. |
| */ |
| @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); |
| } |
| |
| protected void onPollPdp() { |
| if (mState == State.CONNECTED) { |
| // only poll when connected |
| mPhone.mCM.getPDPContextList(this.obtainMessage(EVENT_GET_PDP_LIST_COMPLETE)); |
| sendMessageDelayed(obtainMessage(EVENT_POLL_PDP), POLL_PDP_MILLIS); |
| } |
| } |
| |
| @Override |
| protected void onVoiceCallStarted() { |
| if (mState == State.CONNECTED && ! mGsmPhone.mSST.isConcurrentVoiceAndData()) { |
| stopNetStatPoll(); |
| notifyDataConnection(Phone.REASON_VOICE_CALL_STARTED); |
| } |
| } |
| |
| @Override |
| protected void onVoiceCallEnded() { |
| if (mState == State.CONNECTED) { |
| if (!mGsmPhone.mSST.isConcurrentVoiceAndData()) { |
| startNetStatPoll(); |
| notifyDataConnection(Phone.REASON_VOICE_CALL_ENDED); |
| } else { |
| // clean slate after call end. |
| resetPollStats(); |
| } |
| } else { |
| // reset reconnect timer |
| mRetryMgr.resetRetryCount(); |
| mReregisterOnReconnectFailure = false; |
| // 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, String reason) { |
| cleanUpConnection(tearDown, reason); |
| } |
| |
| /** |
| * Based on the sim operator numeric, create a list for all possible |
| * Data Connections and setup the preferredApn. |
| */ |
| private void createAllApnList() { |
| mAllApns = new ArrayList<ApnSetting>(); |
| String operator = mGsmPhone.mSIMRecords.getSIMOperatorNumeric(); |
| |
| if (operator != null) { |
| String selection = "numeric = '" + operator + "'"; |
| |
| Cursor cursor = mPhone.getContext().getContentResolver().query( |
| Telephony.Carriers.CONTENT_URI, null, selection, null, null); |
| |
| if (cursor != null) { |
| if (cursor.getCount() > 0) { |
| mAllApns = createApnList(cursor); |
| } |
| cursor.close(); |
| } |
| } |
| |
| if (mAllApns.isEmpty()) { |
| if (DBG) log("No APN found for carrier: " + operator); |
| mPreferredApn = null; |
| notifyNoData(GsmDataConnection.FailCause.MISSING_UNKNOWN_APN); |
| } else { |
| mPreferredApn = getPreferredApn(); |
| log("Get PreferredAPN"); |
| if (mPreferredApn != null && !mPreferredApn.numeric.equals(operator)) { |
| mPreferredApn = null; |
| setPreferredApn(-1); |
| } |
| } |
| } |
| |
| /** Return the id for a new data connection */ |
| private int createDataConnection(String apnType) { |
| log("createDataConnection(" + apnType + ") E"); |
| RetryManager rm = new RetryManager(); |
| |
| if (apnType.equals(Phone.APN_TYPE_DEFAULT)) { |
| if (!rm.configure(SystemProperties.get("ro.gsm.data_retry_config"))) { |
| 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); |
| } |
| } |
| } else { |
| if (!rm.configure(SystemProperties.get("ro.gsm.2nd_data_retry_config"))) { |
| if (!rm.configure(SECONDARY_DATA_RETRY_CONFIG)) { |
| // Should never happen, log an error and default to a simple sequence. |
| log("Could note configure using SECONDARY_DATA_RETRY_CONFIG=" |
| + SECONDARY_DATA_RETRY_CONFIG); |
| rm.configure("max_retries=3, 333, 333, 333"); |
| } |
| } |
| } |
| |
| int id = mUniqueIdGenerator.getAndIncrement(); |
| DataConnection conn = GsmDataConnection.makeDataConnection(mGsmPhone, id, rm); |
| mDataConnections.put(id, conn); |
| mApnToDataConnectionId.put(apnType, id); |
| |
| log("createDataConnection(" + apnType + ") X id=" + id); |
| return id; |
| } |
| |
| private void destroyDataConnections() { |
| if(mDataConnections != null) { |
| log("destroyDataConnectionList clear mDataConnectionList"); |
| mDataConnections.clear(); |
| } else { |
| log("destroyDataConnectionList mDataConnecitonList is empty, ignore"); |
| } |
| } |
| |
| private ApnSetting fetchDunApn() { |
| Context c = mPhone.getContext(); |
| String apnData = Settings.Secure.getString(c.getContentResolver(), |
| Settings.Secure.TETHER_DUN_APN); |
| ApnSetting dunSetting = ApnSetting.fromString(apnData); |
| if (dunSetting != null) return dunSetting; |
| |
| apnData = c.getResources().getString(R.string.config_tether_apndata); |
| return ApnSetting.fromString(apnData); |
| } |
| |
| /** |
| * Build a list of APNs to be used to create PDP's. |
| * |
| * @param requestedApnType |
| * @return waitingApns list to be used to create PDP |
| * error when waitingApns.isEmpty() |
| */ |
| private ArrayList<ApnSetting> buildWaitingApns(String requestedApnType) { |
| ArrayList<ApnSetting> apnList = new ArrayList<ApnSetting>(); |
| |
| if (requestedApnType.equals(Phone.APN_TYPE_DUN)) { |
| ApnSetting dun = fetchDunApn(); |
| if (dun != null) apnList.add(dun); |
| return apnList; |
| } |
| |
| String operator = mGsmPhone.mSIMRecords.getSIMOperatorNumeric(); |
| |
| if (requestedApnType.equals(Phone.APN_TYPE_DEFAULT)) { |
| if (canSetPreferApn && mPreferredApn != null) { |
| log("Preferred APN:" + operator + ":" |
| + mPreferredApn.numeric + ":" + mPreferredApn); |
| if (mPreferredApn.numeric.equals(operator)) { |
| log("Waiting APN set to preferred APN"); |
| apnList.add(mPreferredApn); |
| return apnList; |
| } else { |
| setPreferredApn(-1); |
| mPreferredApn = null; |
| } |
| } |
| } |
| |
| if (mAllApns != null) { |
| for (ApnSetting apn : mAllApns) { |
| if (apn.canHandleType(requestedApnType)) { |
| apnList.add(apn); |
| } |
| } |
| } |
| return apnList; |
| } |
| |
| /** |
| * Get next apn in waitingApns |
| * @return the first apn found in waitingApns, null if none |
| */ |
| private ApnSetting getNextApn() { |
| ArrayList<ApnSetting> list = mWaitingApns; |
| ApnSetting apn = null; |
| |
| if (list != null) { |
| if (!list.isEmpty()) { |
| apn = list.get(0); |
| } |
| } |
| return apn; |
| } |
| |
| private String apnListToString (ArrayList<ApnSetting> apns) { |
| StringBuilder result = new StringBuilder(); |
| for (int i = 0, size = apns.size(); i < size; i++) { |
| result.append('[') |
| .append(apns.get(i).toString()) |
| .append(']'); |
| } |
| return result.toString(); |
| } |
| |
| private void startDelayedRetry(GsmDataConnection.FailCause cause, String reason) { |
| notifyNoData(cause); |
| reconnectAfterFail(cause, reason); |
| } |
| |
| private void setPreferredApn(int pos) { |
| if (!canSetPreferApn) { |
| return; |
| } |
| |
| ContentResolver resolver = mPhone.getContext().getContentResolver(); |
| resolver.delete(PREFERAPN_URI, null, null); |
| |
| if (pos >= 0) { |
| ContentValues values = new ContentValues(); |
| values.put(APN_ID, pos); |
| resolver.insert(PREFERAPN_URI, values); |
| } |
| } |
| |
| private ApnSetting getPreferredApn() { |
| if (mAllApns.isEmpty()) { |
| return null; |
| } |
| |
| Cursor cursor = mPhone.getContext().getContentResolver().query( |
| PREFERAPN_URI, new String[] { "_id", "name", "apn" }, |
| null, null, Telephony.Carriers.DEFAULT_SORT_ORDER); |
| |
| if (cursor != null) { |
| canSetPreferApn = true; |
| } else { |
| canSetPreferApn = false; |
| } |
| |
| if (canSetPreferApn && cursor.getCount() > 0) { |
| int pos; |
| cursor.moveToFirst(); |
| pos = cursor.getInt(cursor.getColumnIndexOrThrow(Telephony.Carriers._ID)); |
| for(ApnSetting p:mAllApns) { |
| if (p.id == pos && p.canHandleType(mRequestedApnType)) { |
| cursor.close(); |
| return p; |
| } |
| } |
| } |
| |
| if (cursor != null) { |
| cursor.close(); |
| } |
| |
| return null; |
| } |
| |
| @Override |
| public void handleMessage (Message msg) { |
| if (DBG) log("GSMDataConnTrack handleMessage "+msg); |
| |
| if (!mGsmPhone.mIsTheCurrentActivePhone) { |
| log("Ignore GSM msgs since GSM phone is inactive"); |
| return; |
| } |
| |
| switch (msg.what) { |
| case EVENT_RECORDS_LOADED: |
| onRecordsLoaded(); |
| break; |
| |
| case EVENT_GPRS_DETACHED: |
| onGprsDetached(); |
| break; |
| |
| case EVENT_GPRS_ATTACHED: |
| onGprsAttached(); |
| break; |
| |
| case EVENT_DATA_STATE_CHANGED: |
| onDataStateChanged((AsyncResult) msg.obj, false); |
| break; |
| |
| case EVENT_GET_PDP_LIST_COMPLETE: |
| onDataStateChanged((AsyncResult) msg.obj, true); |
| break; |
| |
| case EVENT_POLL_PDP: |
| onPollPdp(); |
| break; |
| |
| case EVENT_START_NETSTAT_POLL: |
| startNetStatPoll(); |
| break; |
| |
| case EVENT_START_RECOVERY: |
| doRecovery(); |
| break; |
| |
| case EVENT_APN_CHANGED: |
| onApnChanged(); |
| break; |
| |
| case EVENT_PS_RESTRICT_ENABLED: |
| /** |
| * We don't need to explicitly to tear down the PDP context |
| * when PS restricted is enabled. The base band will deactive |
| * PDP context and notify us with PDP_CONTEXT_CHANGED. |
| * But we should stop the network polling and prevent reset PDP. |
| */ |
| log("[DSAC DEB] " + "EVENT_PS_RESTRICT_ENABLED " + mIsPsRestricted); |
| stopNetStatPoll(); |
| mIsPsRestricted = true; |
| break; |
| |
| case EVENT_PS_RESTRICT_DISABLED: |
| /** |
| * When PS restrict is removed, we need setup PDP connection if |
| * PDP connection is down. |
| */ |
| log("[DSAC DEB] " + "EVENT_PS_RESTRICT_DISABLED " + mIsPsRestricted); |
| mIsPsRestricted = false; |
| if (mState == State.CONNECTED) { |
| startNetStatPoll(); |
| } else { |
| if (mState == State.FAILED) { |
| cleanUpConnection(false, Phone.REASON_PS_RESTRICT_ENABLED); |
| mRetryMgr.resetRetryCount(); |
| mReregisterOnReconnectFailure = false; |
| } |
| trySetupData(Phone.REASON_PS_RESTRICT_ENABLED); |
| } |
| break; |
| |
| default: |
| // handle the message in the super class DataConnectionTracker |
| super.handleMessage(msg); |
| break; |
| } |
| } |
| |
| @Override |
| protected void log(String s) { |
| Log.d(LOG_TAG, "[GsmDataConnectionTracker] " + s); |
| } |
| |
| @Override |
| protected void loge(String s) { |
| Log.e(LOG_TAG, "[GsmDataConnectionTracker] " + s); |
| } |
| } |