Merge change I0a56959e into eclair-mr2

* changes:
  Implement a HierarchicalStateMachine
This commit is contained in:
Android (Google) Code Review
2009-12-10 21:22:19 -08:00
9 changed files with 2848 additions and 456 deletions

View File

@@ -1,33 +0,0 @@
/*
* 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 android.os;
/**
* {@hide}
*/
public abstract class HandlerState {
public HandlerState() {
}
public void enter(Message message) {
}
public abstract void processMessage(Message message);
public void exit(Message message) {
}
}

View File

@@ -1,290 +0,0 @@
/*
* 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 android.os;
import android.util.Log;
import android.util.LogPrinter;
/**
* {@hide}
*
* Implement a state machine where each state is an object,
* HandlerState. Each HandlerState must implement processMessage
* and optionally enter/exit. When a state machine is created
* the initial state must be set. When messages are sent to
* a state machine the current state's processMessage method is
* invoked. If this is the first message for this state the
* enter method is called prior to processMessage and when
* transtionTo is invoked the state's exit method will be
* called after returning from processMessage.
*
* If a message should be handled in a different state the
* processMessage method may call deferMessage. This causes
* the message to be saved on a list until transitioning
* to a new state, at which time all of the deferred messages
* will be put on the front of the state machines queue and
* processed by the new current state's processMessage
* method.
*
* Below is an example state machine with two state's, S1 and S2.
* The initial state is S1 which defers all messages and only
* transition to S2 when message.what == TEST_WHAT_2. State S2
* will process each messages until it receives TEST_WHAT_2
* where it will transition back to S1:
<code>
class StateMachine1 extends HandlerStateMachine {
private static final int TEST_WHAT_1 = 1;
private static final int TEST_WHAT_2 = 2;
StateMachine1(String name) {
super(name);
setInitialState(mS1);
}
class S1 extends HandlerState {
&amp;#064;Override public void enter(Message message) {
}
&amp;#064;Override public void processMessage(Message message) {
deferMessage(message);
if (message.what == TEST_WHAT_2) {
transitionTo(mS2);
}
}
&amp;#064;Override public void exit(Message message) {
}
}
class S2 extends HandlerState {
&amp;#064;Override public void processMessage(Message message) {
// Do some processing
if (message.what == TEST_WHAT_2) {
transtionTo(mS1);
}
}
}
private S1 mS1 = new S1();
private S2 mS2 = new S2();
}
</code>
*/
public class HandlerStateMachine {
private boolean mDbg = false;
private static final String TAG = "HandlerStateMachine";
private String mName;
private SmHandler mHandler;
private HandlerThread mHandlerThread;
/**
* Handle messages sent to the state machine by calling
* the current state's processMessage. It also handles
* the enter/exit calls and placing any deferred messages
* back onto the queue when transitioning to a new state.
*/
class SmHandler extends Handler {
SmHandler(Looper looper) {
super(looper);
}
/**
* This will dispatch the message to the
* current state's processMessage.
*/
@Override
final public void handleMessage(Message msg) {
if (mDbg) Log.d(TAG, "SmHandler.handleMessage E");
if (mDestState != null) {
if (mDbg) Log.d(TAG, "SmHandler.handleMessage; new destation call enter");
mCurrentState = mDestState;
mDestState = null;
mCurrentState.enter(msg);
}
if (mCurrentState != null) {
if (mDbg) Log.d(TAG, "SmHandler.handleMessage; call processMessage");
mCurrentState.processMessage(msg);
} else {
/* Strange no state to execute */
Log.e(TAG, "handleMessage: no current state, did you call setInitialState");
}
if (mDestState != null) {
if (mDbg) Log.d(TAG, "SmHandler.handleMessage; new destination call exit");
mCurrentState.exit(msg);
/**
* Place the messages from the deferred queue:t
* on to the Handler's message queue in the
* same order that they originally arrived.
*
* We set cur.when = 0 to circumvent the check
* that this message has already been sent.
*/
while (mDeferredMessages != null) {
Message cur = mDeferredMessages;
mDeferredMessages = mDeferredMessages.next;
cur.when = 0;
if (mDbg) Log.d(TAG, "SmHandler.handleMessage; queue deferred message what="
+ cur.what + " target=" + cur.target);
sendMessageAtFrontOfQueue(cur);
}
if (mDbg) Log.d(TAG, "SmHandler.handleMessage X");
}
}
public HandlerState mCurrentState;
public HandlerState mDestState;
public Message mDeferredMessages;
}
/**
* Create an active StateMachine, one that has a
* dedicated thread/looper/queue.
*/
public HandlerStateMachine(String name) {
mName = name;
mHandlerThread = new HandlerThread(name);
mHandlerThread.start();
mHandler = new SmHandler(mHandlerThread.getLooper());
}
/**
* Get a message and set Message.target = this.
*/
public final Message obtainMessage()
{
Message msg = Message.obtain(mHandler);
if (mDbg) Log.d(TAG, "StateMachine.obtainMessage() EX target=" + msg.target);
return msg;
}
/**
* Get a message and set Message.target = this and
* Message.what = what.
*/
public final Message obtainMessage(int what) {
Message msg = Message.obtain(mHandler, what);
if (mDbg) {
Log.d(TAG, "StateMachine.obtainMessage(what) EX what=" + msg.what +
" target=" + msg.target);
}
return msg;
}
/**
* Enqueue a message to this state machine.
*/
public final void sendMessage(Message msg) {
if (mDbg) Log.d(TAG, "StateMachine.sendMessage EX msg.what=" + msg.what);
mHandler.sendMessage(msg);
}
/**
* Enqueue a message to this state machine after a delay.
*/
public final void sendMessageDelayed(Message msg, long delayMillis) {
if (mDbg) {
Log.d(TAG, "StateMachine.sendMessageDelayed EX msg.what="
+ msg.what + " delay=" + delayMillis);
}
mHandler.sendMessageDelayed(msg, delayMillis);
}
/**
* Set the initial state. This must be invoked before
* and messages are sent to the state machine.
*/
public void setInitialState(HandlerState initialState) {
if (mDbg) {
Log.d(TAG, "StateMachine.setInitialState EX initialState"
+ initialState.getClass().getName());
}
mHandler.mDestState = initialState;
}
/**
* transition to destination state. Upon returning
* from processMessage the current state's exit will
* be executed and upon the next message arriving
* destState.enter will be invoked.
*/
final public void transitionTo(HandlerState destState) {
if (mDbg) {
Log.d(TAG, "StateMachine.transitionTo EX destState"
+ destState.getClass().getName());
}
mHandler.mDestState = destState;
}
/**
* Defer this message until next state transition.
* Upon transitioning all deferred messages will be
* placed on the queue and reprocessed in the original
* order. (i.e. The next state the oldest messages will
* be processed first)
*/
final public void deferMessage(Message msg) {
if (mDbg) {
Log.d(TAG, "StateMachine.deferMessage EX mDeferredMessages="
+ mHandler.mDeferredMessages);
}
/* Copy the "msg" to "newMsg" as "msg" will be recycled */
Message newMsg = obtainMessage();
newMsg.copyFrom(msg);
/* Place on front of queue */
newMsg.next = mHandler.mDeferredMessages;
mHandler.mDeferredMessages = newMsg;
}
/**
* @return the name
*/
public String getName() {
return mName;
}
/**
* @return Handler
*/
public Handler getHandler() {
return mHandler;
}
/**
* @return if debugging is enabled
*/
public boolean isDbg() {
return mDbg;
}
/**
* Set debug enable/disabled.
*/
public void setDbg(boolean dbg) {
mDbg = dbg;
if (mDbg) {
mHandlerThread.getLooper().setMessageLogging(new LogPrinter(Log.VERBOSE, TAG));
} else {
mHandlerThread.getLooper().setMessageLogging(null);
}
}
}

View File

@@ -0,0 +1,75 @@
/**
* Copyright (C) 2009 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.util;
import android.os.Message;
/**
* {@hide}
*
* The abstract class for implementing states in a
* HierarchicalStateMachine and HandlerStateMachine.
*/
public abstract class HierarchicalState {
/**
* Constructor
*/
protected HierarchicalState() {
}
/**
* Called when a state is entered.
*/
protected void enter() {
}
/**
* Called when a message is to be processed by the
* state machine.
*
* This routine is never reentered thus no synchronization
* is needed as only one processMessage method will ever be
* executing within a state machine at any given time. This
* does mean that processing by this routine must be completed
* as expeditiously as possible as no subsequent messages will
* be processed until this routine returns.
*
* @param msg to process
* @return true if processing has completed and false
* if the parent state's processMessage should
* be invoked.
*/
abstract protected boolean processMessage(Message msg);
/**
* Called when a state is exited.
*/
protected void exit() {
}
/**
* @return name of state, but default returns the states
* class name. An instance name would be better but requiring
* it seems unnecessary.
*/
public String getName() {
String name = getClass().getName();
int lastDollar = name.lastIndexOf('$');
return name.substring(lastDollar + 1);
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,198 @@
/**
* Copyright (C) 2009 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.util;
import android.os.Message;
import java.util.Vector;
/**
* {@hide}
*
* A list of messages recently processed by the state machine.
*
* The class maintains a list of messages that have been most
* recently processed. The list is finite and may be set in the
* constructor or by calling setSize. The public interface also
* includes size which returns the number of recent messages,
* count which is the number of message processed since the
* the last setSize, get which returns a processed message and
* add which adds a processed messaged.
*/
public class ProcessedMessages {
public static final int DEFAULT_SIZE = 20;
/**
* The information maintained for a processed message.
*/
public class Info {
private int what;
private HierarchicalState state;
private HierarchicalState orgState;
/**
* Constructor
* @param message
* @param state that handled the message
* @param orgState is the first state the received the message but
* did not processes the message.
*/
Info(Message message, HierarchicalState state, HierarchicalState orgState) {
update(message, state, orgState);
}
/**
* Update the information in the record.
* @param state that handled the message
* @param orgState is the first state the received the message but
* did not processes the message.
*/
public void update(Message message, HierarchicalState state, HierarchicalState orgState) {
this.what = message.what;
this.state = state;
this.orgState = orgState;
}
/**
* @return the command that was executing
*/
public int getWhat() {
return what;
}
/**
* @return the state that handled this message
*/
public HierarchicalState getState() {
return state;
}
/**
* @return the original state that received the message.
*/
public HierarchicalState getOriginalState() {
return orgState;
}
/**
* @return as string
*/
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("what=");
sb.append(what);
sb.append(" state=");
sb.append(cn(state));
sb.append(" orgState=");
sb.append(cn(orgState));
return sb.toString();
}
/**
* @return an objects class name
*/
private String cn(Object n) {
if (n == null) {
return "null";
} else {
String name = n.getClass().getName();
int lastDollar = name.lastIndexOf('$');
return name.substring(lastDollar + 1);
}
}
}
private Vector<Info> mMessages = new Vector<Info>();
private int mMaxSize = DEFAULT_SIZE;
private int mOldestIndex = 0;
private int mCount = 0;
/**
* Constructor
*/
ProcessedMessages() {
}
ProcessedMessages(int maxSize) {
setSize(maxSize);
}
/**
* Set size of messages to maintain and clears all current messages.
*
* @param maxSize number of messages to maintain at anyone time.
*/
void setSize(int maxSize) {
mMaxSize = maxSize;
mCount = 0;
mMessages.clear();
}
/**
* @return the number of recent messages.
*/
int size() {
return mMessages.size();
}
/**
* @return the total number of messages processed since size was set.
*/
int count() {
return mCount;
}
/**
* @return the information on a particular record. 0 is the oldest
* record and size()-1 is the newest record. If the index is to
* large null is returned.
*/
Info get(int index) {
int nextIndex = mOldestIndex + index;
if (nextIndex >= mMaxSize) {
nextIndex -= mMaxSize;
}
if (nextIndex >= size()) {
return null;
} else {
return mMessages.get(nextIndex);
}
}
/**
* Add a processed message.
*
* @param message
* @param state that handled the message
* @param orgState is the first state the received the message but
* did not processes the message.
*/
void add(Message message, HierarchicalState state, HierarchicalState orgState) {
mCount += 1;
if (mMessages.size() < mMaxSize) {
mMessages.add(new Info(message, state, orgState));
} else {
Info info = mMessages.get(mOldestIndex);
mOldestIndex += 1;
if (mOldestIndex >= mMaxSize) {
mOldestIndex = 0;
}
info.update(message, state, orgState);
}
}
}

View File

@@ -203,7 +203,7 @@ class CommandParamsFactory extends Handler {
}
private void sendCmdParams(ResultCode resCode) {
mCaller.sendMessageParamsDecoded(resCode, mCmdParams);
mCaller.sendMsgParamsDecoded(resCode, mCmdParams);
}
/**

View File

@@ -20,18 +20,18 @@ import com.android.internal.telephony.gsm.SIMFileHandler;
import com.android.internal.telephony.IccUtils;
import android.os.Handler;
import android.os.HandlerState;
import android.os.HandlerStateMachine;
import com.android.internal.util.HierarchicalState;
import com.android.internal.util.HierarchicalStateMachine;
import android.os.Message;
/**
* Class used for queuing raw ril messages, decoding them into CommanParams
* objects and sending the result back to the STK Service.
*/
class RilMessageDecoder extends HandlerStateMachine {
class RilMessageDecoder extends HierarchicalStateMachine {
// constants
private static final int START = 1;
private static final int CMD_START = 1;
private static final int CMD_PARAMS_READY = 2;
// members
@@ -54,6 +54,7 @@ class RilMessageDecoder extends HandlerStateMachine {
public static synchronized RilMessageDecoder getInstance(Handler caller, SIMFileHandler fh) {
if (sInstance == null) {
sInstance = new RilMessageDecoder(caller, fh);
sInstance.start();
}
return sInstance;
}
@@ -65,7 +66,7 @@ class RilMessageDecoder extends HandlerStateMachine {
* @param rilMsg
*/
public void sendStartDecodingMessageParams(RilMessage rilMsg) {
Message msg = obtainMessage(START);
Message msg = obtainMessage(CMD_START);
msg.obj = rilMsg;
sendMessage(msg);
}
@@ -76,7 +77,7 @@ class RilMessageDecoder extends HandlerStateMachine {
* @param resCode
* @param cmdParams
*/
public void sendMessageParamsDecoded(ResultCode resCode, CommandParams cmdParams) {
public void sendMsgParamsDecoded(ResultCode resCode, CommandParams cmdParams) {
Message msg = obtainMessage(RilMessageDecoder.CMD_PARAMS_READY);
msg.arg1 = resCode.value();
msg.obj = cmdParams;
@@ -91,28 +92,31 @@ class RilMessageDecoder extends HandlerStateMachine {
private RilMessageDecoder(Handler caller, SIMFileHandler fh) {
super("RilMessageDecoder");
setDbg(false);
addState(mStateStart);
addState(mStateCmdParamsReady);
setInitialState(mStateStart);
mCaller = caller;
mCmdParamsFactory = CommandParamsFactory.getInstance(this, fh);
}
private class StateStart extends HandlerState {
@Override public void processMessage(Message msg) {
if (msg.what == START) {
private class StateStart extends HierarchicalState {
@Override protected boolean processMessage(Message msg) {
if (msg.what == CMD_START) {
if (decodeMessageParams((RilMessage)msg.obj)) {
transitionTo(mStateCmdParamsReady);
}
} else {
StkLog.d(this, "StateStart unexpected expecting START=" +
START + " got " + msg.what);
CMD_START + " got " + msg.what);
}
return true;
}
}
private class StateCmdParamsReady extends HandlerState {
@Override public void processMessage(Message msg) {
private class StateCmdParamsReady extends HierarchicalState {
@Override protected boolean processMessage(Message msg) {
if (msg.what == CMD_PARAMS_READY) {
mCurrentRilMessage.mResCode = ResultCode.fromInt(msg.arg1);
mCurrentRilMessage.mData = msg.obj;
@@ -123,6 +127,7 @@ class RilMessageDecoder extends HandlerStateMachine {
+ CMD_PARAMS_READY + " got " + msg.what);
deferMessage(msg);
}
return true;
}
}

View File

@@ -1,119 +0,0 @@
/*
* 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.unit_tests.os;
import junit.framework.TestCase;
import java.util.Vector;
import android.os.Handler;
import android.os.HandlerState;
import android.os.HandlerStateMachine;
import android.os.HandlerThread;
import android.os.Looper;
import android.os.Process;
import android.os.Message;
import android.test.suitebuilder.annotation.MediumTest;
import android.test.suitebuilder.annotation.SmallTest;
import android.util.Log;
public class HandlerStateMachineTest extends TestCase {
private static final int TEST_WHAT_1 = 1;
private static final int TEST_WHAT_2 = 2;
private static final boolean DBG = false;
private static final String TAG = "HandlerStateMachineTest";
private boolean mDidEnter = false;
private boolean mDidExit = false;
private Vector<Integer> mGotMessagesWhat = new Vector<Integer>();
/**
* This test statemachine has two states, it receives
* two messages in state mS1 deferring them until what == TEST_WHAT_2
* and then transitions to state mS2. State mS2 should then receive
* both of the deferred messages first TEST_WHAT_1 and then TEST_WHAT_2.
* When TEST_WHAT_2 is received it invokes notifyAll so the test can
* conclude.
*/
class StateMachine1 extends HandlerStateMachine {
StateMachine1(String name) {
super(name);
mThisSm = this;
setDbg(DBG);
setInitialState(mS1);
}
class S1 extends HandlerState {
@Override public void enter(Message message) {
mDidEnter = true;
}
@Override public void processMessage(Message message) {
deferMessage(message);
if (message.what == TEST_WHAT_2) {
transitionTo(mS2);
}
}
@Override public void exit(Message message) {
mDidExit = true;
}
}
class S2 extends HandlerState {
@Override public void processMessage(Message message) {
mGotMessagesWhat.add(message.what);
if (message.what == TEST_WHAT_2) {
synchronized (mThisSm) {
mThisSm.notifyAll();
}
}
}
}
private StateMachine1 mThisSm;
private S1 mS1 = new S1();
private S2 mS2 = new S2();
}
@SmallTest
public void testStateMachine1() throws Exception {
StateMachine1 sm1 = new StateMachine1("sm1");
if (sm1.isDbg()) Log.d(TAG, "testStateMachine1 E");
synchronized (sm1) {
// Send two messages
sm1.sendMessage(sm1.obtainMessage(TEST_WHAT_1));
sm1.sendMessage(sm1.obtainMessage(TEST_WHAT_2));
try {
// wait for the messages to be handled
sm1.wait();
} catch (InterruptedException e) {
Log.e(TAG, "testStateMachine1: exception while waiting " + e.getMessage());
}
}
assertTrue(mDidEnter);
assertTrue(mDidExit);
assertTrue(mGotMessagesWhat.size() == 2);
assertTrue(mGotMessagesWhat.get(0) == TEST_WHAT_1);
assertTrue(mGotMessagesWhat.get(1) == TEST_WHAT_2);
if (sm1.isDbg()) Log.d(TAG, "testStateMachine1 X");
}
}