/* * 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; import com.android.internal.util.HierarchicalState; import com.android.internal.util.HierarchicalStateMachine; import android.net.LinkAddress; import android.net.LinkCapabilities; import android.net.LinkProperties; import android.net.NetworkUtils; import android.os.AsyncResult; import android.os.Message; import android.os.SystemProperties; import android.text.TextUtils; import java.net.InetAddress; import java.net.Inet4Address; import java.net.UnknownHostException; import java.util.HashMap; /** * {@hide} * * DataConnection HierarchicalStateMachine. * * This is an abstract base class for representing a single data connection. * Instances of this class such as CdmaDataConnection and * GsmDataConnection, * represent a connection via the cellular network. * There may be multiple data connections and all of them are managed by the * DataConnectionTracker. * * Instances are asynchronous state machines and have two primary entry points * connect() and disconnect. The message a parameter will be returned * hen the operation completes. The msg.obj will contain an AsyncResult * object and AsyncResult.userObj is the original msg.obj. if successful * with the AsyncResult.result == null and AsyncResult.exception == null. * If an error AsyncResult.result = FailCause and * AsyncResult.exception = new Exception(). * * The other public methods are provided for debugging. */ public abstract class DataConnection extends HierarchicalStateMachine { protected static final boolean DBG = true; protected static Object mCountLock = new Object(); protected static int mCount; /** * Class returned by onSetupConnectionCompleted. */ protected enum SetupResult { SUCCESS, ERR_BadCommand, ERR_UnacceptableParameter, ERR_GetLastErrorFromRil, ERR_Stale, ERR_RilError; public FailCause mFailCause; SetupResult() { mFailCause = FailCause.fromInt(0); } @Override public String toString() { return name() + " SetupResult.mFailCause=" + mFailCause; } } /** * Used internally for saving connecting parameters. */ protected static class ConnectionParams { public ConnectionParams(ApnSetting apn, Message onCompletedMsg) { this.apn = apn; this.onCompletedMsg = onCompletedMsg; } public int tag; public ApnSetting apn; public Message onCompletedMsg; } /** * An instance used for notification of blockingReset. * TODO: Remove when blockingReset is removed. */ class ResetSynchronouslyLock { } /** * Used internally for saving disconnecting parameters. */ protected static class DisconnectParams { public DisconnectParams(Message onCompletedMsg) { this.onCompletedMsg = onCompletedMsg; } public DisconnectParams(ResetSynchronouslyLock lockObj) { this.lockObj = lockObj; } public int tag; public Message onCompletedMsg; public ResetSynchronouslyLock lockObj; } /** * Returned as the reason for a connection failure as defined * by RIL_DataCallFailCause in ril.h and some local errors. */ public enum FailCause { NONE(0), // This series of errors as specified by the standards // specified in ril.h OPERATOR_BARRED(0x08), INSUFFICIENT_RESOURCES(0x1A), MISSING_UNKNOWN_APN(0x1B), UNKNOWN_PDP_ADDRESS_TYPE(0x1C), USER_AUTHENTICATION(0x1D), ACTIVATION_REJECT_GGSN(0x1E), ACTIVATION_REJECT_UNSPECIFIED(0x1F), SERVICE_OPTION_NOT_SUPPORTED(0x20), SERVICE_OPTION_NOT_SUBSCRIBED(0x21), SERVICE_OPTION_OUT_OF_ORDER(0x22), NSAPI_IN_USE(0x23), ONLY_IPV4_ALLOWED(0x32), ONLY_IPV6_ALLOWED(0x33), ONLY_SINGLE_BEARER_ALLOWED(0x34), PROTOCOL_ERRORS(0x6F), // Local errors generated by Vendor RIL // specified in ril.h REGISTRATION_FAIL(-1), GPRS_REGISTRATION_FAIL(-2), SIGNAL_LOST(-3), PREF_RADIO_TECH_CHANGED(-4), RADIO_POWER_OFF(-5), TETHERED_CALL_ACTIVE(-6), ERROR_UNSPECIFIED(0xFFFF), // Errors generated by the Framework // specified here UNKNOWN(0x10000), RADIO_NOT_AVAILABLE(0x10001), UNACCEPTABLE_NETWORK_PARAMETER(0x10002); private final int mErrorCode; private static final HashMap sErrorCodeToFailCauseMap; static { sErrorCodeToFailCauseMap = new HashMap(); for (FailCause fc : values()) { sErrorCodeToFailCauseMap.put(fc.ordinal(), fc); } } FailCause(int errorCode) { mErrorCode = errorCode; } int getErrorCode() { return mErrorCode; } public boolean isPermanentFail() { return (this == OPERATOR_BARRED) || (this == MISSING_UNKNOWN_APN) || (this == UNKNOWN_PDP_ADDRESS_TYPE) || (this == USER_AUTHENTICATION) || (this == SERVICE_OPTION_NOT_SUPPORTED) || (this == SERVICE_OPTION_NOT_SUBSCRIBED) || (this == NSAPI_IN_USE) || (this == PROTOCOL_ERRORS); } public boolean isEventLoggable() { return (this == OPERATOR_BARRED) || (this == INSUFFICIENT_RESOURCES) || (this == UNKNOWN_PDP_ADDRESS_TYPE) || (this == USER_AUTHENTICATION) || (this == ACTIVATION_REJECT_GGSN) || (this == ACTIVATION_REJECT_UNSPECIFIED) || (this == SERVICE_OPTION_NOT_SUBSCRIBED) || (this == SERVICE_OPTION_NOT_SUPPORTED) || (this == SERVICE_OPTION_OUT_OF_ORDER) || (this == NSAPI_IN_USE) || (this == PROTOCOL_ERRORS) || (this == UNACCEPTABLE_NETWORK_PARAMETER); } public static FailCause fromInt(int errorCode) { FailCause fc = sErrorCodeToFailCauseMap.get(errorCode); if (fc == null) { fc = UNKNOWN; } return fc; } } // ***** Event codes for driving the state machine protected static final int EVENT_RESET = 1; protected static final int EVENT_CONNECT = 2; protected static final int EVENT_SETUP_DATA_CONNECTION_DONE = 3; protected static final int EVENT_GET_LAST_FAIL_DONE = 4; protected static final int EVENT_DEACTIVATE_DONE = 5; protected static final int EVENT_DISCONNECT = 6; //***** Tag IDs for EventLog protected static final int EVENT_LOG_BAD_DNS_ADDRESS = 50100; //***** Member Variables protected ApnSetting mApn; protected int mTag; protected PhoneBase phone; protected int cid; protected LinkProperties mLinkProperties = new LinkProperties(); protected LinkCapabilities mCapabilities = new LinkCapabilities(); protected long createTime; protected long lastFailTime; protected FailCause lastFailCause; protected static final String NULL_IP = "0.0.0.0"; Object userData; //***** Abstract methods @Override public abstract String toString(); protected abstract void onConnect(ConnectionParams cp); protected abstract boolean isDnsOk(String[] domainNameServers); protected abstract void log(String s); //***** Constructor protected DataConnection(PhoneBase phone, String name, int id, RetryManager rm) { super(name); if (DBG) log("DataConnection constructor E"); this.phone = phone; mId = id; mRetryMgr = rm; this.cid = -1; clearSettings(); setDbg(false); addState(mDefaultState); addState(mInactiveState, mDefaultState); addState(mActivatingState, mDefaultState); addState(mActiveState, mDefaultState); addState(mDisconnectingState, mDefaultState); addState(mDisconnectingErrorCreatingConnection, mDefaultState); setInitialState(mInactiveState); if (DBG) log("DataConnection constructor X"); } /** * TearDown the data connection. * * @param o will be returned in AsyncResult.userObj * and is either a DisconnectParams or ConnectionParams. */ private void tearDownData(Object o) { int discReason = RILConstants.DEACTIVATE_REASON_NONE; if ((o != null) && (o instanceof DisconnectParams)) { DisconnectParams dp = (DisconnectParams)o; Message m = dp.onCompletedMsg; if ((m != null) && (m.obj != null) && (m.obj instanceof String)) { String reason = (String)m.obj; if (TextUtils.equals(reason, Phone.REASON_RADIO_TURNED_OFF)) discReason = RILConstants.DEACTIVATE_REASON_RADIO_OFF; } } if (phone.mCM.getRadioState().isOn()) { if (DBG) log("tearDownData radio is on, call deactivateDataCall"); phone.mCM.deactivateDataCall(cid, discReason, obtainMessage(EVENT_DEACTIVATE_DONE, o)); } else { if (DBG) log("tearDownData radio is off sendMessage EVENT_DEACTIVATE_DONE immediately"); AsyncResult ar = new AsyncResult(o, null, null); sendMessage(obtainMessage(EVENT_DEACTIVATE_DONE, ar)); } } /** * Send the connectionCompletedMsg. * * @param cp is the ConnectionParams * @param cause */ private void notifyConnectCompleted(ConnectionParams cp, FailCause cause) { Message connectionCompletedMsg = cp.onCompletedMsg; if (connectionCompletedMsg == null) { return; } long timeStamp = System.currentTimeMillis(); connectionCompletedMsg.arg1 = cid; if (cause == FailCause.NONE) { createTime = timeStamp; AsyncResult.forMessage(connectionCompletedMsg); } else { lastFailCause = cause; lastFailTime = timeStamp; AsyncResult.forMessage(connectionCompletedMsg, cause, new Exception()); } if (DBG) log("notifyConnection at " + timeStamp + " cause=" + cause); connectionCompletedMsg.sendToTarget(); } /** * Send ar.userObj if its a message, which is should be back to originator. * * @param dp is the DisconnectParams. */ private void notifyDisconnectCompleted(DisconnectParams dp) { if (DBG) log("NotifyDisconnectCompleted"); if (dp.onCompletedMsg != null) { Message msg = dp.onCompletedMsg; log(String.format("msg=%s msg.obj=%s", msg.toString(), ((msg.obj instanceof String) ? (String) msg.obj : ""))); AsyncResult.forMessage(msg); msg.sendToTarget(); } if (dp.lockObj != null) { synchronized(dp.lockObj) { dp.lockObj.notify(); } } clearSettings(); } /* * ************************************************************************** * Begin Members and methods owned by DataConnectionTracker but stored * in a DataConnection because there is one per connection. * ************************************************************************** */ /* * The id is owned by DataConnectionTracker. */ private int mId; /** * Get the DataConnection ID */ public int getDataConnectionId() { return mId; } /* * The retry manager is currently owned by the DataConnectionTracker but is stored * in the DataConnection because there is one per connection. These methods * should only be used by the DataConnectionTracker although someday the retrying * maybe managed by the DataConnection itself and these methods could disappear. */ private RetryManager mRetryMgr; /** * @return retry manager retryCount */ public int getRetryCount() { return mRetryMgr.getRetryCount(); } /** * @return retry manager retryTimer */ public int getRetryTimer() { return mRetryMgr.getRetryTimer(); } /** * increaseRetryCount of retry manager */ public void increaseRetryCount() { mRetryMgr.increaseRetryCount(); } /** * @return retry manager isRetryNeeded */ public boolean isRetryNeeded() { return mRetryMgr.isRetryNeeded(); } /** * resetRetryCount of retry manager */ public void resetRetryCount() { mRetryMgr.resetRetryCount(); } /** * set retryForeverUsingLasttimeout of retry manager */ public void retryForeverUsingLastTimeout() { mRetryMgr.retryForeverUsingLastTimeout(); } /** * @return retry manager isRetryForever */ public boolean isRetryForever() { return mRetryMgr.isRetryForever(); } /* * ************************************************************************** * End members owned by DataConnectionTracker * ************************************************************************** */ /** * Clear all settings called when entering mInactiveState. */ protected void clearSettings() { if (DBG) log("clearSettings"); createTime = -1; lastFailTime = -1; lastFailCause = FailCause.NONE; mLinkProperties = new LinkProperties(); mApn = null; } /** * Process setup completion. * * @param ar is the result * @return SetupResult. */ private SetupResult onSetupConnectionCompleted(AsyncResult ar) { DataCallState response = (DataCallState) ar.result; ConnectionParams cp = (ConnectionParams) ar.userObj; SetupResult result; if (ar.exception != null) { if (DBG) { log("onSetupConnectionCompleted failed, ar.exception=" + ar.exception + " response=" + response); } if (ar.exception instanceof CommandException && ((CommandException) (ar.exception)).getCommandError() == CommandException.Error.RADIO_NOT_AVAILABLE) { result = SetupResult.ERR_BadCommand; result.mFailCause = FailCause.RADIO_NOT_AVAILABLE; } else if ((response == null) || (response.version < 4)) { result = SetupResult.ERR_GetLastErrorFromRil; } else { result = SetupResult.ERR_RilError; result.mFailCause = FailCause.fromInt(response.status); } } else if (cp.tag != mTag) { if (DBG) { log("BUG: onSetupConnectionCompleted is stale cp.tag=" + cp.tag + ", mtag=" + mTag); } result = SetupResult.ERR_Stale; } else { log("onSetupConnectionCompleted received DataCallState: " + response); // Start with clean network properties and if we have // a failure we'll clear again at the bottom of this code. LinkProperties linkProperties = new LinkProperties(); if (response.status == FailCause.NONE.getErrorCode()) { String propertyPrefix = "net." + response.ifname + "."; try { cid = response.cid; linkProperties.setInterfaceName(response.ifname); if (response.addresses != null && response.addresses.length > 0) { for (String addr : response.addresses) { LinkAddress la; int addrPrefixLen; String [] ap = addr.split("/"); if (ap.length == 2) { addr = ap[0]; addrPrefixLen = Integer.parseInt(ap[1]); } else { addrPrefixLen = 0; } InetAddress ia; try { ia = NetworkUtils.numericToInetAddress(addr); } catch (IllegalArgumentException e) { EventLogTags.writeBadIpAddress(addr); throw new UnknownHostException("Non-numeric ip addr=" + addr); } if (addrPrefixLen == 0) { // Assume point to point addrPrefixLen = (ia instanceof Inet4Address) ? 32 : 128; } if (DBG) log("addr/pl=" + addr + "/" + addrPrefixLen); la = new LinkAddress(ia, addrPrefixLen); linkProperties.addLinkAddress(la); } } else { EventLogTags.writeBadIpAddress("no address for ifname=" + response.ifname); throw new UnknownHostException("no address for ifname=" + response.ifname); } if (response.dnses != null && response.dnses.length > 0) { for (String addr : response.dnses) { InetAddress ia; try { ia = NetworkUtils.numericToInetAddress(addr); } catch (IllegalArgumentException e) { EventLogTags.writePdpBadDnsAddress("dns=" + addr); throw new UnknownHostException("Non-numeric dns addr=" + addr); } linkProperties.addDns(ia); } result = SetupResult.SUCCESS; } else { String dnsServers[] = new String[2]; dnsServers[0] = SystemProperties.get(propertyPrefix + "dns1"); dnsServers[1] = SystemProperties.get(propertyPrefix + "dns2"); if (isDnsOk(dnsServers)) { for (String dnsAddr : dnsServers) { InetAddress ia; try { ia = NetworkUtils.numericToInetAddress(dnsAddr); } catch (IllegalArgumentException e) { EventLogTags.writePdpBadDnsAddress("dnsAddr=" + dnsAddr); throw new UnknownHostException("Non-numeric dns addr=" + dnsAddr); } linkProperties.addDns(ia); } result = SetupResult.SUCCESS; } else { StringBuilder sb = new StringBuilder(); for (String dnsAddr : dnsServers) { sb.append(dnsAddr); sb.append(" "); } EventLogTags.writePdpBadDnsAddress("Unacceptable dns addresses=" + sb); throw new UnknownHostException("Unacceptable dns addresses=" + sb); } } if ((response.gateways == null) || (response.gateways.length == 0)) { String gateways = SystemProperties.get(propertyPrefix + "gw"); if (gateways != null) { response.gateways = gateways.split(" "); } else { response.gateways = new String[0]; } } for (String addr : response.gateways) { InetAddress ia; try { ia = NetworkUtils.numericToInetAddress(addr); } catch (IllegalArgumentException e) { EventLogTags.writePdpBadDnsAddress("gateway=" + addr); throw new UnknownHostException("Non-numeric gateway addr=" + addr); } linkProperties.addGateway(ia); } result = SetupResult.SUCCESS; } catch (UnknownHostException e) { log("onSetupCompleted: UnknownHostException " + e); e.printStackTrace(); result = SetupResult.ERR_UnacceptableParameter; } } else { if (response.version < 4) { result = SetupResult.ERR_GetLastErrorFromRil; } else { result = SetupResult.ERR_RilError; } } // An error occurred so clear properties if (result != SetupResult.SUCCESS) { log("onSetupConnectionCompleted with an error, clearing LinkProperties"); linkProperties.clear(); } mLinkProperties = linkProperties; } if (DBG) { log("onSetupConnectionCompleted: DataConnection setup result='" + result + "' on cid=" + cid); if (result == SetupResult.SUCCESS) { log("onSetupConnectionCompleted: LinkProperties: " + mLinkProperties.toString()); } } return result; } /** * The parent state for all other states. */ private class DcDefaultState extends HierarchicalState { @Override protected boolean processMessage(Message msg) { AsyncResult ar; switch (msg.what) { case EVENT_RESET: if (DBG) log("DcDefaultState: msg.what=EVENT_RESET"); clearSettings(); if (msg.obj != null) { notifyDisconnectCompleted((DisconnectParams) msg.obj); } transitionTo(mInactiveState); break; case EVENT_CONNECT: if (DBG) log("DcDefaultState: msg.what=EVENT_CONNECT, fail not expected"); ConnectionParams cp = (ConnectionParams) msg.obj; notifyConnectCompleted(cp, FailCause.UNKNOWN); break; case EVENT_DISCONNECT: if (DBG) log("DcDefaultState: msg.what=EVENT_DISCONNECT"); notifyDisconnectCompleted((DisconnectParams) msg.obj); break; default: if (DBG) { log("DcDefaultState: shouldn't happen but ignore msg.what=" + msg.what); } break; } return true; } } private DcDefaultState mDefaultState = new DcDefaultState(); /** * The state machine is inactive and expects a EVENT_CONNECT. */ private class DcInactiveState extends HierarchicalState { private ConnectionParams mConnectionParams = null; private FailCause mFailCause = null; private DisconnectParams mDisconnectParams = null; public void setEnterNotificationParams(ConnectionParams cp, FailCause cause) { log("DcInactiveState: setEnterNoticationParams cp,cause"); mConnectionParams = cp; mFailCause = cause; } public void setEnterNotificationParams(DisconnectParams dp) { log("DcInactiveState: setEnterNoticationParams dp"); mDisconnectParams = dp; } @Override protected void enter() { mTag += 1; /** * Now that we've transitioned to Inactive state we * can send notifications. Previously we sent the * notifications in the processMessage handler but * that caused a race condition because the synchronous * call to isInactive. */ if ((mConnectionParams != null) && (mFailCause != null)) { log("DcInactiveState: enter notifyConnectCompleted"); notifyConnectCompleted(mConnectionParams, mFailCause); } if (mDisconnectParams != null) { log("DcInactiveState: enter notifyDisconnectCompleted"); notifyDisconnectCompleted(mDisconnectParams); } } @Override protected void exit() { // clear notifications mConnectionParams = null; mFailCause = null; mDisconnectParams = null; } @Override protected boolean processMessage(Message msg) { boolean retVal; switch (msg.what) { case EVENT_RESET: if (DBG) { log("DcInactiveState: msg.what=EVENT_RESET, ignore we're already reset"); } if (msg.obj != null) { notifyDisconnectCompleted((DisconnectParams) msg.obj); } retVal = true; break; case EVENT_CONNECT: if (DBG) log("DcInactiveState msg.what=EVENT_CONNECT"); ConnectionParams cp = (ConnectionParams) msg.obj; cp.tag = mTag; onConnect(cp); transitionTo(mActivatingState); retVal = true; break; default: if (DBG) log("DcInactiveState nothandled msg.what=" + msg.what); retVal = false; break; } return retVal; } } private DcInactiveState mInactiveState = new DcInactiveState(); /** * The state machine is activating a connection. */ private class DcActivatingState extends HierarchicalState { @Override protected boolean processMessage(Message msg) { boolean retVal; AsyncResult ar; ConnectionParams cp; switch (msg.what) { case EVENT_DISCONNECT: if (DBG) log("DcActivatingState deferring msg.what=EVENT_DISCONNECT"); deferMessage(msg); retVal = true; break; case EVENT_SETUP_DATA_CONNECTION_DONE: if (DBG) log("DcActivatingState msg.what=EVENT_SETUP_DATA_CONNECTION_DONE"); ar = (AsyncResult) msg.obj; cp = (ConnectionParams) ar.userObj; SetupResult result = onSetupConnectionCompleted(ar); if (DBG) log("DcActivatingState onSetupConnectionCompleted result=" + result); switch (result) { case SUCCESS: // All is well mActiveState.setEnterNotificationParams(cp, FailCause.NONE); transitionTo(mActiveState); break; case ERR_BadCommand: // Vendor ril rejected the command and didn't connect. // Transition to inactive but send notifications after // we've entered the mInactive state. mInactiveState.setEnterNotificationParams(cp, result.mFailCause); transitionTo(mInactiveState); break; case ERR_UnacceptableParameter: // The addresses given from the RIL are bad tearDownData(cp); transitionTo(mDisconnectingErrorCreatingConnection); break; case ERR_GetLastErrorFromRil: // Request failed and this is an old RIL phone.mCM.getLastDataCallFailCause( obtainMessage(EVENT_GET_LAST_FAIL_DONE, cp)); break; case ERR_RilError: // Request failed and mFailCause has the reason mInactiveState.setEnterNotificationParams(cp, result.mFailCause); transitionTo(mInactiveState); break; case ERR_Stale: // Request is stale, ignore. break; default: throw new RuntimeException("Unkown SetupResult, should not happen"); } retVal = true; break; case EVENT_GET_LAST_FAIL_DONE: ar = (AsyncResult) msg.obj; cp = (ConnectionParams) ar.userObj; FailCause cause = FailCause.UNKNOWN; if (cp.tag == mTag) { if (DBG) log("DcActivatingState msg.what=EVENT_GET_LAST_FAIL_DONE"); if (ar.exception == null) { int rilFailCause = ((int[]) (ar.result))[0]; cause = FailCause.fromInt(rilFailCause); } // Transition to inactive but send notifications after // we've entered the mInactive state. mInactiveState.setEnterNotificationParams(cp, cause); transitionTo(mInactiveState); } else { if (DBG) { log("DcActivatingState EVENT_GET_LAST_FAIL_DONE is stale cp.tag=" + cp.tag + ", mTag=" + mTag); } } retVal = true; break; default: if (DBG) log("DcActivatingState not handled msg.what=" + msg.what); retVal = false; break; } return retVal; } } private DcActivatingState mActivatingState = new DcActivatingState(); /** * The state machine is connected, expecting an EVENT_DISCONNECT. */ private class DcActiveState extends HierarchicalState { private ConnectionParams mConnectionParams = null; private FailCause mFailCause = null; public void setEnterNotificationParams(ConnectionParams cp, FailCause cause) { log("DcInactiveState: setEnterNoticationParams cp,cause"); mConnectionParams = cp; mFailCause = cause; } @Override public void enter() { /** * Now that we've transitioned to Active state we * can send notifications. Previously we sent the * notifications in the processMessage handler but * that caused a race condition because the synchronous * call to isActive. */ if ((mConnectionParams != null) && (mFailCause != null)) { log("DcActiveState: enter notifyConnectCompleted"); notifyConnectCompleted(mConnectionParams, mFailCause); } } @Override protected void exit() { // clear notifications mConnectionParams = null; mFailCause = null; } @Override protected boolean processMessage(Message msg) { boolean retVal; switch (msg.what) { case EVENT_DISCONNECT: if (DBG) log("DcActiveState msg.what=EVENT_DISCONNECT"); DisconnectParams dp = (DisconnectParams) msg.obj; dp.tag = mTag; tearDownData(dp); transitionTo(mDisconnectingState); retVal = true; break; default: if (DBG) log("DcActiveState nothandled msg.what=" + msg.what); retVal = false; break; } return retVal; } } private DcActiveState mActiveState = new DcActiveState(); /** * The state machine is disconnecting. */ private class DcDisconnectingState extends HierarchicalState { @Override protected boolean processMessage(Message msg) { boolean retVal; switch (msg.what) { case EVENT_DEACTIVATE_DONE: if (DBG) log("DcDisconnectingState msg.what=EVENT_DEACTIVATE_DONE"); AsyncResult ar = (AsyncResult) msg.obj; DisconnectParams dp = (DisconnectParams) ar.userObj; if (dp.tag == mTag) { // Transition to inactive but send notifications after // we've entered the mInactive state. mInactiveState.setEnterNotificationParams((DisconnectParams) ar.userObj); transitionTo(mInactiveState); } else { if (DBG) log("DcDisconnectState EVENT_DEACTIVATE_DONE stale dp.tag=" + dp.tag + " mTag=" + mTag); } retVal = true; break; default: if (DBG) log("DcDisconnectingState not handled msg.what=" + msg.what); retVal = false; break; } return retVal; } } private DcDisconnectingState mDisconnectingState = new DcDisconnectingState(); /** * The state machine is disconnecting after an creating a connection. */ private class DcDisconnectionErrorCreatingConnection extends HierarchicalState { @Override protected boolean processMessage(Message msg) { boolean retVal; switch (msg.what) { case EVENT_DEACTIVATE_DONE: AsyncResult ar = (AsyncResult) msg.obj; ConnectionParams cp = (ConnectionParams) ar.userObj; if (cp.tag == mTag) { if (DBG) { log("DcDisconnectionErrorCreatingConnection" + " msg.what=EVENT_DEACTIVATE_DONE"); } // Transition to inactive but send notifications after // we've entered the mInactive state. mInactiveState.setEnterNotificationParams(cp, FailCause.UNACCEPTABLE_NETWORK_PARAMETER); transitionTo(mInactiveState); } else { if (DBG) { log("DcDisconnectionErrorCreatingConnection EVENT_DEACTIVATE_DONE" + " stale dp.tag=" + cp.tag + ", mTag=" + mTag); } } retVal = true; break; default: if (DBG) { log("DcDisconnectionErrorCreatingConnection not handled msg.what=" + msg.what); } retVal = false; break; } return retVal; } } private DcDisconnectionErrorCreatingConnection mDisconnectingErrorCreatingConnection = new DcDisconnectionErrorCreatingConnection(); // ******* public interface /** * Disconnect from the network. * * @param onCompletedMsg is sent with its msg.obj as an AsyncResult object. * With AsyncResult.userObj set to the original msg.obj. */ public void reset(Message onCompletedMsg) { sendMessage(obtainMessage(EVENT_RESET, new DisconnectParams(onCompletedMsg))); } /** * Reset the connection and wait for it to complete. * TODO: Remove when all callers only need the asynchronous * reset defined above. */ public void resetSynchronously() { ResetSynchronouslyLock lockObj = new ResetSynchronouslyLock(); synchronized(lockObj) { sendMessage(obtainMessage(EVENT_RESET, new DisconnectParams(lockObj))); try { lockObj.wait(); } catch (InterruptedException e) { log("blockingReset: unexpected interrupted of wait()"); } } } /** * Connect to the apn and return an AsyncResult in onCompletedMsg. * Used for cellular networks that use Acesss Point Names (APN) such * as GSM networks. * * @param onCompletedMsg is sent with its msg.obj as an AsyncResult object. * With AsyncResult.userObj set to the original msg.obj, * AsyncResult.result = FailCause and AsyncResult.exception = Exception(). * @param apn is the Access Point Name to connect to */ public void connect(Message onCompletedMsg, ApnSetting apn) { sendMessage(obtainMessage(EVENT_CONNECT, new ConnectionParams(apn, onCompletedMsg))); } /** * Connect to the apn and return an AsyncResult in onCompletedMsg. * * @param onCompletedMsg is sent with its msg.obj as an AsyncResult object. * With AsyncResult.userObj set to the original msg.obj, * AsyncResult.result = FailCause and AsyncResult.exception = Exception(). */ public void connect(Message onCompletedMsg) { sendMessage(obtainMessage(EVENT_CONNECT, new ConnectionParams(null, onCompletedMsg))); } /** * Disconnect from the network. * * @param onCompletedMsg is sent with its msg.obj as an AsyncResult object. * With AsyncResult.userObj set to the original msg.obj. */ public void disconnect(Message onCompletedMsg) { sendMessage(obtainMessage(EVENT_DISCONNECT, new DisconnectParams(onCompletedMsg))); } // ****** The following are used for debugging. /** * TODO: This should be an asynchronous call and we wouldn't * have to use handle the notification in the DcInactiveState.enter. * * @return true if the state machine is in the inactive state. */ public boolean isInactive() { boolean retVal = getCurrentState() == mInactiveState; return retVal; } /** * TODO: This should be an asynchronous call and we wouldn't * have to use handle the notification in the DcActiveState.enter. * * @return true if the state machine is in the active state. */ public boolean isActive() { boolean retVal = getCurrentState() == mActiveState; return retVal; } /** * Return the LinkProperties for the connection. * * @return a copy of the LinkProperties, is never null. */ public LinkProperties getLinkProperties() { return new LinkProperties(mLinkProperties); } /** * A capability is an Integer/String pair, the capabilities * are defined in the class LinkSocket#Key. * * @return a copy of this connections capabilities, may be empty but never null. */ public LinkCapabilities getLinkCapabilities() { return new LinkCapabilities(mCapabilities); } /** * @return the current state as a string. */ public String getStateAsString() { String retVal = getCurrentState().getName(); return retVal; } /** * @return the time of when this connection was created. */ public long getConnectionTime() { return createTime; } /** * @return the time of the last failure. */ public long getLastFailTime() { return lastFailTime; } /** * @return the last cause of failure. */ public FailCause getLastFailCause() { return lastFailCause; } /** * @return the current ApnSetting */ public ApnSetting getApn() { return mApn; } }