From 1b8b98b3db5dcf0b01d1a632aafea076cc91f5a4 Mon Sep 17 00:00:00 2001 From: Wink Saville Date: Thu, 11 Mar 2010 11:49:54 -0800 Subject: [PATCH] Add quit and fix HSM EBNF. Add support for quiting the looper and stopping a thread. Allow enter/exit at top or bottom of a STATE EBNF. Fix missing brace in MSG_LIST. Change-Id: Iddf5ce908008933bf8f0646e844254183da3d1f3 --- .../util/HierarchicalStateMachine.java | 75 ++++++++++++- .../os/HierarchicalStateMachineTest.java | 102 +++++++++++++++++- 2 files changed, 173 insertions(+), 4 deletions(-) diff --git a/core/java/com/android/internal/util/HierarchicalStateMachine.java b/core/java/com/android/internal/util/HierarchicalStateMachine.java index b4af79c00e1e6..1289d36f75ce6 100644 --- a/core/java/com/android/internal/util/HierarchicalStateMachine.java +++ b/core/java/com/android/internal/util/HierarchicalStateMachine.java @@ -69,6 +69,10 @@ import java.util.HashMap; * and invoke halting. Any message subsequently received by the state * machine will cause haltedProcessMessage to be invoked. * + * If it is desirable to completely stop the state machine call quit. This + * will exit the current state and its parent and then exit from the controlling thread + * and no further messages will be processed. + * * In addition to processMessage each HierarchicalState has * an enter method and exit method which may be overridden. * @@ -422,13 +426,13 @@ class Hsm1 extends HierarchicalStateMachine { * HSM EBNF: * HSM = HSM_NAME "{" { STATE }+ "}" ; * HSM_NAME = alpha_numeric_name ; - * STATE = INTRODUCE_STATE [ ENTER ] "{" [ MESSAGES ] "}" [ EXIT ] ; + * STATE = INTRODUCE_STATE [ ENTER | [ ENTER EXIT ] "{" [ MESSAGES ] "}" [ EXIT ] ; * INTRODUCE_STATE = { STATE_DEPTH }+ [ INITIAL_STATE_INDICATOR ] STATE_NAME ; * STATE_DEPTH = "+" ; * INITIAL_STATE_INDICATOR = "#" * ENTER = "e(" SEND_ACTION | TRANSITION_ACTION | HALT_ACTION ")" ; * MESSAGES = { MSG_LIST MESSAGE_ACTIONS } ; - * MSG_LIST = { MSG_NAME { "," MSG_NAME } ; + * MSG_LIST = { MSG_NAME { "," MSG_NAME } }; * EXIT = "x(" SEND_ACTION | TRANSITION_ACTION | HALT_ACTION ")" ; * PROCESS_COMPLETION = PROCESS_IN_PARENT_OR_COMPLETE | PROCESS_COMPLETE ; * SEND_ACTION = "$" MSG_NAME ; @@ -454,11 +458,16 @@ public class HierarchicalStateMachine { private static final String TAG = "HierarchicalStateMachine"; private String mName; + public static final int HSM_QUIT_CMD = -1; + private static class HsmHandler extends Handler { /** The debug flag */ private boolean mDbg = false; + /** The quit object */ + private static final Object mQuitObj = new Object(); + /** A list of messages that this state machine has processed */ private ProcessedMessages mProcessedMessages = new ProcessedMessages(); @@ -480,6 +489,9 @@ public class HierarchicalStateMachine { /** State used when state machine is halted */ private HaltingState mHaltingState = new HaltingState(); + /** State used when state machine is quitting */ + private QuittingState mQuittingState = new QuittingState(); + /** Reference to the HierarchicalStateMachine */ private HierarchicalStateMachine mHsm; @@ -532,6 +544,16 @@ public class HierarchicalStateMachine { } } + /** + * State entered when a valid quit message is handled. + */ + private class QuittingState extends HierarchicalState { + @Override + public boolean processMessage(Message msg) { + throw new RuntimeException("QuitingState: processMessage called should not happen"); + } + } + /** * Handle messages sent to the state machine by calling * the current state's processMessage. It also handles @@ -586,7 +608,13 @@ public class HierarchicalStateMachine { * state. All subsequent messages will be processed in * in the halting state which invokes haltedProcessMessage(msg); */ - if (mDestState == mHaltingState) { + if (mDestState == mQuittingState) { + mHsm.quitting(); + if (mHsm.mHsmThread != null) { + // If we made the thread then quit looper + getLooper().quit(); + } + } else if (mDestState == mHaltingState) { mHsm.halting(); } mDestState = null; @@ -651,6 +679,9 @@ public class HierarchicalStateMachine { * No parents left so it's not handled */ mHsm.unhandledMessage(msg); + if (isQuit(msg)) { + transitionTo(mQuittingState); + } break; } if (mDbg) { @@ -851,6 +882,7 @@ public class HierarchicalStateMachine { mHsm = hsm; addState(mHaltingState, null); + addState(mQuittingState, null); } /** @see HierarchicalStateMachine#setInitialState(HierarchicalState) */ @@ -876,6 +908,17 @@ public class HierarchicalStateMachine { mDeferredMessages.add(newMsg); } + /** @see HierarchicalStateMachine#deferMessage(Message) */ + private final void quit() { + if (mDbg) Log.d(TAG, "quit:"); + sendMessage(obtainMessage(HSM_QUIT_CMD, mQuitObj)); + } + + /** @see HierarchicalStateMachine#isQuit(Message) */ + private final boolean isQuit(Message msg) { + return (msg.what == HSM_QUIT_CMD) && (msg.obj == mQuitObj); + } + /** @see HierarchicalStateMachine#isDbg() */ private final boolean isDbg() { return mDbg; @@ -1039,6 +1082,13 @@ public class HierarchicalStateMachine { protected void halting() { } + /** + * Called after the quitting message was NOT handled and + * just before the quit actually occurs. + */ + protected void quitting() { + } + /** * @return the name */ @@ -1138,6 +1188,25 @@ public class HierarchicalStateMachine { mHsmHandler.sendMessageAtFrontOfQueue(msg); } + /** + * Conditionally quit the looper and stop execution. + * + * This sends the HSM_QUIT_MSG to the state machine and + * if not handled by any state's processMessage then the + * state machine will be stopped and no further messages + * will be processed. + */ + public final void quit() { + mHsmHandler.quit(); + } + + /** + * @return ture if msg is quit + */ + protected final boolean isQuit(Message msg) { + return mHsmHandler.isQuit(msg); + } + /** * @return if debugging is enabled */ diff --git a/core/tests/coretests/src/android/os/HierarchicalStateMachineTest.java b/core/tests/coretests/src/android/os/HierarchicalStateMachineTest.java index 6e50c7e56117d..3f4bc04c555c0 100644 --- a/core/tests/coretests/src/android/os/HierarchicalStateMachineTest.java +++ b/core/tests/coretests/src/android/os/HierarchicalStateMachineTest.java @@ -51,6 +51,106 @@ public class HierarchicalStateMachineTest extends TestCase { private static final boolean WAIT_FOR_DEBUGGER = false; private static final String TAG = "HierarchicalStateMachineTest"; + /** + * Tests that we can quit the state machine. + */ + class StateMachineQuitTest extends HierarchicalStateMachine { + private int mQuitCount = 0; + + StateMachineQuitTest(String name) { + super(name); + mThisSm = this; + setDbg(DBG); + + // Setup state machine with 1 state + addState(mS1); + + // Set the initial state + setInitialState(mS1); + } + + class S1 extends HierarchicalState { + @Override protected boolean processMessage(Message message) { + if (isQuit(message)) { + mQuitCount += 1; + if (mQuitCount > 2) { + // Returning false to actually quit + return false; + } else { + // Do NOT quit + return true; + } + } else { + // All other message are handled + return true; + } + } + } + + @Override + protected void quitting() { + synchronized (mThisSm) { + mThisSm.notifyAll(); + } + } + + private StateMachineQuitTest mThisSm; + private S1 mS1 = new S1(); + } + + @SmallTest + public void testStateMachineQuitTest() throws Exception { + //if (WAIT_FOR_DEBUGGER) Debug.waitForDebugger(); + + StateMachineQuitTest smQuitTest = new StateMachineQuitTest("smQuitTest"); + smQuitTest.start(); + if (smQuitTest.isDbg()) Log.d(TAG, "testStateMachineQuitTest E"); + + synchronized (smQuitTest) { + // Send 6 messages + for (int i = 1; i <= 6; i++) { + smQuitTest.sendMessage(smQuitTest.obtainMessage(i)); + } + + // First two are ignored + smQuitTest.quit(); + smQuitTest.quit(); + + // Now we will quit + smQuitTest.quit(); + + try { + // wait for the messages to be handled + smQuitTest.wait(); + } catch (InterruptedException e) { + Log.e(TAG, "testStateMachineQuitTest: exception while waiting " + e.getMessage()); + } + } + + assertTrue(smQuitTest.getProcessedMessagesCount() == 9); + + ProcessedMessages.Info pmi; + + // The first two message didn't quit and were handled by mS1 + pmi = smQuitTest.getProcessedMessage(6); + assertEquals(HierarchicalStateMachine.HSM_QUIT_CMD, pmi.getWhat()); + assertEquals(smQuitTest.mS1, pmi.getState()); + assertEquals(smQuitTest.mS1, pmi.getOriginalState()); + + pmi = smQuitTest.getProcessedMessage(7); + assertEquals(HierarchicalStateMachine.HSM_QUIT_CMD, pmi.getWhat()); + assertEquals(smQuitTest.mS1, pmi.getState()); + assertEquals(smQuitTest.mS1, pmi.getOriginalState()); + + // The last message was never handled so the states are null + pmi = smQuitTest.getProcessedMessage(8); + assertEquals(HierarchicalStateMachine.HSM_QUIT_CMD, pmi.getWhat()); + assertEquals(null, pmi.getState()); + assertEquals(null, pmi.getOriginalState()); + + if (smQuitTest.isDbg()) Log.d(TAG, "testStateMachineQuitTest X"); + } + /** * Tests that ProcessedMessage works as a circular buffer. */ @@ -90,7 +190,7 @@ public class HierarchicalStateMachineTest extends TestCase { @SmallTest public void testStateMachine0() throws Exception { - if (WAIT_FOR_DEBUGGER) Debug.waitForDebugger(); + //if (WAIT_FOR_DEBUGGER) Debug.waitForDebugger(); StateMachine0 sm0 = new StateMachine0("sm0"); sm0.start();