Loading core/java/com/android/internal/util/HierarchicalStateMachine.java +72 −3 Original line number Diff line number Diff line Loading @@ -69,6 +69,10 @@ import java.util.HashMap; * and invoke <code>halting</code>. Any message subsequently received by the state * machine will cause <code>haltedProcessMessage</code> to be invoked. * * If it is desirable to completely stop the state machine call <code>quit</code>. 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 <code>processMessage</code> each <code>HierarchicalState</code> has * an <code>enter</code> method and <code>exit</exit> method which may be overridden. * Loading Loading @@ -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 ; Loading @@ -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(); Loading @@ -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; Loading Loading @@ -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 Loading Loading @@ -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; Loading Loading @@ -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) { Loading Loading @@ -851,6 +882,7 @@ public class HierarchicalStateMachine { mHsm = hsm; addState(mHaltingState, null); addState(mQuittingState, null); } /** @see HierarchicalStateMachine#setInitialState(HierarchicalState) */ Loading @@ -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; Loading Loading @@ -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 */ Loading Loading @@ -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 */ Loading core/tests/coretests/src/android/os/HierarchicalStateMachineTest.java +101 −1 Original line number Diff line number Diff line Loading @@ -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. */ Loading Loading @@ -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(); Loading Loading
core/java/com/android/internal/util/HierarchicalStateMachine.java +72 −3 Original line number Diff line number Diff line Loading @@ -69,6 +69,10 @@ import java.util.HashMap; * and invoke <code>halting</code>. Any message subsequently received by the state * machine will cause <code>haltedProcessMessage</code> to be invoked. * * If it is desirable to completely stop the state machine call <code>quit</code>. 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 <code>processMessage</code> each <code>HierarchicalState</code> has * an <code>enter</code> method and <code>exit</exit> method which may be overridden. * Loading Loading @@ -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 ; Loading @@ -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(); Loading @@ -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; Loading Loading @@ -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 Loading Loading @@ -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; Loading Loading @@ -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) { Loading Loading @@ -851,6 +882,7 @@ public class HierarchicalStateMachine { mHsm = hsm; addState(mHaltingState, null); addState(mQuittingState, null); } /** @see HierarchicalStateMachine#setInitialState(HierarchicalState) */ Loading @@ -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; Loading Loading @@ -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 */ Loading Loading @@ -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 */ Loading
core/tests/coretests/src/android/os/HierarchicalStateMachineTest.java +101 −1 Original line number Diff line number Diff line Loading @@ -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. */ Loading Loading @@ -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(); Loading