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();