/*
* Copyright (C) 2014 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.dataconnection;
import com.android.internal.util.AsyncChannel;
import com.android.internal.util.IState;
import com.android.internal.util.Protocol;
import com.android.internal.util.State;
import com.android.internal.util.StateMachine;
import com.android.internal.telephony.CommandException;
import com.android.internal.telephony.Phone;
import com.android.internal.telephony.PhoneConstants;
import com.android.internal.telephony.PhoneBase;
import com.android.internal.telephony.PhoneProxy;
import com.android.internal.telephony.dataconnection.DcSwitchAsyncChannel.RequestInfo;
import android.os.AsyncResult;
import android.os.Message;
import android.telephony.Rlog;
public class DcSwitchStateMachine extends StateMachine {
private static final boolean DBG = true;
private static final boolean VDBG = false;
private static final String LOG_TAG = "DcSwitchSM";
// ***** Event codes for driving the state machine
private static final int BASE = Protocol.BASE_DATA_CONNECTION_TRACKER + 0x00001000;
private static final int EVENT_CONNECTED = BASE + 0;
private static final int EVENT_DATA_ALLOWED = BASE + 1;
private static final int CMD_RETRY_ATTACH = BASE + 2;
private static final int EVENT_DATA_DISALLOWED = BASE + 3;
private int mId;
private Phone mPhone;
private AsyncChannel mAc;
private IdleState mIdleState = new IdleState();
private EmergencyState mEmergencyState = new EmergencyState();
private AttachingState mAttachingState = new AttachingState();
private AttachedState mAttachedState = new AttachedState();
private DetachingState mDetachingState = new DetachingState();
private DefaultState mDefaultState = new DefaultState();
// In case of transition to emergency state, this tracks the state of the state machine prior
// to entering emergency state
private IState mPreEmergencyState;
protected DcSwitchStateMachine(Phone phone, String name, int id) {
super(name);
if (DBG) log("DcSwitchState constructor E");
mPhone = phone;
mId = id;
addState(mDefaultState);
addState(mIdleState, mDefaultState);
addState(mEmergencyState, mDefaultState);
addState(mAttachingState, mDefaultState);
addState(mAttachedState, mDefaultState);
addState(mDetachingState, mDefaultState);
setInitialState(mIdleState);
if (DBG) log("DcSwitchState constructor X");
}
// public void notifyDataConnection(int phoneId, String state, String reason,
// String apnName, String apnType, boolean unavailable) {
// if (phoneId == mId &&
// TextUtils.equals(state, PhoneConstants.DataState.CONNECTED.toString())) {
// sendMessage(obtainMessage(EVENT_CONNECTED));
// }
// }
private class IdleState extends State {
@Override
public void enter() {
if (DBG) log("IdleState: enter");
try {
DctController.getInstance().processRequests();
} catch (RuntimeException e) {
if (DBG) loge("DctController is not ready");
}
}
@Override
public boolean processMessage(Message msg) {
boolean retVal;
switch (msg.what) {
case DcSwitchAsyncChannel.REQ_CONNECT: {
RequestInfo apnRequest = (RequestInfo)msg.obj;
apnRequest.log("DcSwitchStateMachine.IdleState: REQ_CONNECT");
if (DBG) log("IdleState: REQ_CONNECT, apnRequest=" + apnRequest);
transitionTo(mAttachingState);
retVal = HANDLED;
break;
}
case DcSwitchAsyncChannel.REQ_DISCONNECT_ALL: {
if (DBG) log("AttachingState: REQ_DISCONNECT_ALL" );
// Shouldn't have any requests, but why not try..
DctController.getInstance().releaseAllRequests(mId);
retVal = HANDLED;
break;
}
case DcSwitchAsyncChannel.EVENT_DATA_ATTACHED:
if (DBG) {
log("AttachingState: EVENT_DATA_ATTACHED");
}
transitionTo(mAttachedState);
retVal = HANDLED;
break;
case EVENT_CONNECTED: {
if (DBG) {
log("IdleState: Receive invalid event EVENT_CONNECTED!");
}
retVal = HANDLED;
break;
}
default:
if (VDBG) {
log("IdleState: nothandled msg.what=0x" +
Integer.toHexString(msg.what));
}
retVal = NOT_HANDLED;
break;
}
return retVal;
}
}
private class EmergencyState extends State {
@Override
public boolean processMessage(Message msg) {
final PhoneBase pb = (PhoneBase)((PhoneProxy)mPhone).getActivePhone();
if (!pb.mDcTracker.isEmergency()) {
loge("EmergencyState: isEmergency() is false. deferMessage msg.what=0x" +
Integer.toHexString(msg.what));
deferMessage(msg);
transitionTo(mPreEmergencyState);
return HANDLED;
}
switch (msg.what) {
case DcSwitchAsyncChannel.EVENT_EMERGENCY_CALL_ENDED: {
transitionTo(mPreEmergencyState);
break;
}
case DcSwitchAsyncChannel.EVENT_EMERGENCY_CALL_STARTED: {
loge("EmergencyState: ignoring EVENT_EMERGENCY_CALL_STARTED");
break;
}
default: {
log("EmergencyState: deferMessage msg.what=0x" + Integer.toHexString(msg.what));
deferMessage(msg);
break;
}
}
return HANDLED;
}
}
private class AttachingState extends State {
private int mCurrentAllowedSequence = 0;
@Override
public void enter() {
log("AttachingState: enter");
doEnter();
}
private void doEnter() {
final PhoneBase pb = (PhoneBase)((PhoneProxy)mPhone).getActivePhone();
pb.mCi.setDataAllowed(true, obtainMessage(EVENT_DATA_ALLOWED,
++mCurrentAllowedSequence, 0));
}
@Override
public boolean processMessage(Message msg) {
boolean retVal;
switch (msg.what) {
case DcSwitchAsyncChannel.REQ_CONNECT: {
RequestInfo apnRequest = (RequestInfo)msg.obj;
apnRequest.log("DcSwitchStateMachine.AttachingState: REQ_CONNECT");
if (DBG) log("AttachingState: REQ_CONNECT, apnRequest=" + apnRequest);
// do nothing - wait til we attach and then we'll execute all requests
retVal = HANDLED;
break;
}
case EVENT_DATA_ALLOWED: {
AsyncResult ar = (AsyncResult)msg.obj;
if (mCurrentAllowedSequence != msg.arg1) {
loge("EVENT_DATA_ALLOWED ignored arg1=" + msg.arg1 + ", seq=" +
mCurrentAllowedSequence);
} else if (ar.exception != null) {
if (ar.exception instanceof CommandException) {
CommandException e = (CommandException)ar.exception;
if (e.getCommandError() ==
CommandException.Error.REQUEST_NOT_SUPPORTED) {
// must be on a single-sim device so stay in Attaching
// this is important to avoid an infinite loop
retVal = HANDLED;
break;
}
}
loge("EVENT_DATA_ALLOWED failed, " + ar.exception);
transitionTo(mIdleState);
}
retVal = HANDLED;
break;
}
case DcSwitchAsyncChannel.REQ_RETRY_CONNECT: {
if (DBG) log("AttachingState going to retry");
doEnter();
retVal = HANDLED;
break;
}
case DcSwitchAsyncChannel.EVENT_DATA_ATTACHED: {
if (DBG) {
log("AttachingState: EVENT_DATA_ATTACHED");
}
transitionTo(mAttachedState);
retVal = HANDLED;
break;
}
case DcSwitchAsyncChannel.REQ_DISCONNECT_ALL: {
if (DBG) {
log("AttachingState: REQ_DISCONNECT_ALL" );
}
// modem gets unhappy if we try to detach while attaching
// wait til attach finishes.
deferMessage(msg);
retVal = HANDLED;
break;
}
default:
if (VDBG) {
log("AttachingState: nothandled msg.what=0x" +
Integer.toHexString(msg.what));
}
retVal = NOT_HANDLED;
break;
}
return retVal;
}
}
private class AttachedState extends State {
@Override
public void enter() {
if (DBG) log("AttachedState: enter");
//When enter attached state, we need exeute all requests.
DctController.getInstance().executeAllRequests(mId);
}
@Override
public boolean processMessage(Message msg) {
boolean retVal;
switch (msg.what) {
case DcSwitchAsyncChannel.REQ_CONNECT: {
RequestInfo apnRequest = (RequestInfo)msg.obj;
apnRequest.log("DcSwitchStateMachine.AttachedState: REQ_CONNECT");
if (DBG) log("AttachedState: REQ_CONNECT, apnRequest=" + apnRequest);
DctController.getInstance().executeRequest(apnRequest);
retVal = HANDLED;
break;
}
case DcSwitchAsyncChannel.REQ_DISCONNECT_ALL: {
if (DBG) {
log("AttachedState: REQ_DISCONNECT_ALL" );
}
DctController.getInstance().releaseAllRequests(mId);
transitionTo(mDetachingState);
retVal = HANDLED;
break;
}
case DcSwitchAsyncChannel.EVENT_DATA_DETACHED: {
if (DBG) {
log("AttachedState: EVENT_DATA_DETACHED");
}
transitionTo(mAttachingState);
retVal = HANDLED;
break;
}
default:
if (VDBG) {
log("AttachedState: nothandled msg.what=0x" +
Integer.toHexString(msg.what));
}
retVal = NOT_HANDLED;
break;
}
return retVal;
}
}
private class DetachingState extends State {
private int mCurrentDisallowedSequence = 0;
@Override
public void enter() {
if (DBG) log("DetachingState: enter");
PhoneBase pb = (PhoneBase)((PhoneProxy)mPhone).getActivePhone();
pb.mCi.setDataAllowed(false, obtainMessage(EVENT_DATA_DISALLOWED,
++mCurrentDisallowedSequence, 0));
}
@Override
public boolean processMessage(Message msg) {
boolean retVal;
switch (msg.what) {
case DcSwitchAsyncChannel.REQ_CONNECT: {
RequestInfo apnRequest = (RequestInfo)msg.obj;
apnRequest.log("DcSwitchStateMachine.DetachingState: REQ_CONNECT");
if (DBG) log("DetachingState: REQ_CONNECT, apnRequest=" + apnRequest);
// can't process this now - wait until we return to idle
deferMessage(msg);
retVal = HANDLED;
break;
}
case DcSwitchAsyncChannel.EVENT_DATA_DETACHED: {
if (DBG) {
log("DetachingState: EVENT_DATA_DETACHED");
}
transitionTo(mIdleState);
retVal = HANDLED;
break;
}
case EVENT_DATA_DISALLOWED: {
AsyncResult ar = (AsyncResult)msg.obj;
if (mCurrentDisallowedSequence != msg.arg1) {
loge("EVENT_DATA_DISALLOWED ignored arg1=" + msg.arg1 + ", seq=" +
mCurrentDisallowedSequence);
} else if (ar.exception != null) {
// go back to attached as we still think we are. Notifications
// from the ServiceStateTracker will kick us out of attached when
// appropriate.
loge("EVENT_DATA_DISALLOWED failed, " + ar.exception);
transitionTo(mAttachedState);
}
retVal = HANDLED;
break;
}
case DcSwitchAsyncChannel.REQ_DISCONNECT_ALL: {
if (DBG) {
log("DetachingState: REQ_DISCONNECT_ALL, already detaching" );
}
retVal = HANDLED;
break;
}
default:
if (VDBG) {
log("DetachingState: nothandled msg.what=0x" +
Integer.toHexString(msg.what));
}
retVal = NOT_HANDLED;
break;
}
return retVal;
}
}
private class DefaultState extends State {
@Override
public boolean processMessage(Message msg) {
switch (msg.what) {
case AsyncChannel.CMD_CHANNEL_FULL_CONNECTION: {
if (mAc != null) {
if (VDBG) log("Disconnecting to previous connection mAc=" + mAc);
mAc.replyToMessage(msg, AsyncChannel.CMD_CHANNEL_FULLY_CONNECTED,
AsyncChannel.STATUS_FULL_CONNECTION_REFUSED_ALREADY_CONNECTED);
} else {
mAc = new AsyncChannel();
mAc.connected(null, getHandler(), msg.replyTo);
if (VDBG) log("DcDefaultState: FULL_CONNECTION reply connected");
mAc.replyToMessage(msg, AsyncChannel.CMD_CHANNEL_FULLY_CONNECTED,
AsyncChannel.STATUS_SUCCESSFUL, mId, "hi");
}
break;
}
case AsyncChannel.CMD_CHANNEL_DISCONNECT: {
if (VDBG) log("CMD_CHANNEL_DISCONNECT");
mAc.disconnect();
break;
}
case AsyncChannel.CMD_CHANNEL_DISCONNECTED: {
if (VDBG) log("CMD_CHANNEL_DISCONNECTED");
mAc = null;
break;
}
case DcSwitchAsyncChannel.REQ_IS_IDLE_STATE: {
boolean val = getCurrentState() == mIdleState;
if (VDBG) log("REQ_IS_IDLE_STATE isIdle=" + val);
mAc.replyToMessage(msg, DcSwitchAsyncChannel.RSP_IS_IDLE_STATE, val ? 1 : 0);
break;
}
case DcSwitchAsyncChannel.REQ_IS_IDLE_OR_DETACHING_STATE: {
boolean val = (getCurrentState() == mIdleState ||
getCurrentState() == mDetachingState);
if (VDBG) log("REQ_IS_IDLE_OR_DETACHING_STATE isIdleDetaching=" + val);
mAc.replyToMessage(msg,
DcSwitchAsyncChannel.RSP_IS_IDLE_OR_DETACHING_STATE, val ? 1 : 0);
break;
}
case DcSwitchAsyncChannel.EVENT_EMERGENCY_CALL_STARTED: {
mPreEmergencyState = getCurrentState();
transitionTo(mEmergencyState);
break;
}
default:
if (DBG) {
log("DefaultState: shouldn't happen but ignore msg.what=0x" +
Integer.toHexString(msg.what));
}
break;
}
return HANDLED;
}
}
@Override
protected void log(String s) {
Rlog.d(LOG_TAG, "[" + getName() + "] " + s);
}
}