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

Commit 4ea0628a authored by Wink Saville's avatar Wink Saville Committed by Android (Google) Code Review
Browse files

Merge "Add quit and fix HSM EBNF."

parents 08e1f21d 1b8b98b3
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();