/**
 * Copyright (C) 2009 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package android.os;

import junit.framework.TestCase;

import android.os.Debug;
import android.os.HandlerThread;
import android.os.Looper;
import android.os.Message;
import android.os.SystemClock;
import android.test.suitebuilder.annotation.SmallTest;

import android.util.Log;

import com.android.internal.util.HierarchicalStateMachine;
import com.android.internal.util.HierarchicalState;
import com.android.internal.util.ProcessedMessages;

import java.util.ArrayList;
import java.util.Arrays;

/**
 * Test for HierarchicalStateMachine.
 *
 * @author wink@google.com (Wink Saville)
 */
public class HierarchicalStateMachineTest extends TestCase {
    private static final int TEST_CMD_1 = 1;
    private static final int TEST_CMD_2 = 2;
    private static final int TEST_CMD_3 = 3;
    private static final int TEST_CMD_4 = 4;
    private static final int TEST_CMD_5 = 5;
    private static final int TEST_CMD_6 = 6;

    private static final boolean DBG = true;
    private static final boolean WAIT_FOR_DEBUGGER = true;
    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(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");
    }

    /**
     * Test enter/exit can use transitionTo
     */
    class StateMachineEnterExitTransitionToTest extends HierarchicalStateMachine {
        StateMachineEnterExitTransitionToTest(String name) {
            super(name);
            mThisSm = this;
            setDbg(DBG);

            // Setup state machine with 1 state
            addState(mS1);
            addState(mS2);
            addState(mS3);
            addState(mS4);

            // Set the initial state
            setInitialState(mS1);
        }

        class S1 extends HierarchicalState {
            @Override protected void enter() {
                // Test that a transition in enter and the initial state works
                mS1EnterCount += 1;
                transitionTo(mS2);
                Log.d(TAG, "S1.enter");
            }
            @Override protected void exit() {
                mS1ExitCount += 1;
                Log.d(TAG, "S1.exit");
            }
        }

        class S2 extends HierarchicalState {
            @Override protected void enter() {
                mS2EnterCount += 1;
                Log.d(TAG, "S2.enter");
            }
            @Override protected void exit() {
                // Test transition in exit work
                mS2ExitCount += 1;
                transitionTo(mS4);
                Log.d(TAG, "S2.exit");
            }
            @Override protected boolean processMessage(Message message) {
                // Start a transition to S3 but it will be
                // changed to a transition to S4
                transitionTo(mS3);
                Log.d(TAG, "S2.processMessage");
                return true;
            }
        }

        class S3 extends HierarchicalState {
            @Override protected void enter() {
                // Test that we can do halting in an enter/exit
                transitionToHaltingState();
                mS3EnterCount += 1;
                Log.d(TAG, "S3.enter");
            }
            @Override protected void exit() {
                mS3ExitCount += 1;
                Log.d(TAG, "S3.exit");
            }
        }


        class S4 extends HierarchicalState {
            @Override protected void enter() {
                // Test that we can do halting in an enter/exit
                transitionToHaltingState();
                mS4EnterCount += 1;
                Log.d(TAG, "S4.enter");
            }
            @Override protected void exit() {
                mS4ExitCount += 1;
                Log.d(TAG, "S4.exit");
            }
        }

        @Override
        protected void halting() {
            synchronized (mThisSm) {
                mThisSm.notifyAll();
            }
        }

        private StateMachineEnterExitTransitionToTest mThisSm;
        private S1 mS1 = new S1();
        private S2 mS2 = new S2();
        private S3 mS3 = new S3();
        private S4 mS4 = new S4();
        private int mS1EnterCount = 0;
        private int mS1ExitCount = 0;
        private int mS2EnterCount = 0;
        private int mS2ExitCount = 0;
        private int mS3EnterCount = 0;
        private int mS3ExitCount = 0;
        private int mS4EnterCount = 0;
        private int mS4ExitCount = 0;
    }

    @SmallTest
    public void testStateMachineEnterExitTransitionToTest() throws Exception {
        //if (WAIT_FOR_DEBUGGER) Debug.waitForDebugger();

        StateMachineEnterExitTransitionToTest smEnterExitTranstionToTest =
            new StateMachineEnterExitTransitionToTest("smEnterExitTranstionToTest");
        smEnterExitTranstionToTest.start();
        if (smEnterExitTranstionToTest.isDbg()) {
            Log.d(TAG, "testStateMachineEnterExitTransitionToTest E");
        }

        synchronized (smEnterExitTranstionToTest) {
            smEnterExitTranstionToTest.sendMessage(1);

            try {
                // wait for the messages to be handled
                smEnterExitTranstionToTest.wait();
            } catch (InterruptedException e) {
                Log.e(TAG, "testStateMachineEnterExitTransitionToTest: exception while waiting "
                    + e.getMessage());
            }
        }

        assertTrue(smEnterExitTranstionToTest.getProcessedMessagesCount() == 1);

        ProcessedMessages.Info pmi;

        // Message should be handled by mS2.
        pmi = smEnterExitTranstionToTest.getProcessedMessage(0);
        assertEquals(TEST_CMD_1, pmi.getWhat());
        assertEquals(smEnterExitTranstionToTest.mS2, pmi.getState());
        assertEquals(smEnterExitTranstionToTest.mS2, pmi.getOriginalState());

        assertEquals(smEnterExitTranstionToTest.mS1EnterCount, 1);
        assertEquals(smEnterExitTranstionToTest.mS1ExitCount, 1);
        assertEquals(smEnterExitTranstionToTest.mS2EnterCount, 1);
        assertEquals(smEnterExitTranstionToTest.mS2ExitCount, 1);
        assertEquals(smEnterExitTranstionToTest.mS3EnterCount, 1);
        assertEquals(smEnterExitTranstionToTest.mS3ExitCount, 1);
        assertEquals(smEnterExitTranstionToTest.mS3EnterCount, 1);
        assertEquals(smEnterExitTranstionToTest.mS3ExitCount, 1);

        if (smEnterExitTranstionToTest.isDbg()) {
            Log.d(TAG, "testStateMachineEnterExitTransitionToTest X");
        }
    }

    /**
     * Tests that ProcessedMessage works as a circular buffer.
     */
    class StateMachine0 extends HierarchicalStateMachine {
        StateMachine0(String name) {
            super(name);
            mThisSm = this;
            setDbg(DBG);
            setProcessedMessagesSize(3);

            // 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 (message.what == TEST_CMD_6) {
                    transitionToHaltingState();
                }
                return true;
            }
        }

        @Override
        protected void halting() {
            synchronized (mThisSm) {
                mThisSm.notifyAll();
            }
        }

        private StateMachine0 mThisSm;
        private S1 mS1 = new S1();
    }

    @SmallTest
    public void testStateMachine0() throws Exception {
        //if (WAIT_FOR_DEBUGGER) Debug.waitForDebugger();

        StateMachine0 sm0 = new StateMachine0("sm0");
        sm0.start();
        if (sm0.isDbg()) Log.d(TAG, "testStateMachine0 E");

        synchronized (sm0) {
            // Send 6 messages
            for (int i = 1; i <= 6; i++) {
                sm0.sendMessage(sm0.obtainMessage(i));
            }

            try {
                // wait for the messages to be handled
                sm0.wait();
            } catch (InterruptedException e) {
                Log.e(TAG, "testStateMachine0: exception while waiting " + e.getMessage());
            }
        }

        assertTrue(sm0.getProcessedMessagesCount() == 6);
        assertTrue(sm0.getProcessedMessagesSize() == 3);

        ProcessedMessages.Info pmi;
        pmi = sm0.getProcessedMessage(0);
        assertEquals(TEST_CMD_4, pmi.getWhat());
        assertEquals(sm0.mS1, pmi.getState());
        assertEquals(sm0.mS1, pmi.getOriginalState());

        pmi = sm0.getProcessedMessage(1);
        assertEquals(TEST_CMD_5, pmi.getWhat());
        assertEquals(sm0.mS1, pmi.getState());
        assertEquals(sm0.mS1, pmi.getOriginalState());

        pmi = sm0.getProcessedMessage(2);
        assertEquals(TEST_CMD_6, pmi.getWhat());
        assertEquals(sm0.mS1, pmi.getState());
        assertEquals(sm0.mS1, pmi.getOriginalState());

        if (sm0.isDbg()) Log.d(TAG, "testStateMachine0 X");
    }

    /**
     * This tests enter/exit and transitions to the same state.
     * The state machine has one state, it receives two messages
     * in state mS1. With the first message it transitions to
     * itself which causes it to be exited and reentered.
     */
    class StateMachine1 extends HierarchicalStateMachine {
        StateMachine1(String name) {
            super(name);
            mThisSm = this;
            setDbg(DBG);

            // Setup state machine with 1 state
            addState(mS1);

            // Set the initial state
            setInitialState(mS1);
            if (DBG) Log.d(TAG, "StateMachine1: ctor X");
        }

        class S1 extends HierarchicalState {
            @Override protected void enter() {
                mEnterCount++;
            }

            @Override protected boolean processMessage(Message message) {
                if (message.what == TEST_CMD_1) {
                    assertEquals(1, mEnterCount);
                    assertEquals(0, mExitCount);
                    transitionTo(mS1);
                } else if (message.what == TEST_CMD_2) {
                    assertEquals(2, mEnterCount);
                    assertEquals(1, mExitCount);
                    transitionToHaltingState();
                }
                return true;
            }

            @Override protected void exit() {
                mExitCount++;
            }
        }

        @Override
        protected void halting() {
            synchronized (mThisSm) {
                mThisSm.notifyAll();
            }
        }

        private StateMachine1 mThisSm;
        private S1 mS1 = new S1();

        private int mEnterCount;
        private int mExitCount;
    }

    @SmallTest
    public void testStateMachine1() throws Exception {
        StateMachine1 sm1 = new StateMachine1("sm1");
        sm1.start();
        if (sm1.isDbg()) Log.d(TAG, "testStateMachine1 E");

        synchronized (sm1) {
            // Send two messages
            sm1.sendMessage(TEST_CMD_1);
            sm1.sendMessage(TEST_CMD_2);

            try {
                // wait for the messages to be handled
                sm1.wait();
            } catch (InterruptedException e) {
                Log.e(TAG, "testStateMachine1: exception while waiting " + e.getMessage());
            }
        }

        assertEquals(2, sm1.mEnterCount);
        assertEquals(2, sm1.mExitCount);

        assertTrue(sm1.getProcessedMessagesSize() == 2);

        ProcessedMessages.Info pmi;
        pmi = sm1.getProcessedMessage(0);
        assertEquals(TEST_CMD_1, pmi.getWhat());
        assertEquals(sm1.mS1, pmi.getState());
        assertEquals(sm1.mS1, pmi.getOriginalState());

        pmi = sm1.getProcessedMessage(1);
        assertEquals(TEST_CMD_2, pmi.getWhat());
        assertEquals(sm1.mS1, pmi.getState());
        assertEquals(sm1.mS1, pmi.getOriginalState());

        assertEquals(2, sm1.mEnterCount);
        assertEquals(2, sm1.mExitCount);

        if (sm1.isDbg()) Log.d(TAG, "testStateMachine1 X");
    }

    /**
     * Test deferring messages and states with no parents. The state machine
     * has two states, it receives two messages in state mS1 deferring them
     * until what == TEST_CMD_2 and then transitions to state mS2. State
     * mS2 then receives both of the deferred messages first TEST_CMD_1 and
     * then TEST_CMD_2.
     */
    class StateMachine2 extends HierarchicalStateMachine {
        StateMachine2(String name) {
            super(name);
            mThisSm = this;
            setDbg(DBG);

            // Setup the hierarchy
            addState(mS1);
            addState(mS2);

            // Set the initial state
            setInitialState(mS1);
            if (DBG) Log.d(TAG, "StateMachine2: ctor X");
        }

        class S1 extends HierarchicalState {
            @Override protected void enter() {
                mDidEnter = true;
            }

            @Override protected boolean processMessage(Message message) {
                deferMessage(message);
                if (message.what == TEST_CMD_2) {
                    transitionTo(mS2);
                }
                return true;
            }

            @Override protected void exit() {
                mDidExit = true;
            }
        }

        class S2 extends HierarchicalState {
            @Override protected boolean processMessage(Message message) {
                if (message.what == TEST_CMD_2) {
                    transitionToHaltingState();
                }
                return true;
            }
        }

        @Override
        protected void halting() {
            synchronized (mThisSm) {
                mThisSm.notifyAll();
            }
        }

        private StateMachine2 mThisSm;
        private S1 mS1 = new S1();
        private S2 mS2 = new S2();

        private boolean mDidEnter = false;
        private boolean mDidExit = false;
    }

    @SmallTest
    public void testStateMachine2() throws Exception {
        StateMachine2 sm2 = new StateMachine2("sm2");
        sm2.start();
        if (sm2.isDbg()) Log.d(TAG, "testStateMachine2 E");

        synchronized (sm2) {
            // Send two messages
            sm2.sendMessage(TEST_CMD_1);
            sm2.sendMessage(TEST_CMD_2);

            try {
                // wait for the messages to be handled
                sm2.wait();
            } catch (InterruptedException e) {
                Log.e(TAG, "testStateMachine2: exception while waiting " + e.getMessage());
            }
        }

        assertTrue(sm2.getProcessedMessagesSize() == 4);

        ProcessedMessages.Info pmi;
        pmi = sm2.getProcessedMessage(0);
        assertEquals(TEST_CMD_1, pmi.getWhat());
        assertEquals(sm2.mS1, pmi.getState());

        pmi = sm2.getProcessedMessage(1);
        assertEquals(TEST_CMD_2, pmi.getWhat());
        assertEquals(sm2.mS1, pmi.getState());

        pmi = sm2.getProcessedMessage(2);
        assertEquals(TEST_CMD_1, pmi.getWhat());
        assertEquals(sm2.mS2, pmi.getState());

        pmi = sm2.getProcessedMessage(3);
        assertEquals(TEST_CMD_2, pmi.getWhat());
        assertEquals(sm2.mS2, pmi.getState());

        assertTrue(sm2.mDidEnter);
        assertTrue(sm2.mDidExit);

        if (sm2.isDbg()) Log.d(TAG, "testStateMachine2 X");
    }

    /**
     * Test that unhandled messages in a child are handled by the parent.
     * When TEST_CMD_2 is received.
     */
    class StateMachine3 extends HierarchicalStateMachine {
        StateMachine3(String name) {
            super(name);
            mThisSm = this;
            setDbg(DBG);

            // Setup the simplest hierarchy of two states
            // mParentState and mChildState.
            // (Use indentation to help visualize hierarchy)
            addState(mParentState);
                addState(mChildState, mParentState);

            // Set the initial state will be the child
            setInitialState(mChildState);
            if (DBG) Log.d(TAG, "StateMachine3: ctor X");
        }

        class ParentState extends HierarchicalState {
            @Override protected boolean processMessage(Message message) {
                if (message.what == TEST_CMD_2) {
                    transitionToHaltingState();
                }
                return true;
            }
        }

        class ChildState extends HierarchicalState {
            @Override protected boolean processMessage(Message message) {
                return false;
            }
        }

        @Override
        protected void halting() {
            synchronized (mThisSm) {
                mThisSm.notifyAll();
            }
        }

        private StateMachine3 mThisSm;
        private ParentState mParentState = new ParentState();
        private ChildState mChildState = new ChildState();
    }

    @SmallTest
    public void testStateMachine3() throws Exception {
        StateMachine3 sm3 = new StateMachine3("sm3");
        sm3.start();
        if (sm3.isDbg()) Log.d(TAG, "testStateMachine3 E");

        synchronized (sm3) {
            // Send two messages
            sm3.sendMessage(TEST_CMD_1);
            sm3.sendMessage(TEST_CMD_2);

            try {
                // wait for the messages to be handled
                sm3.wait();
            } catch (InterruptedException e) {
                Log.e(TAG, "testStateMachine3: exception while waiting " + e.getMessage());
            }
        }

        assertTrue(sm3.getProcessedMessagesSize() == 2);

        ProcessedMessages.Info pmi;
        pmi = sm3.getProcessedMessage(0);
        assertEquals(TEST_CMD_1, pmi.getWhat());
        assertEquals(sm3.mParentState, pmi.getState());
        assertEquals(sm3.mChildState, pmi.getOriginalState());

        pmi = sm3.getProcessedMessage(1);
        assertEquals(TEST_CMD_2, pmi.getWhat());
        assertEquals(sm3.mParentState, pmi.getState());
        assertEquals(sm3.mChildState, pmi.getOriginalState());

        if (sm3.isDbg()) Log.d(TAG, "testStateMachine3 X");
    }

    /**
     * Test a hierarchy of 3 states a parent and two children
     * with transition from child 1 to child 2 and child 2
     * lets the parent handle the messages.
     */
    class StateMachine4 extends HierarchicalStateMachine {
        StateMachine4(String name) {
            super(name);
            mThisSm = this;
            setDbg(DBG);

            // Setup a hierarchy of three states
            // mParentState, mChildState1 & mChildState2
            // (Use indentation to help visualize hierarchy)
            addState(mParentState);
                addState(mChildState1, mParentState);
                addState(mChildState2, mParentState);

            // Set the initial state will be child 1
            setInitialState(mChildState1);
            if (DBG) Log.d(TAG, "StateMachine4: ctor X");
        }

        class ParentState extends HierarchicalState {
            @Override protected boolean processMessage(Message message) {
                if (message.what == TEST_CMD_2) {
                    transitionToHaltingState();
                }
                return true;
            }
        }

        class ChildState1 extends HierarchicalState {
            @Override protected boolean processMessage(Message message) {
                transitionTo(mChildState2);
                return true;
            }
        }

        class ChildState2 extends HierarchicalState {
            @Override protected boolean processMessage(Message message) {
                return false;
            }
        }

        @Override
        protected void halting() {
            synchronized (mThisSm) {
                mThisSm.notifyAll();
            }
        }

        private StateMachine4 mThisSm;
        private ParentState mParentState = new ParentState();
        private ChildState1 mChildState1 = new ChildState1();
        private ChildState2 mChildState2 = new ChildState2();
    }

    @SmallTest
    public void testStateMachine4() throws Exception {
        StateMachine4 sm4 = new StateMachine4("sm4");
        sm4.start();
        if (sm4.isDbg()) Log.d(TAG, "testStateMachine4 E");

        synchronized (sm4) {
            // Send two messages
            sm4.sendMessage(TEST_CMD_1);
            sm4.sendMessage(TEST_CMD_2);

            try {
                // wait for the messages to be handled
                sm4.wait();
            } catch (InterruptedException e) {
                Log.e(TAG, "testStateMachine4: exception while waiting " + e.getMessage());
            }
        }


        assertTrue(sm4.getProcessedMessagesSize() == 2);

        ProcessedMessages.Info pmi;
        pmi = sm4.getProcessedMessage(0);
        assertEquals(TEST_CMD_1, pmi.getWhat());
        assertEquals(sm4.mChildState1, pmi.getState());
        assertEquals(sm4.mChildState1, pmi.getOriginalState());

        pmi = sm4.getProcessedMessage(1);
        assertEquals(TEST_CMD_2, pmi.getWhat());
        assertEquals(sm4.mParentState, pmi.getState());
        assertEquals(sm4.mChildState2, pmi.getOriginalState());

        if (sm4.isDbg()) Log.d(TAG, "testStateMachine4 X");
    }

    /**
     * Test transition from one child to another of a "complex"
     * hierarchy with two parents and multiple children.
     */
    class StateMachine5 extends HierarchicalStateMachine {
        StateMachine5(String name) {
            super(name);
            mThisSm = this;
            setDbg(DBG);

            // Setup a hierarchy with two parents and some children.
            // (Use indentation to help visualize hierarchy)
            addState(mParentState1);
                addState(mChildState1, mParentState1);
                addState(mChildState2, mParentState1);

            addState(mParentState2);
                addState(mChildState3, mParentState2);
                addState(mChildState4, mParentState2);
                    addState(mChildState5, mChildState4);

            // Set the initial state will be the child
            setInitialState(mChildState1);
            if (DBG) Log.d(TAG, "StateMachine5: ctor X");
        }

        class ParentState1 extends HierarchicalState {
            @Override protected void enter() {
                mParentState1EnterCount += 1;
            }
            @Override protected boolean processMessage(Message message) {
                return true;
            }
            @Override protected void exit() {
                mParentState1ExitCount += 1;
            }
        }

        class ChildState1 extends HierarchicalState {
            @Override protected void enter() {
                mChildState1EnterCount += 1;
            }
            @Override protected boolean processMessage(Message message) {
                assertEquals(1, mParentState1EnterCount);
                assertEquals(0, mParentState1ExitCount);
                assertEquals(1, mChildState1EnterCount);
                assertEquals(0, mChildState1ExitCount);
                assertEquals(0, mChildState2EnterCount);
                assertEquals(0, mChildState2ExitCount);
                assertEquals(0, mParentState2EnterCount);
                assertEquals(0, mParentState2ExitCount);
                assertEquals(0, mChildState3EnterCount);
                assertEquals(0, mChildState3ExitCount);
                assertEquals(0, mChildState4EnterCount);
                assertEquals(0, mChildState4ExitCount);
                assertEquals(0, mChildState5EnterCount);
                assertEquals(0, mChildState5ExitCount);

                transitionTo(mChildState2);
                return true;
            }
            @Override protected void exit() {
                mChildState1ExitCount += 1;
            }
        }

        class ChildState2 extends HierarchicalState {
            @Override protected void enter() {
                mChildState2EnterCount += 1;
            }
            @Override protected boolean processMessage(Message message) {
                assertEquals(1, mParentState1EnterCount);
                assertEquals(0, mParentState1ExitCount);
                assertEquals(1, mChildState1EnterCount);
                assertEquals(1, mChildState1ExitCount);
                assertEquals(1, mChildState2EnterCount);
                assertEquals(0, mChildState2ExitCount);
                assertEquals(0, mParentState2EnterCount);
                assertEquals(0, mParentState2ExitCount);
                assertEquals(0, mChildState3EnterCount);
                assertEquals(0, mChildState3ExitCount);
                assertEquals(0, mChildState4EnterCount);
                assertEquals(0, mChildState4ExitCount);
                assertEquals(0, mChildState5EnterCount);
                assertEquals(0, mChildState5ExitCount);

                transitionTo(mChildState5);
                return true;
            }
            @Override protected void exit() {
                mChildState2ExitCount += 1;
            }
        }

        class ParentState2 extends HierarchicalState {
            @Override protected void enter() {
                mParentState2EnterCount += 1;
            }
            @Override protected boolean processMessage(Message message) {
                assertEquals(1, mParentState1EnterCount);
                assertEquals(1, mParentState1ExitCount);
                assertEquals(1, mChildState1EnterCount);
                assertEquals(1, mChildState1ExitCount);
                assertEquals(1, mChildState2EnterCount);
                assertEquals(1, mChildState2ExitCount);
                assertEquals(2, mParentState2EnterCount);
                assertEquals(1, mParentState2ExitCount);
                assertEquals(1, mChildState3EnterCount);
                assertEquals(1, mChildState3ExitCount);
                assertEquals(2, mChildState4EnterCount);
                assertEquals(2, mChildState4ExitCount);
                assertEquals(1, mChildState5EnterCount);
                assertEquals(1, mChildState5ExitCount);

                transitionToHaltingState();
                return true;
            }
            @Override protected void exit() {
                mParentState2ExitCount += 1;
            }
        }

        class ChildState3 extends HierarchicalState {
            @Override protected void enter() {
                mChildState3EnterCount += 1;
            }
            @Override protected boolean processMessage(Message message) {
                assertEquals(1, mParentState1EnterCount);
                assertEquals(1, mParentState1ExitCount);
                assertEquals(1, mChildState1EnterCount);
                assertEquals(1, mChildState1ExitCount);
                assertEquals(1, mChildState2EnterCount);
                assertEquals(1, mChildState2ExitCount);
                assertEquals(1, mParentState2EnterCount);
                assertEquals(0, mParentState2ExitCount);
                assertEquals(1, mChildState3EnterCount);
                assertEquals(0, mChildState3ExitCount);
                assertEquals(1, mChildState4EnterCount);
                assertEquals(1, mChildState4ExitCount);
                assertEquals(1, mChildState5EnterCount);
                assertEquals(1, mChildState5ExitCount);

                transitionTo(mChildState4);
                return true;
            }
            @Override protected void exit() {
                mChildState3ExitCount += 1;
            }
        }

        class ChildState4 extends HierarchicalState {
            @Override protected void enter() {
                mChildState4EnterCount += 1;
            }
            @Override protected boolean processMessage(Message message) {
                assertEquals(1, mParentState1EnterCount);
                assertEquals(1, mParentState1ExitCount);
                assertEquals(1, mChildState1EnterCount);
                assertEquals(1, mChildState1ExitCount);
                assertEquals(1, mChildState2EnterCount);
                assertEquals(1, mChildState2ExitCount);
                assertEquals(1, mParentState2EnterCount);
                assertEquals(0, mParentState2ExitCount);
                assertEquals(1, mChildState3EnterCount);
                assertEquals(1, mChildState3ExitCount);
                assertEquals(2, mChildState4EnterCount);
                assertEquals(1, mChildState4ExitCount);
                assertEquals(1, mChildState5EnterCount);
                assertEquals(1, mChildState5ExitCount);

                transitionTo(mParentState2);
                return true;
            }
            @Override protected void exit() {
                mChildState4ExitCount += 1;
            }
        }

        class ChildState5 extends HierarchicalState {
            @Override protected void enter() {
                mChildState5EnterCount += 1;
            }
            @Override protected boolean processMessage(Message message) {
                assertEquals(1, mParentState1EnterCount);
                assertEquals(1, mParentState1ExitCount);
                assertEquals(1, mChildState1EnterCount);
                assertEquals(1, mChildState1ExitCount);
                assertEquals(1, mChildState2EnterCount);
                assertEquals(1, mChildState2ExitCount);
                assertEquals(1, mParentState2EnterCount);
                assertEquals(0, mParentState2ExitCount);
                assertEquals(0, mChildState3EnterCount);
                assertEquals(0, mChildState3ExitCount);
                assertEquals(1, mChildState4EnterCount);
                assertEquals(0, mChildState4ExitCount);
                assertEquals(1, mChildState5EnterCount);
                assertEquals(0, mChildState5ExitCount);

                transitionTo(mChildState3);
                return true;
            }
            @Override protected void exit() {
                mChildState5ExitCount += 1;
            }
        }

        @Override
        protected void halting() {
            synchronized (mThisSm) {
                mThisSm.notifyAll();
            }
        }

        private StateMachine5 mThisSm;
        private ParentState1 mParentState1 = new ParentState1();
        private ChildState1 mChildState1 = new ChildState1();
        private ChildState2 mChildState2 = new ChildState2();
        private ParentState2 mParentState2 = new ParentState2();
        private ChildState3 mChildState3 = new ChildState3();
        private ChildState4 mChildState4 = new ChildState4();
        private ChildState5 mChildState5 = new ChildState5();

        private int mParentState1EnterCount = 0;
        private int mParentState1ExitCount = 0;
        private int mChildState1EnterCount = 0;
        private int mChildState1ExitCount = 0;
        private int mChildState2EnterCount = 0;
        private int mChildState2ExitCount = 0;
        private int mParentState2EnterCount = 0;
        private int mParentState2ExitCount = 0;
        private int mChildState3EnterCount = 0;
        private int mChildState3ExitCount = 0;
        private int mChildState4EnterCount = 0;
        private int mChildState4ExitCount = 0;
        private int mChildState5EnterCount = 0;
        private int mChildState5ExitCount = 0;
    }

    @SmallTest
    public void testStateMachine5() throws Exception {
        StateMachine5 sm5 = new StateMachine5("sm5");
        sm5.start();
        if (sm5.isDbg()) Log.d(TAG, "testStateMachine5 E");

        synchronized (sm5) {
            // Send 6 messages
            sm5.sendMessage(TEST_CMD_1);
            sm5.sendMessage(TEST_CMD_2);
            sm5.sendMessage(TEST_CMD_3);
            sm5.sendMessage(TEST_CMD_4);
            sm5.sendMessage(TEST_CMD_5);
            sm5.sendMessage(TEST_CMD_6);

            try {
                // wait for the messages to be handled
                sm5.wait();
            } catch (InterruptedException e) {
                Log.e(TAG, "testStateMachine5: exception while waiting " + e.getMessage());
            }
        }


        assertTrue(sm5.getProcessedMessagesSize() == 6);

        assertEquals(1, sm5.mParentState1EnterCount);
        assertEquals(1, sm5.mParentState1ExitCount);
        assertEquals(1, sm5.mChildState1EnterCount);
        assertEquals(1, sm5.mChildState1ExitCount);
        assertEquals(1, sm5.mChildState2EnterCount);
        assertEquals(1, sm5.mChildState2ExitCount);
        assertEquals(2, sm5.mParentState2EnterCount);
        assertEquals(2, sm5.mParentState2ExitCount);
        assertEquals(1, sm5.mChildState3EnterCount);
        assertEquals(1, sm5.mChildState3ExitCount);
        assertEquals(2, sm5.mChildState4EnterCount);
        assertEquals(2, sm5.mChildState4ExitCount);
        assertEquals(1, sm5.mChildState5EnterCount);
        assertEquals(1, sm5.mChildState5ExitCount);

        ProcessedMessages.Info pmi;
        pmi = sm5.getProcessedMessage(0);
        assertEquals(TEST_CMD_1, pmi.getWhat());
        assertEquals(sm5.mChildState1, pmi.getState());
        assertEquals(sm5.mChildState1, pmi.getOriginalState());

        pmi = sm5.getProcessedMessage(1);
        assertEquals(TEST_CMD_2, pmi.getWhat());
        assertEquals(sm5.mChildState2, pmi.getState());
        assertEquals(sm5.mChildState2, pmi.getOriginalState());

        pmi = sm5.getProcessedMessage(2);
        assertEquals(TEST_CMD_3, pmi.getWhat());
        assertEquals(sm5.mChildState5, pmi.getState());
        assertEquals(sm5.mChildState5, pmi.getOriginalState());

        pmi = sm5.getProcessedMessage(3);
        assertEquals(TEST_CMD_4, pmi.getWhat());
        assertEquals(sm5.mChildState3, pmi.getState());
        assertEquals(sm5.mChildState3, pmi.getOriginalState());

        pmi = sm5.getProcessedMessage(4);
        assertEquals(TEST_CMD_5, pmi.getWhat());
        assertEquals(sm5.mChildState4, pmi.getState());
        assertEquals(sm5.mChildState4, pmi.getOriginalState());

        pmi = sm5.getProcessedMessage(5);
        assertEquals(TEST_CMD_6, pmi.getWhat());
        assertEquals(sm5.mParentState2, pmi.getState());
        assertEquals(sm5.mParentState2, pmi.getOriginalState());

        if (sm5.isDbg()) Log.d(TAG, "testStateMachine5 X");
    }

    /**
     * Test that the initial state enter is invoked immediately
     * after construction and before any other messages arrive and that
     * sendMessageDelayed works.
     */
    class StateMachine6 extends HierarchicalStateMachine {
        StateMachine6(String name) {
            super(name);
            mThisSm = this;
            setDbg(DBG);

            // Setup state machine with 1 state
            addState(mS1);

            // Set the initial state
            setInitialState(mS1);
            if (DBG) Log.d(TAG, "StateMachine6: ctor X");
        }

        class S1 extends HierarchicalState {

            @Override protected void enter() {
                sendMessage(TEST_CMD_1);
            }

            @Override protected boolean processMessage(Message message) {
                if (message.what == TEST_CMD_1) {
                    mArrivalTimeMsg1 = SystemClock.elapsedRealtime();
                } else if (message.what == TEST_CMD_2) {
                    mArrivalTimeMsg2 = SystemClock.elapsedRealtime();
                    transitionToHaltingState();
                }
                return true;
            }

            @Override protected void exit() {
            }
        }

        @Override
        protected void halting() {
            synchronized (mThisSm) {
                mThisSm.notifyAll();
            }
        }

        private StateMachine6 mThisSm;
        private S1 mS1 = new S1();

        private long mArrivalTimeMsg1;
        private long mArrivalTimeMsg2;
    }

    @SmallTest
    public void testStateMachine6() throws Exception {
        long sentTimeMsg2;
        final int DELAY_TIME = 250;
        final int DELAY_FUDGE = 20;

        StateMachine6 sm6 = new StateMachine6("sm6");
        sm6.start();
        if (sm6.isDbg()) Log.d(TAG, "testStateMachine6 E");

        synchronized (sm6) {
            // Send a message
            sentTimeMsg2 = SystemClock.elapsedRealtime();
            sm6.sendMessageDelayed(TEST_CMD_2, DELAY_TIME);

            try {
                // wait for the messages to be handled
                sm6.wait();
            } catch (InterruptedException e) {
                Log.e(TAG, "testStateMachine6: exception while waiting " + e.getMessage());
            }
        }

        /**
         * TEST_CMD_1 was sent in enter and must always have been processed
         * immediately after construction and hence the arrival time difference
         * should always >= to the DELAY_TIME
         */
        long arrivalTimeDiff = sm6.mArrivalTimeMsg2 - sm6.mArrivalTimeMsg1;
        long expectedDelay = DELAY_TIME - DELAY_FUDGE;
        if (sm6.isDbg()) Log.d(TAG, "testStateMachine6: expect " + arrivalTimeDiff
                                    + " >= " + expectedDelay);
        assertTrue(arrivalTimeDiff >= expectedDelay);

        if (sm6.isDbg()) Log.d(TAG, "testStateMachine6 X");
    }

    /**
     * Test that enter is invoked immediately after exit. This validates
     * that enter can be used to send a watch dog message for its state.
     */
    class StateMachine7 extends HierarchicalStateMachine {
        private final int SM7_DELAY_TIME = 250;

        StateMachine7(String name) {
            super(name);
            mThisSm = this;
            setDbg(DBG);

            // Setup state machine with 1 state
            addState(mS1);
            addState(mS2);

            // Set the initial state
            setInitialState(mS1);
            if (DBG) Log.d(TAG, "StateMachine7: ctor X");
        }

        class S1 extends HierarchicalState {
            @Override protected boolean processMessage(Message message) {
                transitionTo(mS2);
                return true;
            }
            @Override protected void exit() {
                sendMessage(TEST_CMD_2);
            }
        }

        class S2 extends HierarchicalState {

            @Override protected void enter() {
                // Send a delayed message as a watch dog
                sendMessageDelayed(TEST_CMD_3, SM7_DELAY_TIME);
            }

            @Override protected boolean processMessage(Message message) {
                if (message.what == TEST_CMD_2) {
                    mMsgCount += 1;
                    mArrivalTimeMsg2 = SystemClock.elapsedRealtime();
                } else if (message.what == TEST_CMD_3) {
                    mMsgCount += 1;
                    mArrivalTimeMsg3 = SystemClock.elapsedRealtime();
                }

                if (mMsgCount == 2) {
                    transitionToHaltingState();
                }
                return true;
            }

            @Override protected void exit() {
            }
        }

        @Override
        protected void halting() {
            synchronized (mThisSm) {
                mThisSm.notifyAll();
            }
        }

        private StateMachine7 mThisSm;
        private S1 mS1 = new S1();
        private S2 mS2 = new S2();

        private int mMsgCount = 0;
        private long mArrivalTimeMsg2;
        private long mArrivalTimeMsg3;
    }

    @SmallTest
    public void testStateMachine7() throws Exception {
        long sentTimeMsg2;
        final int SM7_DELAY_FUDGE = 20;

        StateMachine7 sm7 = new StateMachine7("sm7");
        sm7.start();
        if (sm7.isDbg()) Log.d(TAG, "testStateMachine7 E");

        synchronized (sm7) {
            // Send a message
            sentTimeMsg2 = SystemClock.elapsedRealtime();
            sm7.sendMessage(TEST_CMD_1);

            try {
                // wait for the messages to be handled
                sm7.wait();
            } catch (InterruptedException e) {
                Log.e(TAG, "testStateMachine7: exception while waiting " + e.getMessage());
            }
        }

        /**
         * TEST_CMD_3 was sent in S2.enter with a delay and must always have been
         * processed immediately after S1.exit. Since S1.exit sent TEST_CMD_2
         * without a delay the arrival time difference should always >= to SM7_DELAY_TIME.
         */
        long arrivalTimeDiff = sm7.mArrivalTimeMsg3 - sm7.mArrivalTimeMsg2;
        long expectedDelay = sm7.SM7_DELAY_TIME - SM7_DELAY_FUDGE;
        if (sm7.isDbg()) Log.d(TAG, "testStateMachine7: expect " + arrivalTimeDiff
                                    + " >= " + expectedDelay);
        assertTrue(arrivalTimeDiff >= expectedDelay);

        if (sm7.isDbg()) Log.d(TAG, "testStateMachine7 X");
    }

    /**
     * Test unhandledMessage.
     */
    class StateMachineUnhandledMessage extends HierarchicalStateMachine {
        StateMachineUnhandledMessage(String name) {
            super(name);
            mThisSm = this;
            setDbg(DBG);

            // Setup state machine with 1 state
            addState(mS1);

            // Set the initial state
            setInitialState(mS1);
        }

        @Override protected void unhandledMessage(Message message) {
            mUnhandledMessageCount += 1;
        }

        class S1 extends HierarchicalState {
            @Override protected boolean processMessage(Message message) {
                if (message.what == TEST_CMD_2) {
                    transitionToHaltingState();
                }
                return false;
            }
        }

        @Override
        protected void halting() {
            synchronized (mThisSm) {
                mThisSm.notifyAll();
            }
        }

        private StateMachineUnhandledMessage mThisSm;
        private int mUnhandledMessageCount;
        private S1 mS1 = new S1();
    }

    @SmallTest
    public void testStateMachineUnhandledMessage() throws Exception {

        StateMachineUnhandledMessage sm = new StateMachineUnhandledMessage("sm");
        sm.start();
        if (sm.isDbg()) Log.d(TAG, "testStateMachineUnhandledMessage E");

        synchronized (sm) {
            // Send 2 messages
            for (int i = 1; i <= 2; i++) {
                sm.sendMessage(i);
            }

            try {
                // wait for the messages to be handled
                sm.wait();
            } catch (InterruptedException e) {
                Log.e(TAG, "testStateMachineUnhandledMessage: exception while waiting "
                        + e.getMessage());
            }
        }

        assertTrue(sm.getProcessedMessagesCount() == 2);
        assertEquals(2, sm.mUnhandledMessageCount);

        if (sm.isDbg()) Log.d(TAG, "testStateMachineUnhandledMessage X");
    }

    /**
     * Test state machines sharing the same thread/looper. Multiple instances
     * of the same state machine will be created. They will all share the
     * same thread and thus each can update <code>sharedCounter</code> which
     * will be used to notify testStateMachineSharedThread that the test is
     * complete.
     */
    class StateMachineSharedThread extends HierarchicalStateMachine {
        StateMachineSharedThread(String name, Looper looper, int maxCount) {
            super(name, looper);
            mMaxCount = maxCount;
            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 (message.what == TEST_CMD_4) {
                    transitionToHaltingState();
                }
                return true;
            }
        }

        @Override
        protected void halting() {
            // Update the shared counter, which is OK since all state
            // machines are using the same thread.
            sharedCounter += 1;
            if (sharedCounter == mMaxCount) {
                synchronized (waitObject) {
                    waitObject.notifyAll();
                }
            }
        }

        private int mMaxCount;
        private S1 mS1 = new S1();
    }
    private static int sharedCounter = 0;
    private static Object waitObject = new Object();

    @SmallTest
    public void testStateMachineSharedThread() throws Exception {
        if (DBG) Log.d(TAG, "testStateMachineSharedThread E");

        // Create and start the handler thread
        HandlerThread smThread = new HandlerThread("testStateMachineSharedThread");
        smThread.start();

        // Create the state machines
        StateMachineSharedThread sms[] = new StateMachineSharedThread[10];
        for (int i = 0; i < sms.length; i++) {
            sms[i] = new StateMachineSharedThread("sm", smThread.getLooper(), sms.length);
            sms[i].start();
        }

        synchronized (waitObject) {
            // Send messages to each of the state machines
            for (StateMachineSharedThread sm : sms) {
                for (int i = 1; i <= 4; i++) {
                    sm.sendMessage(i);
                }
            }

            // Wait for the last state machine to notify its done
            try {
                waitObject.wait();
            } catch (InterruptedException e) {
                Log.e(TAG, "testStateMachineSharedThread: exception while waiting "
                        + e.getMessage());
            }
        }

        for (StateMachineSharedThread sm : sms) {
            assertTrue(sm.getProcessedMessagesCount() == 4);
            for (int i = 0; i < sm.getProcessedMessagesCount(); i++) {
                ProcessedMessages.Info pmi = sm.getProcessedMessage(i);
                assertEquals(i+1, pmi.getWhat());
                assertEquals(sm.mS1, pmi.getState());
                assertEquals(sm.mS1, pmi.getOriginalState());
            }
        }

        if (DBG) Log.d(TAG, "testStateMachineSharedThread X");
    }

    @SmallTest
    public void testHsm1() throws Exception {
        if (DBG) Log.d(TAG, "testHsm1 E");

        Hsm1 sm = Hsm1.makeHsm1();

        // Send messages
        sm.sendMessage(Hsm1.CMD_1);
        sm.sendMessage(Hsm1.CMD_2);

        synchronized (sm) {
            // Wait for the last state machine to notify its done
            try {
                sm.wait();
            } catch (InterruptedException e) {
                Log.e(TAG, "testHsm1: exception while waiting " + e.getMessage());
            }
        }

        assertEquals(7, sm.getProcessedMessagesCount());
        ProcessedMessages.Info pmi = sm.getProcessedMessage(0);
        assertEquals(Hsm1.CMD_1, pmi.getWhat());
        assertEquals(sm.mS1, pmi.getState());
        assertEquals(sm.mS1, pmi.getOriginalState());

        pmi = sm.getProcessedMessage(1);
        assertEquals(Hsm1.CMD_2, pmi.getWhat());
        assertEquals(sm.mP1, pmi.getState());
        assertEquals(sm.mS1, pmi.getOriginalState());

        pmi = sm.getProcessedMessage(2);
        assertEquals(Hsm1.CMD_2, pmi.getWhat());
        assertEquals(sm.mS2, pmi.getState());
        assertEquals(sm.mS2, pmi.getOriginalState());

        pmi = sm.getProcessedMessage(3);
        assertEquals(Hsm1.CMD_3, pmi.getWhat());
        assertEquals(sm.mS2, pmi.getState());
        assertEquals(sm.mS2, pmi.getOriginalState());

        pmi = sm.getProcessedMessage(4);
        assertEquals(Hsm1.CMD_3, pmi.getWhat());
        assertEquals(sm.mP2, pmi.getState());
        assertEquals(sm.mP2, pmi.getOriginalState());

        pmi = sm.getProcessedMessage(5);
        assertEquals(Hsm1.CMD_4, pmi.getWhat());
        assertEquals(sm.mP2, pmi.getState());
        assertEquals(sm.mP2, pmi.getOriginalState());

        pmi = sm.getProcessedMessage(6);
        assertEquals(Hsm1.CMD_5, pmi.getWhat());
        assertEquals(sm.mP2, pmi.getState());
        assertEquals(sm.mP2, pmi.getOriginalState());

        if (DBG) Log.d(TAG, "testStateMachineSharedThread X");
    }
}

class Hsm1 extends HierarchicalStateMachine {
    private static final String TAG = "hsm1";

    public static final int CMD_1 = 1;
    public static final int CMD_2 = 2;
    public static final int CMD_3 = 3;
    public static final int CMD_4 = 4;
    public static final int CMD_5 = 5;

    public static Hsm1 makeHsm1() {
        Log.d(TAG, "makeHsm1 E");
        Hsm1 sm = new Hsm1("hsm1");
        sm.start();
        Log.d(TAG, "makeHsm1 X");
        return sm;
    }

    Hsm1(String name) {
        super(name);
        Log.d(TAG, "ctor E");

        // Add states, use indentation to show hierarchy
        addState(mP1);
            addState(mS1, mP1);
            addState(mS2, mP1);
        addState(mP2);

        // Set the initial state
        setInitialState(mS1);
        Log.d(TAG, "ctor X");
    }

    class P1 extends HierarchicalState {
        @Override protected void enter() {
            Log.d(TAG, "P1.enter");
        }
        @Override protected boolean processMessage(Message message) {
            boolean retVal;
            Log.d(TAG, "P1.processMessage what=" + message.what);
            switch(message.what) {
            case CMD_2:
                // CMD_2 will arrive in mS2 before CMD_3
                sendMessage(CMD_3);
                deferMessage(message);
                transitionTo(mS2);
                retVal = true;
                break;
            default:
                // Any message we don't understand in this state invokes unhandledMessage
                retVal = false;
                break;
            }
            return retVal;
        }
        @Override protected void exit() {
            Log.d(TAG, "P1.exit");
        }
    }

    class S1 extends HierarchicalState {
        @Override protected void enter() {
            Log.d(TAG, "S1.enter");
        }
        @Override protected boolean processMessage(Message message) {
            Log.d(TAG, "S1.processMessage what=" + message.what);
            if (message.what == CMD_1) {
                // Transition to ourself to show that enter/exit is called
                transitionTo(mS1);
                return true;
            } else {
                // Let parent process all other messages
                return false;
            }
        }
        @Override protected void exit() {
            Log.d(TAG, "S1.exit");
        }
    }

    class S2 extends HierarchicalState {
        @Override protected void enter() {
            Log.d(TAG, "S2.enter");
        }
        @Override protected boolean processMessage(Message message) {
            boolean retVal;
            Log.d(TAG, "S2.processMessage what=" + message.what);
            switch(message.what) {
            case(CMD_2):
                sendMessage(CMD_4);
                retVal = true;
                break;
            case(CMD_3):
                deferMessage(message);
                transitionTo(mP2);
                retVal = true;
                break;
            default:
                retVal = false;
                break;
            }
            return retVal;
        }
        @Override protected void exit() {
            Log.d(TAG, "S2.exit");
        }
    }

    class P2 extends HierarchicalState {
        @Override protected void enter() {
            Log.d(TAG, "P2.enter");
            sendMessage(CMD_5);
        }
        @Override protected boolean processMessage(Message message) {
            Log.d(TAG, "P2.processMessage what=" + message.what);
            switch(message.what) {
            case(CMD_3):
                break;
            case(CMD_4):
                break;
            case(CMD_5):
                transitionToHaltingState();
                break;
            }
            return true;
        }
        @Override protected void exit() {
            Log.d(TAG, "P2.exit");
        }
    }

    @Override
    protected void halting() {
        Log.d(TAG, "halting");
        synchronized (this) {
            this.notifyAll();
        }
    }

    P1 mP1 = new P1();
    S1 mS1 = new S1();
    S2 mS2 = new S2();
    P2 mP2 = new P2();
}
