Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 1b8b98b3 authored by Wink Saville's avatar Wink Saville
Browse files

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
parent bfb5d4b9
Loading
Loading
Loading
Loading
+72 −3
Original line number Diff line number Diff line
@@ -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.
 *
@@ -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
     */
+101 −1
Original line number Diff line number Diff line
@@ -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();