/* * 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.telephony.gsm.ApnSetting; import com.android.internal.util.HierarchicalState; import com.android.internal.util.HierarchicalStateMachine; import android.os.AsyncResult; import android.os.Message; import android.os.SystemProperties; import android.util.EventLog; /** * {@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. * * Below is the state machine description for this class. * * DataConnection { * + mDefaultState { * EVENT_RESET { clearSettings, notifiyDisconnectCompleted, >mInactiveState }. * EVENT_CONNECT { notifyConnectCompleted(FailCause.UNKNOWN) }. * EVENT_DISCONNECT { notifyDisconnectCompleted }. * * // Ignored messages * EVENT_SETUP_DATA_CONNECTION_DONE, * EVENT_GET_LAST_FAIL_DONE, * EVENT_DEACTIVATE_DONE. * } * ++ # mInactiveState * e(doNotifications) * x(clearNotifications) { * EVENT_RESET { notifiyDisconnectCompleted }. * EVENT_CONNECT {startConnecting, >mActivatingState }. * } * ++ mActivatingState { * EVENT_DISCONNECT { %EVENT_DISCONNECT }. * EVENT_SETUP_DATA_CONNECTION_DONE { * if (SUCCESS) { notifyConnectCompleted(FailCause.NONE), >mActiveState }. * if (ERR_BadCommand) { * notifyConnectCompleted(FailCause.UNKNOWN), >mInactiveState }. * if (ERR_BadDns) { tearDownData($DEACTIVATE_DONE), >mDisconnectingBadDnsState }. * if (ERR_Other) { getLastDataCallFailCause($EVENT_GET_LAST_FAIL_DONE) }. * if (ERR_Stale) {}. * } * EVENT_GET_LAST_FAIL_DONE { notifyConnectCompleted(result), >mInactive }. * } * ++ mActiveState { * EVENT_DISCONNECT { tearDownData($EVENT_DEACTIVATE_DONE), >mDisconnecting }. * } * ++ mDisconnectingState { * EVENT_DEACTIVATE_DONE { notifyDisconnectCompleted, >mInactiveState }. * } * ++ mDisconnectingBadDnsState { * EVENT_DEACTIVATE_DONE { notifyConnectComplete(FailCause.UNKNOWN), >mInactiveState }. * } * } */ 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 { ERR_BadCommand, ERR_BadDns, ERR_Other, ERR_Stale, SUCCESS; public FailCause mFailCause; @Override public String toString() { switch (this) { case ERR_BadCommand: return "Bad Command"; case ERR_BadDns: return "Bad DNS"; case ERR_Other: return "Other error"; case ERR_Stale: return "Stale command"; case SUCCESS: return "SUCCESS"; default: return "unknown"; } } } /** * 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. */ public enum FailCause { NONE, OPERATOR_BARRED, INSUFFICIENT_RESOURCES, MISSING_UNKNOWN_APN, UNKNOWN_PDP_ADDRESS, USER_AUTHENTICATION, ACTIVATION_REJECT_GGSN, ACTIVATION_REJECT_UNSPECIFIED, SERVICE_OPTION_NOT_SUPPORTED, SERVICE_OPTION_NOT_SUBSCRIBED, SERVICE_OPTION_OUT_OF_ORDER, NSAPI_IN_USE, PROTOCOL_ERRORS, REGISTRATION_FAIL, GPRS_REGISTRATION_FAIL, UNKNOWN, RADIO_NOT_AVAILABLE; public boolean isPermanentFail() { return (this == OPERATOR_BARRED) || (this == MISSING_UNKNOWN_APN) || (this == UNKNOWN_PDP_ADDRESS) || (this == USER_AUTHENTICATION) || (this == ACTIVATION_REJECT_GGSN) || (this == ACTIVATION_REJECT_UNSPECIFIED) || (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) || (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); } @Override public String toString() { switch (this) { case NONE: return "No Error"; case OPERATOR_BARRED: return "Operator Barred"; case INSUFFICIENT_RESOURCES: return "Insufficient Resources"; case MISSING_UNKNOWN_APN: return "Missing / Unknown APN"; case UNKNOWN_PDP_ADDRESS: return "Unknown PDP Address"; case USER_AUTHENTICATION: return "Error User Authentication"; case ACTIVATION_REJECT_GGSN: return "Activation Reject GGSN"; case ACTIVATION_REJECT_UNSPECIFIED: return "Activation Reject unspecified"; case SERVICE_OPTION_NOT_SUPPORTED: return "Data Not Supported"; case SERVICE_OPTION_NOT_SUBSCRIBED: return "Data Not subscribed"; case SERVICE_OPTION_OUT_OF_ORDER: return "Data Services Out of Order"; case NSAPI_IN_USE: return "NSAPI in use"; case PROTOCOL_ERRORS: return "Protocol Errors"; case REGISTRATION_FAIL: return "Network Registration Failure"; case GPRS_REGISTRATION_FAIL: return "Data Network Registration Failure"; case RADIO_NOT_AVAILABLE: return "Radio Not Available"; default: return "Unknown Data Error"; } } } // ***** 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 int mTag; protected PhoneBase phone; protected int cid; protected String interfaceName; protected String ipAddress; protected String gatewayAddress; protected String[] dnsServers; protected long createTime; protected long lastFailTime; protected FailCause lastFailCause; protected static final String NULL_IP = "0.0.0.0"; Object userData; //***** Abstract methods public abstract String toString(); protected abstract void onConnect(ConnectionParams cp); protected abstract FailCause getFailCauseFromRequest(int rilCause); protected abstract boolean isDnsOk(String[] domainNameServers); protected abstract void log(String s); //***** Constructor protected DataConnection(PhoneBase phone, String name) { super(name); if (DBG) log("DataConnection constructor E"); this.phone = phone; this.cid = -1; this.dnsServers = new String[2]; clearSettings(); setDbg(false); addState(mDefaultState); addState(mInactiveState, mDefaultState); addState(mActivatingState, mDefaultState); addState(mActiveState, mDefaultState); addState(mDisconnectingState, mDefaultState); addState(mDisconnectingBadDnsState, 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) { if (phone.mCM.getRadioState().isOn()) { if (DBG) log("tearDownData radio is on, call deactivateDataCall"); phone.mCM.deactivateDataCall(cid, 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.what=%d msg.obj=%s", msg.what, ((msg.obj instanceof String) ? (String) msg.obj : ""))); AsyncResult.forMessage(msg); msg.sendToTarget(); } if (dp.lockObj != null) { synchronized(dp.lockObj) { dp.lockObj.notify(); } } clearSettings(); } /** * Clear all settings called when entering mInactiveState. */ protected void clearSettings() { if (DBG) log("clearSettings"); this.createTime = -1; this.lastFailTime = -1; this.lastFailCause = FailCause.NONE; interfaceName = null; ipAddress = null; gatewayAddress = null; dnsServers[0] = null; dnsServers[1] = null; } /** * Process setup completion. * * @param ar is the result * @return SetupResult. */ private SetupResult onSetupConnectionCompleted(AsyncResult ar) { SetupResult result; String[] response = ((String[]) ar.result); ConnectionParams cp = (ConnectionParams) ar.userObj; if (ar.exception != null) { if (DBG) log("DataConnection Init failed " + ar.exception); 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 { result = SetupResult.ERR_Other; } } 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 " + response.length + " response strings:"); // for (int i = 0; i < response.length; i++) { // log(" response[" + i + "]='" + response[i] + "'"); // } if (response.length >= 2) { cid = Integer.parseInt(response[0]); interfaceName = response[1]; if (response.length > 2) { ipAddress = response[2]; String prefix = "net." + interfaceName + "."; gatewayAddress = SystemProperties.get(prefix + "gw"); dnsServers[0] = SystemProperties.get(prefix + "dns1"); dnsServers[1] = SystemProperties.get(prefix + "dns2"); if (DBG) { log("interface=" + interfaceName + " ipAddress=" + ipAddress + " gateway=" + gatewayAddress + " DNS1=" + dnsServers[0] + " DNS2=" + dnsServers[1]); } if (isDnsOk(dnsServers)) { result = SetupResult.SUCCESS; } else { result = SetupResult.ERR_BadDns; } } else { result = SetupResult.SUCCESS; } } else { result = SetupResult.ERR_Other; } } if (DBG) log("DataConnection setup result='" + result + "' on cid=" + cid); 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); 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_BadDns: // Connection succeeded but DNS info is bad so disconnect EventLog.writeEvent(EventLogTags.PDP_BAD_DNS_ADDRESS, dnsServers[0]); tearDownData(cp); transitionTo(mDisconnectingBadDnsState); break; case ERR_Other: // Request the failure cause and process in this state phone.mCM.getLastDataCallFailCause( obtainMessage(EVENT_GET_LAST_FAIL_DONE, cp)); 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 = getFailCauseFromRequest(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 a bad dns setup * was found in mInactivatingState. */ private class DcDisconnectingBadDnsState 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("DcDisconnectingBadDnsState msg.what=EVENT_DEACTIVATE_DONE"); // Transition to inactive but send notifications after // we've entered the mInactive state. mInactiveState.setEnterNotificationParams(cp, FailCause.UNKNOWN); transitionTo(mInactiveState); } else { if (DBG) log("DcDisconnectingBadDnsState EVENT_DEACTIVE_DONE stale dp.tag=" + cp.tag + ", mTag=" + mTag); } retVal = true; break; default: if (DBG) log("DcDisconnectingBadDnsState not handled msg.what=" + msg.what); retVal = false; break; } return retVal; } } private DcDisconnectingBadDnsState mDisconnectingBadDnsState = new DcDisconnectingBadDnsState(); // ******* 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 Acess 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 Acces 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 interface name as a string. */ public String getInterface() { return interfaceName; } /** * @return the ip address as a string. */ public String getIpAddress() { return ipAddress; } /** * @return the gateway address as a string. */ public String getGatewayAddress() { return gatewayAddress; } /** * @return an array of associated DNS addresses. */ public String[] getDnsServers() { return dnsServers; } /** * @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; } }