| /* |
| * 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.BroadcastReceiver; |
| import android.content.ContentResolver; |
| import android.content.Context; |
| import android.content.Intent; |
| import android.content.IntentFilter; |
| import android.content.SharedPreferences; |
| import android.database.ContentObserver; |
| import android.database.Cursor; |
| import android.net.NetworkInfo; |
| import android.net.wifi.WifiManager; |
| import android.os.AsyncResult; |
| import android.os.Handler; |
| import android.os.INetStatService; |
| 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.provider.Checkin; |
| import android.provider.Settings; |
| import android.provider.Telephony; |
| import android.provider.Settings.SettingNotFoundException; |
| import android.telephony.ServiceState; |
| import android.telephony.TelephonyManager; |
| import android.telephony.gsm.GsmCellLocation; |
| import android.text.TextUtils; |
| import android.util.EventLog; |
| import android.util.Log; |
| |
| import com.android.internal.telephony.Phone; |
| import com.android.internal.telephony.gsm.PdpConnection.PdpFailCause; |
| |
| import java.io.IOException; |
| import java.util.ArrayList; |
| |
| /** |
| * {@hide} |
| */ |
| final class DataConnectionTracker extends Handler |
| { |
| private static final String LOG_TAG = "GSM"; |
| private static final boolean DBG = true; |
| |
| /** |
| * IDLE: ready to start data connection setup, default state |
| * INITING: state of issued setupDefaultPDP() but not finish yet |
| * CONNECTING: state of issued startPppd() but not finish yet |
| * SCANNING: data connection fails with one apn but other apns are available |
| * ready to start data connection on other apns (before INITING) |
| * CONNECTED: IP connection is setup |
| * DISCONNECTING: PdpConnection.disconnect() has been called, but PDP |
| * context is not yet deactivated |
| * FAILED: data connection fail for all apns settings |
| * |
| * getDataConnectionState() maps State to DataState |
| * FAILED or IDLE : DISCONNECTED |
| * INITING or CONNECTING or SCANNING: CONNECTING |
| * CONNECTED : CONNECTED or DISCONNECTING |
| */ |
| enum State { |
| IDLE, |
| INITING, |
| CONNECTING, |
| SCANNING, |
| CONNECTED, |
| DISCONNECTING, |
| FAILED |
| } |
| |
| enum Activity { |
| NONE, |
| DATAIN, |
| DATAOUT, |
| DATAINANDOUT |
| } |
| |
| /** |
| * 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 |
| |
| GSMPhone phone; |
| INetStatService netstat; |
| State state = State.IDLE; |
| Activity activity = Activity.NONE; |
| boolean netStatPollEnabled = false; |
| // Indicates baseband will not auto-attach |
| private boolean noAutoAttach = false; |
| long nextReconnectDelay = RECONNECT_DELAY_INITIAL_MILLIS; |
| Handler mDataConnectionTracker = null; |
| private ContentResolver mResolver; |
| |
| long txPkts, rxPkts, sentSinceLastRecv; |
| int netStatPollPeriod; |
| private int mNoRecvPollCount = 0; |
| private boolean mPingTestActive = false; |
| // Count of PDP reset attempts; reset when we see incoming, |
| // call reRegisterNetwork, or pingTest succeeds. |
| private int mPdpResetCount = 0; |
| private boolean mIsScreenOn = true; |
| |
| //useful for debugging |
| boolean failNextConnect = false; |
| |
| /** |
| * allApns holds all apns for this sim spn, retrieved from |
| * the Carrier DB. |
| * |
| * Create once after simcard info is loaded |
| */ |
| private ArrayList<ApnSetting> allApns = 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> waitingApns = null; |
| |
| /** |
| * pdpList holds all the PDP connection, i.e. IP Link in GPRS |
| */ |
| private ArrayList<PdpConnection> pdpList; |
| |
| /** CID of active PDP */ |
| int cidActive; |
| |
| /** Currently requested APN type */ |
| private String mRequestedApnType = Phone.APN_TYPE_DEFAULT; |
| |
| /** Currently active APN */ |
| private ApnSetting mActiveApn; |
| |
| /** Currently active PdpConnection */ |
| private PdpConnection mActivePdp; |
| |
| private static int APN_DEFAULT_ID = 0; |
| private static int APN_MMS_ID = 1; |
| private static int APN_NUM_TYPES = 2; |
| |
| private boolean[] dataEnabled = new boolean[APN_NUM_TYPES]; |
| |
| // wifi connection status will be updated by sticky intent |
| private boolean mIsWifiConnected = false; |
| |
| /** Intent sent when the reconnect alarm fires. */ |
| private PendingIntent mReconnectIntent = null; |
| |
| //***** Constants |
| |
| // TODO: Increase this to match the max number of simultaneous |
| // PDP contexts we plan to support. |
| /** |
| * Pool size of PdpConnection objects. |
| */ |
| private static final int PDP_CONNECTION_POOL_SIZE = 1; |
| |
| private static final int POLL_PDP_MILLIS = 5 * 1000; |
| private static final int RECONNECT_DELAY_INITIAL_MILLIS = 5 * 1000; |
| /** Cap out with 1 hour retry interval. */ |
| private static final int RECONNECT_DELAY_MAX_MILLIS = 60 * 60 * 1000; |
| |
| /** Slow poll when attempting connection recovery. */ |
| private static final int POLL_NETSTAT_SLOW_MILLIS = 5000; |
| |
| /** Default ping deadline, in seconds. */ |
| private final int DEFAULT_PING_DEADLINE = 5; |
| /** Default max failure count before attempting to network re-registration. */ |
| private final int DEFAULT_MAX_PDP_RESET_FAIL = 3; |
| |
| /** |
| * After detecting a potential connection problem, this is the max number |
| * of subsequent polls before attempting a radio reset. At this point, |
| * poll interval is 5 seconds (POLL_NETSTAT_SLOW_MILLIS), so set this to |
| * poll for about 2 more minutes. |
| */ |
| private static final int NO_RECV_POLL_LIMIT = 24; |
| |
| // 1 sec. default polling interval when screen is on. |
| private static final int POLL_NETSTAT_MILLIS = 1000; |
| // 10 min. default polling interval when screen is off. |
| private static final int POLL_NETSTAT_SCREEN_OFF_MILLIS = 1000*60*10; |
| // 2 min for round trip time |
| private static final int POLL_LONGEST_RTT = 120 * 1000; |
| // 10 for packets without ack |
| private static final int NUMBER_SENT_PACKETS_OF_HANG = 10; |
| // how long to wait before switching back to default APN |
| private static final int RESTORE_DEFAULT_APN_DELAY = 1 * 60 * 1000; |
| // system property that can override the above value |
| private static final String APN_RESTORE_DELAY_PROP_NAME = "android.telephony.apn-restore"; |
| // represents an invalid IP address |
| private static final String NULL_IP = "0.0.0.0"; |
| |
| private static final String INTENT_RECONNECT_ALARM = "com.android.internal.telephony.gprs-reconnect"; |
| private static final String INTENT_RECONNECT_ALARM_EXTRA_REASON = "reason"; |
| |
| |
| //***** Event Codes |
| static final int EVENT_DATA_SETUP_COMPLETE = 1; |
| static final int EVENT_RADIO_AVAILABLE = 3; |
| static final int EVENT_RECORDS_LOADED = 4; |
| static final int EVENT_TRY_SETUP_DATA = 5; |
| static final int EVENT_PDP_STATE_CHANGED = 6; |
| static final int EVENT_POLL_PDP = 7; |
| static final int EVENT_GET_PDP_LIST_COMPLETE = 11; |
| static final int EVENT_RADIO_OFF_OR_NOT_AVAILABLE = 12; |
| static final int EVENT_VOICE_CALL_STARTED = 14; |
| static final int EVENT_VOICE_CALL_ENDED = 15; |
| static final int EVENT_GPRS_DETACHED = 19; |
| static final int EVENT_LINK_STATE_CHANGED = 20; |
| static final int EVENT_ROAMING_ON = 21; |
| static final int EVENT_ROAMING_OFF = 22; |
| static final int EVENT_ENABLE_NEW_APN = 23; |
| static final int EVENT_RESTORE_DEFAULT_APN = 24; |
| static final int EVENT_DISCONNECT_DONE = 25; |
| static final int EVENT_GPRS_ATTACHED = 26; |
| static final int EVENT_START_NETSTAT_POLL = 27; |
| static final int EVENT_START_RECOVERY = 28; |
| static final int EVENT_APN_CHANGED = 29; |
| |
| 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, "GPRS 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; |
| } |
| } |
| } |
| }; |
| |
| /** Watches for changes to the APN db. */ |
| private ApnChangeObserver apnObserver; |
| |
| //***** Constructor |
| |
| DataConnectionTracker(GSMPhone phone) |
| { |
| this.phone = phone; |
| phone.mCM.registerForAvailable (this, EVENT_RADIO_AVAILABLE, null); |
| phone.mCM.registerForOffOrNotAvailable(this, EVENT_RADIO_OFF_OR_NOT_AVAILABLE, null); |
| phone.mSIMRecords.registerForRecordsLoaded(this, EVENT_RECORDS_LOADED, null); |
| phone.mCM.registerForPDPStateChanged (this, EVENT_PDP_STATE_CHANGED, null); |
| phone.mCT.registerForVoiceCallEnded (this, EVENT_VOICE_CALL_ENDED, null); |
| phone.mCT.registerForVoiceCallStarted (this, EVENT_VOICE_CALL_STARTED, null); |
| phone.mSST.registerForGprsAttached(this, EVENT_GPRS_ATTACHED, null); |
| phone.mSST.registerForGprsDetached(this, EVENT_GPRS_DETACHED, null); |
| phone.mSST.registerForRoamingOn(this, EVENT_ROAMING_ON, null); |
| phone.mSST.registerForRoamingOff(this, EVENT_ROAMING_OFF, null); |
| |
| this.netstat = INetStatService.Stub.asInterface(ServiceManager.getService("netstat")); |
| |
| 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); |
| |
| phone.getContext().registerReceiver(mIntentReceiver, filter, null, phone.h); |
| |
| |
| mDataConnectionTracker = this; |
| mResolver = phone.getContext().getContentResolver(); |
| |
| apnObserver = new ApnChangeObserver(); |
| phone.getContext().getContentResolver().registerContentObserver( |
| Telephony.Carriers.CONTENT_URI, true, apnObserver); |
| |
| createAllPdpList(); |
| |
| // 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()); |
| dataEnabled[APN_DEFAULT_ID] = !sp.getBoolean(GSMPhone.DATA_DISABLED_ON_BOOT_KEY, false); |
| noAutoAttach = !dataEnabled[APN_DEFAULT_ID]; |
| } |
| |
| void setState(State s) { |
| if (DBG) log ("setState: " + s); |
| if (state != s) { |
| if (s == State.INITING) { // request PDP context |
| Checkin.updateStats( |
| phone.getContext().getContentResolver(), |
| Checkin.Stats.Tag.PHONE_GPRS_ATTEMPTED, 1, 0.0); |
| } |
| |
| if (s == State.CONNECTED) { // pppd is up |
| Checkin.updateStats( |
| phone.getContext().getContentResolver(), |
| Checkin.Stats.Tag.PHONE_GPRS_CONNECTED, 1, 0.0); |
| } |
| } |
| |
| state = s; |
| |
| if (state == State.FAILED) { |
| if (waitingApns != null) |
| waitingApns.clear(); // when teardown the connection and set to IDLE |
| } |
| } |
| |
| String getStateInString() { |
| switch (state) { |
| case IDLE: return "IDLE"; |
| case INITING: return "INIT"; |
| case CONNECTING: return "CING"; |
| case SCANNING: return "SCAN"; |
| case CONNECTED: return "CNTD"; |
| case DISCONNECTING: return "DING"; |
| case FAILED: return "FAIL"; |
| default: return "ERRO"; |
| } |
| } |
| |
| String[] getActiveApnTypes() { |
| String[] result; |
| if (mActiveApn != null) { |
| result = mActiveApn.types; |
| } else { |
| result = new String[1]; |
| result[0] = Phone.APN_TYPE_DEFAULT; |
| } |
| return result; |
| } |
| |
| String getActiveApnString() { |
| String result = null; |
| if (mActiveApn != null) { |
| result = mActiveApn.apn; |
| } |
| return result; |
| } |
| |
| /** |
| * Ensure that we are connected to an APN of the specified type. |
| * @param type the APN type (currently the only valid value |
| * is {@link Phone#APN_TYPE_MMS}) |
| * @return the result of the operation. Success is indicated by |
| * a return value of either {@code Phone.APN_ALREADY_ACTIVE} or |
| * {@code Phone.APN_REQUEST_STARTED}. In the latter case, a broadcast |
| * will be sent by the ConnectivityManager when a connection to |
| * the APN has been established. |
| */ |
| int enableApnType(String type) { |
| if (!TextUtils.equals(type, Phone.APN_TYPE_MMS)) { |
| return Phone.APN_REQUEST_FAILED; |
| } |
| // If already active, return |
| Log.d(LOG_TAG, "enableApnType("+type+")"); |
| if (isApnTypeActive(type)) { |
| setEnabled(type, true); |
| removeMessages(EVENT_RESTORE_DEFAULT_APN); |
| /** |
| * We're being asked to enable a non-default APN that's already in use. |
| * This means we should restart the timer that will automatically |
| * switch back to the default APN and disable the non-default APN |
| * when it expires. |
| */ |
| sendMessageDelayed( |
| obtainMessage(EVENT_RESTORE_DEFAULT_APN), |
| getRestoreDefaultApnDelay()); |
| if (state == State.INITING) return Phone.APN_REQUEST_STARTED; |
| else if (state == State.CONNECTED) return Phone.APN_ALREADY_ACTIVE; |
| } |
| |
| if (!isApnTypeAvailable(type)) { |
| return Phone.APN_TYPE_NOT_AVAILABLE; |
| } |
| |
| setEnabled(type, true); |
| mRequestedApnType = type; |
| sendMessage(obtainMessage(EVENT_ENABLE_NEW_APN)); |
| return Phone.APN_REQUEST_STARTED; |
| } |
| |
| /** |
| * The APN of the specified type is no longer needed. Ensure that if |
| * use of the default APN has not been explicitly disabled, we are connected |
| * to the default APN. |
| * @param type the APN type. The only valid value currently is {@link Phone#APN_TYPE_MMS}. |
| * @return |
| */ |
| int disableApnType(String type) { |
| Log.d(LOG_TAG, "disableApnType("+type+")"); |
| if (TextUtils.equals(type, Phone.APN_TYPE_MMS)) { |
| removeMessages(EVENT_RESTORE_DEFAULT_APN); |
| setEnabled(type, false); |
| if (isApnTypeActive(Phone.APN_TYPE_DEFAULT)) { |
| if (dataEnabled[APN_DEFAULT_ID]) { |
| return Phone.APN_ALREADY_ACTIVE; |
| } else { |
| cleanUpConnection(true, Phone.REASON_DATA_DISABLED); |
| return Phone.APN_REQUEST_STARTED; |
| } |
| } else { |
| /* |
| * Note that if default data is disabled, the following |
| * has the effect of disabling the MMS APN, and then |
| * ignoring the request to enable the default APN. |
| * The net result is that data is completely disabled. |
| */ |
| sendMessage(obtainMessage(EVENT_RESTORE_DEFAULT_APN)); |
| return Phone.APN_REQUEST_STARTED; |
| } |
| } else { |
| return Phone.APN_REQUEST_FAILED; |
| } |
| } |
| |
| /** |
| * 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. |
| */ |
| boolean isDataConnectionAsDesired() { |
| boolean roaming = phone.getServiceState().getRoaming(); |
| |
| if (phone.mSIMRecords.getRecordsLoaded() && |
| phone.mSST.getCurrentGprsState() == ServiceState.STATE_IN_SERVICE && |
| (!roaming || getDataOnRoamingEnabled()) && |
| !mIsWifiConnected ) { |
| return (state == State.CONNECTED); |
| } |
| return true; |
| } |
| |
| private boolean isApnTypeActive(String type) { |
| // TODO: to support simultaneous, mActiveApn can be a List instead. |
| return mActiveApn != null && mActiveApn.canHandleType(type); |
| } |
| |
| private boolean isApnTypeAvailable(String type) { |
| if (allApns != null) { |
| for (ApnSetting apn : allApns) { |
| if (apn.canHandleType(type)) { |
| return true; |
| } |
| } |
| } |
| return false; |
| } |
| |
| private boolean isEnabled(String apnType) { |
| if (TextUtils.equals(apnType, Phone.APN_TYPE_DEFAULT)) { |
| return dataEnabled[APN_DEFAULT_ID]; |
| } else if (TextUtils.equals(apnType, Phone.APN_TYPE_MMS)) { |
| return dataEnabled[APN_MMS_ID]; |
| } else { |
| return false; |
| } |
| } |
| |
| private void setEnabled(String apnType, boolean enable) { |
| Log.d(LOG_TAG, "setEnabled(" + apnType + ", " + enable + ')'); |
| if (TextUtils.equals(apnType, Phone.APN_TYPE_DEFAULT)) { |
| dataEnabled[APN_DEFAULT_ID] = enable; |
| } else if (TextUtils.equals(apnType, Phone.APN_TYPE_MMS)) { |
| dataEnabled[APN_MMS_ID] = enable; |
| } |
| Log.d(LOG_TAG, "dataEnabled[DEFAULT_APN]=" + dataEnabled[APN_DEFAULT_ID] + |
| " dataEnabled[MMS_APN]=" + dataEnabled[APN_MMS_ID]); |
| } |
| |
| /** |
| * Prevent mobile data connections from being established, |
| * or once again allow mobile data connections. If the state |
| * toggles, then either tear down or set up data, as |
| * appropriate to match the new state. |
| * <p>This operation only affects the default APN, and if the same APN is |
| * currently being used for MMS traffic, the teardown will not happen |
| * even when {@code enable} is {@code false}.</p> |
| * @param enable indicates whether to enable ({@code true}) or disable ({@code false}) data |
| * @return {@code true} if the operation succeeded |
| */ |
| public boolean setDataEnabled(boolean enable) { |
| boolean isEnabled = isEnabled(Phone.APN_TYPE_DEFAULT); |
| Log.d(LOG_TAG, "setDataEnabled("+enable+") isEnabled=" + isEnabled); |
| if (!isEnabled && enable) { |
| setEnabled(Phone.APN_TYPE_DEFAULT, true); |
| // trySetupData() will be a no-op if we are currently |
| // connected to the MMS APN |
| return trySetupData(Phone.REASON_DATA_ENABLED); |
| } else if (!enable) { |
| setEnabled(Phone.APN_TYPE_DEFAULT, false); |
| // Don't tear down if there is an active APN and it handles MMS. |
| // TODO: This isn't very general. |
| if (!isApnTypeActive(Phone.APN_TYPE_MMS) || !isEnabled(Phone.APN_TYPE_MMS)) { |
| cleanUpConnection(true, Phone.REASON_DATA_DISABLED); |
| return true; |
| } |
| return false; |
| } else // isEnabled && enable |
| return true; |
| } |
| |
| /** |
| * Report the current state of data connectivity (enabled or disabled) for |
| * the default APN. |
| * @return {@code false} if data connectivity has been explicitly disabled, |
| * {@code true} otherwise. |
| */ |
| public boolean getDataEnabled() { |
| return dataEnabled[APN_DEFAULT_ID]; |
| } |
| |
| /** |
| * Report on whether data connectivity is enabled for any APN. |
| * @return {@code false} if data connectivity has been explicitly disabled, |
| * {@code true} otherwise. |
| */ |
| public boolean getAnyDataEnabled() { |
| return dataEnabled[APN_DEFAULT_ID] || dataEnabled[APN_MMS_ID]; |
| } |
| |
| //The data roaming setting is now located in the shared preferences. |
| // See if the requested preference value is the same as that stored in |
| // the shared values. If it is not, then update it. |
| public void setDataOnRoamingEnabled(boolean enabled) { |
| if (getDataOnRoamingEnabled() != enabled) { |
| Settings.Secure.putInt(phone.getContext().getContentResolver(), |
| Settings.Secure.DATA_ROAMING, enabled ? 1 : 0); |
| } |
| Message roamingMsg = phone.getServiceState().getRoaming() ? |
| obtainMessage(EVENT_ROAMING_ON) : obtainMessage(EVENT_ROAMING_OFF); |
| sendMessage(roamingMsg); |
| } |
| |
| //Retrieve the data roaming setting from the shared preferences. |
| public boolean getDataOnRoamingEnabled() { |
| try { |
| return Settings.Secure.getInt(phone.getContext().getContentResolver(), |
| Settings.Secure.DATA_ROAMING) > 0; |
| } catch (SettingNotFoundException snfe) { |
| return false; |
| } |
| } |
| |
| public ArrayList<PdpConnection> getAllPdps() { |
| ArrayList<PdpConnection> pdps = (ArrayList<PdpConnection>)pdpList.clone(); |
| return pdps; |
| } |
| |
| private boolean isDataAllowed() { |
| boolean roaming = phone.getServiceState().getRoaming(); |
| return getAnyDataEnabled() && (!roaming || getDataOnRoamingEnabled()); |
| } |
| |
| //****** Called from ServiceStateTracker |
| /** |
| * Invoked when ServiceStateTracker observes a transition from GPRS |
| * attach to detach. |
| */ |
| private 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(); |
| phone.notifyDataConnection(Phone.REASON_GPRS_DETACHED); |
| } |
| |
| private void onGprsAttached() { |
| if (state == State.CONNECTED) { |
| startNetStatPoll(); |
| phone.notifyDataConnection(Phone.REASON_GPRS_ATTACHED); |
| } else { |
| if (state == State.FAILED) { |
| cleanUpConnection(false, Phone.REASON_GPRS_ATTACHED); |
| nextReconnectDelay = RECONNECT_DELAY_INITIAL_MILLIS; |
| } |
| trySetupData(Phone.REASON_GPRS_ATTACHED); |
| } |
| } |
| |
| 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 gprsState = phone.mSST.getCurrentGprsState(); |
| boolean roaming = phone.getServiceState().getRoaming(); |
| |
| if ((state == State.IDLE || state == State.SCANNING) |
| && (gprsState == ServiceState.STATE_IN_SERVICE || noAutoAttach) |
| && phone.mSIMRecords.getRecordsLoaded() |
| && ( phone.mSST.isConcurrentVoiceAndData() || |
| phone.getState() == Phone.State.IDLE ) |
| && isDataAllowed()) { |
| |
| if (state == State.IDLE) { |
| waitingApns = buildWaitingApns(); |
| if (waitingApns.isEmpty()) { |
| if (DBG) log("No APN found"); |
| notifyNoData(PdpConnection.PdpFailCause.BAD_APN); |
| return false; |
| } else { |
| log ("Create from allApns : " + apnListToString(allApns)); |
| } |
| } |
| |
| if (DBG) { |
| log ("Setup watingApns : " + apnListToString(waitingApns)); |
| } |
| return setupData(reason); |
| } else { |
| if (DBG) |
| log("trySetupData: Not ready for data: " + |
| " dataState=" + state + |
| " gprsState=" + gprsState + |
| " sim=" + phone.mSIMRecords.getRecordsLoaded() + |
| " UMTS=" + phone.mSST.isConcurrentVoiceAndData() + |
| " phoneState=" + phone.getState() + |
| " dataEnabled=" + getAnyDataEnabled() + |
| " roaming=" + roaming + |
| " dataOnRoamingEnable=" + getDataOnRoamingEnabled()); |
| 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 PdpConnection should be |
| * disconnected. |
| * @param reason 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) phone.getContext().getSystemService(Context.ALARM_SERVICE); |
| am.cancel(mReconnectIntent); |
| mReconnectIntent = null; |
| } |
| |
| for (PdpConnection pdp : pdpList) { |
| if (tearDown) { |
| Message msg = obtainMessage(EVENT_DISCONNECT_DONE, reason); |
| pdp.disconnect(msg); |
| } else { |
| pdp.clearSettings(); |
| } |
| } |
| stopNetStatPoll(); |
| |
| /* |
| * If we've been asked to tear down the connection, |
| * set the state to DISCONNECTING. However, there's |
| * a race that can occur if for some reason we were |
| * already in the IDLE state. In that case, the call |
| * to pdp.disconnect() above will immediately post |
| * a message to the handler thread that the disconnect |
| * is done, and if the handler runs before the code |
| * below does, the handler will have set the state to |
| * IDLE before the code below runs. If we didn't check |
| * for that, future calls to trySetupData would fail, |
| * and we would never get out of the DISCONNECTING state. |
| */ |
| if (!tearDown) { |
| setState(State.IDLE); |
| phone.notifyDataConnection(reason); |
| mActiveApn = null; |
| } else if (state != State.IDLE) { |
| setState(State.DISCONNECTING); |
| } |
| } |
| |
| /** |
| * @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.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)), |
| types); |
| result.add(apn); |
| } while (cursor.moveToNext()); |
| } |
| return result; |
| } |
| |
| private PdpConnection findFreePdp() { |
| for (PdpConnection pdp : pdpList) { |
| if (pdp.getState() == PdpConnection.PdpState.INACTIVE) { |
| return pdp; |
| } |
| } |
| return null; |
| } |
| |
| private boolean setupData(String reason) { |
| ApnSetting apn; |
| PdpConnection pdp; |
| |
| apn = getNextApn(); |
| if (apn == null) return false; |
| pdp = findFreePdp(); |
| if (pdp == null) { |
| if (DBG) log("setupData: No free PdpConnection found!"); |
| return false; |
| } |
| mActiveApn = apn; |
| mActivePdp = pdp; |
| |
| Message msg = obtainMessage(); |
| msg.what = EVENT_DATA_SETUP_COMPLETE; |
| msg.obj = reason; |
| pdp.connect(apn, msg); |
| |
| setState(State.INITING); |
| phone.notifyDataConnection(reason); |
| return true; |
| } |
| |
| String getInterfaceName(String apnType) { |
| if (mActivePdp != null |
| && (apnType == null || mActiveApn.canHandleType(apnType))) { |
| return mActivePdp.getInterface(); |
| } |
| return null; |
| } |
| |
| String getIpAddress(String apnType) { |
| if (mActivePdp != null |
| && (apnType == null || mActiveApn.canHandleType(apnType))) { |
| return mActivePdp.getIpAddress(); |
| } |
| return null; |
| } |
| |
| String getGateway(String apnType) { |
| if (mActivePdp != null |
| && (apnType == null || mActiveApn.canHandleType(apnType))) { |
| return mActivePdp.getGatewayAddress(); |
| } |
| return null; |
| } |
| |
| String[] getDnsServers(String apnType) { |
| if (mActivePdp != null |
| && (apnType == null || mActiveApn.canHandleType(apnType))) { |
| return mActivePdp.getDnsServers(); |
| } |
| return null; |
| } |
| |
| private boolean |
| pdpStatesHasCID (ArrayList<PDPContextState> 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 |
| pdpStatesHasActiveCID (ArrayList<PDPContextState> states, int cid) |
| { |
| for (int i = 0, s = states.size() ; i < s ; i++) { |
| if (states.get(i).cid == cid) return states.get(i).active; |
| } |
| |
| return false; |
| } |
| |
| /** |
| * Handles changes to the APN database. |
| */ |
| private void onApnChanged() { |
| boolean isConnected; |
| |
| isConnected = (state != State.IDLE && state != State.FAILED); |
| |
| // TODO: It'd be nice to only do this if the changed entrie(s) |
| // match the current operator. |
| createAllApnList(); |
| if (state != State.DISCONNECTING) { |
| cleanUpConnection(isConnected, Phone.REASON_APN_CHANGED); |
| if (!isConnected) { |
| 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 |
| onPdpStateChanged (AsyncResult ar, boolean explicitPoll) |
| { |
| ArrayList<PDPContextState> pdpStates; |
| |
| pdpStates = (ArrayList<PDPContextState>)(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; |
| } |
| |
| |
| // This is how things are supposed to work: |
| // The PDP list is supposed to be empty of the CID |
| // when it disconnects |
| |
| if (state == State.CONNECTED |
| && !pdpStatesHasCID(pdpStates, cidActive)) { |
| |
| // It looks like the PDP context has deactivated |
| // Tear everything down and try to reconnect |
| |
| Log.i(LOG_TAG, "PDP connection has dropped. Reconnecting"); |
| |
| // Add an event log when the network drops PDP |
| int cid = -1; |
| GsmCellLocation loc = ((GsmCellLocation)phone.getCellLocation()); |
| if (loc != null) cid = loc.getCid(); |
| |
| EventLog.List val = new EventLog.List(cid, |
| TelephonyManager.getDefault().getNetworkType()); |
| |
| EventLog.writeEvent(TelephonyEventLog.EVENT_LOG_PDP_NETWORK_DROP, val); |
| |
| cleanUpConnection(true, null); |
| |
| return; |
| } |
| |
| if (true) { |
| // |
| // Workaround for issue #655426 |
| // |
| |
| // -------------------------- |
| |
| // This is how some things work now: 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. |
| // |
| // 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 (state == State.CONNECTED |
| && !pdpStatesHasActiveCID(pdpStates, cidActive)) { |
| |
| if (!explicitPoll) { |
| // We think it disconnected but aren't sure...poll from our side |
| phone.mCM.getPDPContextList( |
| this.obtainMessage(EVENT_GET_PDP_LIST_COMPLETE)); |
| } else { |
| Log.i(LOG_TAG, "PDP connection has dropped (active=false case). " |
| + " Reconnecting"); |
| |
| // Log the network drop on the event log. |
| int cid = -1; |
| GsmCellLocation loc = ((GsmCellLocation)phone.getCellLocation()); |
| if (loc != null) cid = loc.getCid(); |
| |
| EventLog.List val = new EventLog.List(cid, |
| TelephonyManager.getDefault().getNetworkType()); |
| |
| EventLog.writeEvent(TelephonyEventLog.EVENT_LOG_PDP_NETWORK_DROP, val); |
| |
| cleanUpConnection(true, null); |
| } |
| } |
| } |
| } |
| |
| private void notifyDefaultData(String reason) { |
| setupDnsProperties(); |
| setState(State.CONNECTED); |
| phone.notifyDataConnection(reason); |
| startNetStatPoll(); |
| // reset reconnect timer |
| nextReconnectDelay = RECONNECT_DELAY_INITIAL_MILLIS; |
| } |
| |
| private void setupDnsProperties() { |
| int mypid = android.os.Process.myPid(); |
| String[] servers = getDnsServers(null); |
| String propName; |
| String propVal; |
| int count; |
| |
| count = 0; |
| for (int i = 0; i < servers.length; i++) { |
| String serverAddr = servers[i]; |
| if (!TextUtils.equals(serverAddr, "0.0.0.0")) { |
| SystemProperties.set("net.dns" + (i+1) + "." + mypid, serverAddr); |
| count++; |
| } |
| } |
| for (int i = count+1; i <= 4; i++) { |
| propName = "net.dns" + i + "." + mypid; |
| propVal = SystemProperties.get(propName); |
| if (propVal.length() != 0) { |
| SystemProperties.set(propName, ""); |
| } |
| } |
| /* |
| * Bump the property that tells the name resolver library |
| * to reread the DNS server list from the properties. |
| */ |
| propVal = SystemProperties.get("net.dnschange"); |
| if (propVal.length() != 0) { |
| try { |
| int n = Integer.parseInt(propVal); |
| SystemProperties.set("net.dnschange", "" + (n+1)); |
| } catch (NumberFormatException e) { |
| } |
| } |
| } |
| |
| /** |
| * This is a kludge to deal with the fact that |
| * the PDP state change notification doesn't always work |
| * with certain RIL impl's/basebands |
| * |
| */ |
| private void |
| startPeriodicPdpPoll() |
| { |
| removeMessages(EVENT_POLL_PDP); |
| |
| sendMessageDelayed(obtainMessage(EVENT_POLL_PDP), POLL_PDP_MILLIS); |
| } |
| |
| private void resetPollStats() { |
| txPkts = -1; |
| rxPkts = -1; |
| sentSinceLastRecv = 0; |
| mNoRecvPollCount = 0; |
| } |
| |
| private void doRecovery() { |
| if (state == State.CONNECTED) { |
| int maxPdpReset = Settings.Gservices.getInt(mResolver, |
| Settings.Gservices.PDP_WATCHDOG_MAX_PDP_RESET_FAIL_COUNT, |
| DEFAULT_MAX_PDP_RESET_FAIL); |
| if (mPdpResetCount < maxPdpReset) { |
| mPdpResetCount++; |
| EventLog.writeEvent(TelephonyEventLog.EVENT_LOG_PDP_RESET, sentSinceLastRecv); |
| cleanUpConnection(true, Phone.REASON_PDP_RESET); |
| } else { |
| mPdpResetCount = 0; |
| EventLog.writeEvent(TelephonyEventLog.EVENT_LOG_REREGISTER_NETWORK, sentSinceLastRecv); |
| phone.mSST.reRegisterNetwork(null); |
| } |
| // TODO: Add increasingly drastic recovery steps, eg, |
| // reset the radio, reset the device. |
| } |
| } |
| |
| private void |
| startNetStatPoll() |
| { |
| if (state == State.CONNECTED && mPingTestActive == false && netStatPollEnabled == false) { |
| Log.d(LOG_TAG, "[DataConnection] Start poll NetStat"); |
| resetPollStats(); |
| netStatPollEnabled = true; |
| mPollNetStat.run(); |
| } |
| } |
| |
| private void |
| stopNetStatPoll() |
| { |
| netStatPollEnabled = false; |
| removeCallbacks(mPollNetStat); |
| Log.d(LOG_TAG, "[DataConnection] Stop poll NetStat"); |
| } |
| |
| private void |
| restartRadio() |
| { |
| Log.d(LOG_TAG, "************TURN OFF RADIO**************"); |
| cleanUpConnection(true, Phone.REASON_RADIO_TURNED_OFF); |
| 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. |
| */ |
| |
| int reset = Integer.parseInt(SystemProperties.get("net.ppp.reset-by-timeout", "0")); |
| SystemProperties.set("net.ppp.reset-by-timeout", String.valueOf(reset+1)); |
| } |
| |
| Runnable mPollNetStat = new Runnable() |
| { |
| |
| public void run() { |
| long sent, received; |
| long preTxPkts = -1, preRxPkts = -1; |
| |
| Activity newActivity; |
| |
| preTxPkts = txPkts; |
| preRxPkts = rxPkts; |
| |
| try { |
| txPkts = netstat.getMobileTxPackets(); |
| rxPkts = netstat.getMobileRxPackets(); |
| } catch (RemoteException e) { |
| txPkts = 0; |
| rxPkts = 0; |
| } |
| |
| //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; |
| mPdpResetCount = 0; |
| } else if (sent > 0 && received == 0) { |
| if (phone.mCT.state == Phone.State.IDLE) { |
| sentSinceLastRecv += sent; |
| } else { |
| sentSinceLastRecv = 0; |
| } |
| newActivity = Activity.DATAOUT; |
| } else if (sent == 0 && received > 0) { |
| sentSinceLastRecv = 0; |
| newActivity = Activity.DATAIN; |
| mPdpResetCount = 0; |
| } else if (sent == 0 && received == 0) { |
| newActivity = Activity.NONE; |
| } else { |
| sentSinceLastRecv = 0; |
| newActivity = Activity.NONE; |
| } |
| |
| if (activity != newActivity && mIsScreenOn) { |
| activity = newActivity; |
| phone.notifyDataActivity(); |
| } |
| } |
| |
| int watchdogTrigger = Settings.Gservices.getInt(mResolver, |
| Settings.Gservices.PDP_WATCHDOG_TRIGGER_PACKET_COUNT, NUMBER_SENT_PACKETS_OF_HANG); |
| |
| if (sentSinceLastRecv >= watchdogTrigger) { |
| // we already have NUMBER_SENT_PACKETS sent without ack |
| if (mNoRecvPollCount == 0) { |
| EventLog.writeEvent(TelephonyEventLog.EVENT_LOG_RADIO_RESET_COUNTDOWN_TRIGGERED, |
| sentSinceLastRecv); |
| } |
| |
| int noRecvPollLimit = Settings.Gservices.getInt(mResolver, |
| Settings.Gservices.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"); |
| phone.mCM.getPDPContextList(obtainMessage(EVENT_GET_PDP_LIST_COMPLETE)); |
| |
| mNoRecvPollCount++; |
| |
| // Slow down the poll interval to let things happen |
| netStatPollPeriod = Settings.Gservices.getInt(mResolver, |
| Settings.Gservices.PDP_WATCHDOG_ERROR_POLL_INTERVAL_MS, POLL_NETSTAT_SLOW_MILLIS); |
| } else { |
| if (DBG) log("Sent " + String.valueOf(sentSinceLastRecv) + |
| " pkts since last received"); |
| // We've exceeded the threshold. Run ping test as a final check; |
| // it will proceed with recovery if ping fails. |
| stopNetStatPoll(); |
| Thread pingTest = new Thread() { |
| public void run() { |
| runPingTest(); |
| } |
| }; |
| mPingTestActive = true; |
| pingTest.start(); |
| } |
| } else { |
| mNoRecvPollCount = 0; |
| if (mIsScreenOn) { |
| netStatPollPeriod = Settings.Gservices.getInt(mResolver, |
| Settings.Gservices.PDP_WATCHDOG_POLL_INTERVAL_MS, POLL_NETSTAT_MILLIS); |
| } else { |
| netStatPollPeriod = Settings.Gservices.getInt(mResolver, |
| Settings.Gservices.PDP_WATCHDOG_LONG_POLL_INTERVAL_MS, |
| POLL_NETSTAT_SCREEN_OFF_MILLIS); |
| } |
| } |
| |
| if (netStatPollEnabled) { |
| mDataConnectionTracker.postDelayed(this, netStatPollPeriod); |
| } |
| } |
| }; |
| |
| private void runPingTest () { |
| int status = -1; |
| try { |
| String address = Settings.Gservices.getString(mResolver, |
| Settings.Gservices.PDP_WATCHDOG_PING_ADDRESS); |
| int deadline = Settings.Gservices.getInt(mResolver, |
| Settings.Gservices.PDP_WATCHDOG_PING_DEADLINE, DEFAULT_PING_DEADLINE); |
| if (DBG) log("pinging " + address + " for " + deadline + "s"); |
| if (address != null && !NULL_IP.equals(address)) { |
| Process p = Runtime.getRuntime() |
| .exec("ping -c 1 -i 1 -w "+ deadline + " " + address); |
| status = p.waitFor(); |
| } |
| } catch (IOException e) { |
| Log.w(LOG_TAG, "ping failed: IOException"); |
| } catch (Exception e) { |
| Log.w(LOG_TAG, "exception trying to ping"); |
| } |
| |
| if (status == 0) { |
| // ping succeeded. False alarm. Reset netStatPoll. |
| // ("-1" for this event indicates a false alarm) |
| EventLog.writeEvent(TelephonyEventLog.EVENT_LOG_PDP_RESET, -1); |
| mPdpResetCount = 0; |
| sendMessage(obtainMessage(EVENT_START_NETSTAT_POLL)); |
| } else { |
| // ping failed. Proceed with recovery. |
| sendMessage(obtainMessage(EVENT_START_RECOVERY)); |
| } |
| } |
| |
| /** |
| * Returns true if the last fail cause is something that |
| * seems like it deserves an error notification. |
| * Transient errors are ignored |
| */ |
| private boolean |
| shouldPostNotification(PdpConnection.PdpFailCause cause) |
| { |
| boolean shouldPost = true; |
| // TODO CHECK |
| // if (dataLink != null) { |
| // shouldPost = dataLink.getLastLinkExitCode() != DataLink.EXIT_OPEN_FAILED; |
| //} |
| return (shouldPost && cause != PdpConnection.PdpFailCause.UNKNOWN); |
| } |
| |
| private void reconnectAfterFail(PdpFailCause lastFailCauseCode, String reason) { |
| if (state == State.FAILED) { |
| Log.d(LOG_TAG, "PDP 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); |
| |
| // double it for next time |
| nextReconnectDelay *= 2; |
| if (nextReconnectDelay > RECONNECT_DELAY_MAX_MILLIS) { |
| nextReconnectDelay = RECONNECT_DELAY_MAX_MILLIS; |
| } |
| |
| if (!shouldPostNotification(lastFailCauseCode)) { |
| Log.d(LOG_TAG,"NOT Posting GPRS Unavailable notification " |
| + "-- likely transient error"); |
| } else { |
| notifyNoData(lastFailCauseCode); |
| } |
| } |
| } |
| |
| private void notifyNoData(PdpConnection.PdpFailCause lastFailCauseCode) { |
| setState(State.FAILED); |
| } |
| |
| |
| private void log(String s) { |
| Log.d(LOG_TAG, "[DataConnectionTracker] " + s); |
| } |
| |
| //***** Overridden from Handler |
| public void |
| handleMessage (Message msg) |
| { |
| AsyncResult ar; |
| String reason = null; |
| |
| switch (msg.what) { |
| case EVENT_RECORDS_LOADED: |
| createAllApnList(); |
| if (state == State.FAILED) { |
| cleanUpConnection(false, null); |
| } |
| sendMessage(obtainMessage(EVENT_TRY_SETUP_DATA)); |
| break; |
| |
| case EVENT_ENABLE_NEW_APN: |
| // TODO: To support simultaneous PDP contexts, this should really only call |
| // cleanUpConnection if it needs to free up a PdpConnection. |
| reason = Phone.REASON_APN_SWITCHED; |
| cleanUpConnection(true, reason); |
| break; |
| |
| case EVENT_TRY_SETUP_DATA: |
| trySetupData(reason); |
| break; |
| |
| case EVENT_RESTORE_DEFAULT_APN: |
| if (DBG) Log.d(LOG_TAG, "Restore default APN"); |
| setEnabled(Phone.APN_TYPE_MMS, false); |
| if (!isApnTypeActive(Phone.APN_TYPE_DEFAULT)) { |
| cleanUpConnection(true, Phone.REASON_RESTORE_DEFAULT_APN); |
| mRequestedApnType = Phone.APN_TYPE_DEFAULT; |
| } |
| break; |
| |
| case EVENT_ROAMING_OFF: |
| trySetupData(Phone.REASON_ROAMING_OFF); |
| break; |
| |
| case EVENT_GPRS_DETACHED: |
| onGprsDetached(); |
| break; |
| |
| case EVENT_GPRS_ATTACHED: |
| onGprsAttached(); |
| break; |
| |
| case EVENT_ROAMING_ON: |
| if (getDataOnRoamingEnabled()) { |
| trySetupData(Phone.REASON_ROAMING_ON); |
| } else { |
| if (DBG) log("Tear down data connection on roaming."); |
| cleanUpConnection(true, Phone.REASON_ROAMING_ON); |
| } |
| break; |
| |
| case EVENT_RADIO_AVAILABLE: |
| 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); |
| } |
| break; |
| |
| case EVENT_RADIO_OFF_OR_NOT_AVAILABLE: |
| // Make sure our reconnect delay starts at the initial value |
| // next time the radio comes on |
| nextReconnectDelay = RECONNECT_DELAY_INITIAL_MILLIS; |
| |
| 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"); |
| // TODO: Should we reset mRequestedApnType to "default"? |
| cleanUpConnection(false, Phone.REASON_RADIO_TURNED_OFF); |
| } |
| break; |
| |
| case EVENT_DATA_SETUP_COMPLETE: |
| ar = (AsyncResult) msg.obj; |
| if (ar.userObj instanceof String) { |
| reason = (String) ar.userObj; |
| } |
| |
| if (ar.exception == null) { |
| // everything is setup |
| |
| // arg1 contains CID for this PDP context |
| cidActive = msg.arg1; |
| /* |
| * We may have switched away from the default PDP context |
| * in order to enable a "special" APN (e.g., for MMS |
| * traffic). Set a timer to switch back and/or disable the |
| * special APN, so that a negligient application doesn't |
| * permanently prevent data connectivity. What we are |
| * protecting against here is not malicious apps, but |
| * rather an app that inadvertantly fails to reset to the |
| * default APN, or that dies before doing so. |
| */ |
| if (dataEnabled[APN_MMS_ID]) { |
| removeMessages(EVENT_RESTORE_DEFAULT_APN); |
| sendMessageDelayed( |
| obtainMessage(EVENT_RESTORE_DEFAULT_APN), |
| getRestoreDefaultApnDelay()); |
| } |
| if (isApnTypeActive(Phone.APN_TYPE_DEFAULT)) { |
| SystemProperties.set("gsm.defaultpdpcontext.active", "true"); |
| } 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 { |
| PdpConnection.PdpFailCause cause; |
| cause = (PdpConnection.PdpFailCause) (ar.result); |
| if(DBG) |
| log("PDP setup failed " + cause); |
| // Log this failure to the Event Logs. |
| if (cause == PdpConnection.PdpFailCause.BAD_APN || |
| cause == PdpConnection.PdpFailCause.BAD_PAP_SECRET || |
| cause == PdpConnection.PdpFailCause.BARRED || |
| cause == PdpConnection.PdpFailCause.RADIO_ERROR_RETRY || |
| cause == PdpConnection.PdpFailCause.SUSPENED_TEMPORARY || |
| cause == PdpConnection.PdpFailCause.UNKNOWN || |
| cause == PdpConnection.PdpFailCause.USER_AUTHENTICATION) { |
| int cid = -1; |
| GsmCellLocation loc = ((GsmCellLocation)phone.getCellLocation()); |
| if (loc != null) cid = loc.getCid(); |
| |
| EventLog.List val = new EventLog.List( |
| cause.ordinal(), cid, |
| TelephonyManager.getDefault().getNetworkType()); |
| EventLog.writeEvent(TelephonyEventLog.EVENT_LOG_RADIO_PDP_SETUP_FAIL, val); |
| } |
| // No try for permanent failure |
| if (cause.isPermanentFail()) { |
| notifyNoData(cause); |
| } |
| |
| if (tryNextApn(cause)) { |
| waitingApns.remove(0); |
| if (waitingApns.isEmpty()) { |
| // No more to try, start delayed retry |
| startDelayedRetry(cause, reason); |
| } else { |
| // we still have more apns to try |
| setState(State.SCANNING); |
| trySetupData(reason); |
| } |
| } else { |
| startDelayedRetry(cause, reason); |
| } |
| } |
| break; |
| |
| case EVENT_DISCONNECT_DONE: |
| if(DBG) log("EVENT_DISCONNECT_DONE"); |
| ar = (AsyncResult) msg.obj; |
| if (ar.userObj instanceof String) { |
| reason = (String) ar.userObj; |
| } |
| setState(State.IDLE); |
| phone.notifyDataConnection(reason); |
| mActiveApn = null; |
| trySetupData(reason); |
| break; |
| |
| case EVENT_PDP_STATE_CHANGED: |
| ar = (AsyncResult) msg.obj; |
| |
| onPdpStateChanged(ar, false); |
| break; |
| |
| case EVENT_GET_PDP_LIST_COMPLETE: |
| ar = (AsyncResult) msg.obj; |
| |
| onPdpStateChanged(ar, true); |
| break; |
| |
| case EVENT_POLL_PDP: |
| /* See comment in startPeriodicPdpPoll */ |
| ar = (AsyncResult) msg.obj; |
| |
| if (!(state == State.CONNECTED)) { |
| // not connected; don't poll anymore |
| break; |
| } |
| |
| phone.mCM.getPDPContextList(this.obtainMessage(EVENT_GET_PDP_LIST_COMPLETE)); |
| |
| sendMessageDelayed(obtainMessage(EVENT_POLL_PDP), |
| POLL_PDP_MILLIS); |
| break; |
| |
| case EVENT_VOICE_CALL_STARTED: |
| if (state == State.CONNECTED && |
| !phone.mSST.isConcurrentVoiceAndData()) { |
| stopNetStatPoll(); |
| phone.notifyDataConnection(Phone.REASON_VOICE_CALL_STARTED); |
| } |
| break; |
| |
| case EVENT_VOICE_CALL_ENDED: |
| if (state == State.CONNECTED) { |
| if (!phone.mSST.isConcurrentVoiceAndData()) { |
| startNetStatPoll(); |
| phone.notifyDataConnection(Phone.REASON_VOICE_CALL_ENDED); |
| } else { |
| // clean slate after call end. |
| resetPollStats(); |
| } |
| } else { |
| // in case data setup was attempted when we were on a voice call |
| trySetupData(Phone.REASON_VOICE_CALL_ENDED); |
| } |
| break; |
| |
| case EVENT_START_NETSTAT_POLL: |
| mPingTestActive = false; |
| startNetStatPoll(); |
| break; |
| |
| case EVENT_START_RECOVERY: |
| mPingTestActive = false; |
| doRecovery(); |
| break; |
| |
| case EVENT_APN_CHANGED: |
| onApnChanged(); |
| break; |
| } |
| } |
| |
| private boolean tryNextApn(PdpFailCause cause) { |
| return (cause != PdpFailCause.RADIO_NOT_AVIALABLE) |
| && (cause != PdpFailCause.RADIO_OFF) |
| && (cause != PdpFailCause.RADIO_ERROR_RETRY) |
| && (cause != PdpFailCause.NO_SIGNAL) |
| && (cause != PdpFailCause.SIM_LOCKED); |
| } |
| |
| private int getRestoreDefaultApnDelay() { |
| String restoreApnDelayStr = SystemProperties.get(APN_RESTORE_DELAY_PROP_NAME); |
| |
| if (restoreApnDelayStr != null && restoreApnDelayStr.length() != 0) { |
| try { |
| return Integer.valueOf(restoreApnDelayStr); |
| } catch (NumberFormatException e) { |
| } |
| } |
| return RESTORE_DEFAULT_APN_DELAY; |
| } |
| |
| /** |
| * Based on the sim operator numeric, create a list for all possible pdps |
| * with all apns associated with that pdp |
| * |
| * |
| */ |
| private void createAllApnList() { |
| allApns = new ArrayList<ApnSetting>(); |
| String operator = phone.mSIMRecords.getSIMOperatorNumeric(); |
| |
| if (operator != null) { |
| String selection = "numeric = '" + operator + "'"; |
| |
| Cursor cursor = phone.getContext().getContentResolver().query( |
| Telephony.Carriers.CONTENT_URI, null, selection, null, null); |
| |
| if (cursor != null) { |
| if (cursor.getCount() > 0) { |
| allApns = createApnList(cursor); |
| // TODO: Figure out where this fits in. This basically just |
| // writes the pap-secrets file. No longer tied to PdpConnection |
| // object. Not used on current platform (no ppp). |
| //PdpConnection pdp = pdpList.get(pdp_name); |
| //if (pdp != null && pdp.dataLink != null) { |
| // pdp.dataLink.setPasswordInfo(cursor); |
| //} |
| } |
| cursor.close(); |
| } |
| } |
| |
| if (allApns.isEmpty()) { |
| if (DBG) log("No APN found for carrier: " + operator); |
| notifyNoData(PdpConnection.PdpFailCause.BAD_APN); |
| } |
| } |
| |
| private void createAllPdpList() { |
| pdpList = new ArrayList<PdpConnection>(); |
| PdpConnection pdp; |
| |
| for (int i = 0; i < PDP_CONNECTION_POOL_SIZE; i++) { |
| pdp = new PdpConnection(phone); |
| pdpList.add(pdp); |
| } |
| } |
| |
| /** |
| * |
| * @return waitingApns list to be used to create PDP |
| * error when waitingApns.isEmpty() |
| */ |
| private ArrayList<ApnSetting> buildWaitingApns() { |
| ArrayList<ApnSetting> apnList = new ArrayList<ApnSetting>(); |
| |
| if (allApns != null) { |
| for (ApnSetting apn : allApns) { |
| if (apn.canHandleType(mRequestedApnType)) { |
| 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 = waitingApns; |
| 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(PdpConnection.PdpFailCause cause, String reason) { |
| notifyNoData(cause); |
| if (mRequestedApnType != Phone.APN_TYPE_DEFAULT) { |
| sendMessage(obtainMessage(EVENT_RESTORE_DEFAULT_APN)); |
| } |
| else { |
| reconnectAfterFail(cause, reason); |
| } |
| } |
| } |