summaryrefslogtreecommitdiffstats
path: root/core/tests/coretests/src/com/android/internal/util/StateMachineTest.java
diff options
context:
space:
mode:
authorWink Saville <wink@google.com>2011-04-18 15:28:47 -0700
committerAndroid Git Automerger <android-git-automerger@android.com>2011-04-18 15:28:47 -0700
commit343c1ad7200619230a55caa6aa8a9d041e62b29d (patch)
tree42ef60951bc803ec683ee61dfa3e29965fb6a32b /core/tests/coretests/src/com/android/internal/util/StateMachineTest.java
parent5664eea800ffc40cd1ee93bd228435aa41e7a64c (diff)
parent37677849998e6c9a39afe0a730b2f0131a371f3b (diff)
downloadframeworks_base-343c1ad7200619230a55caa6aa8a9d041e62b29d.zip
frameworks_base-343c1ad7200619230a55caa6aa8a9d041e62b29d.tar.gz
frameworks_base-343c1ad7200619230a55caa6aa8a9d041e62b29d.tar.bz2
am 37677849: am 64c42cae: Rename HierarchicalStateMachine and HierarchicalState to StateMachine and State.
* commit '37677849998e6c9a39afe0a730b2f0131a371f3b': Rename HierarchicalStateMachine and HierarchicalState to StateMachine and State.
Diffstat (limited to 'core/tests/coretests/src/com/android/internal/util/StateMachineTest.java')
-rw-r--r--core/tests/coretests/src/com/android/internal/util/StateMachineTest.java1698
1 files changed, 1698 insertions, 0 deletions
diff --git a/core/tests/coretests/src/com/android/internal/util/StateMachineTest.java b/core/tests/coretests/src/com/android/internal/util/StateMachineTest.java
new file mode 100644
index 0000000..ab6b2b6
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/util/StateMachineTest.java
@@ -0,0 +1,1698 @@
+/**
+ * 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 com.android.internal.util;
+
+import android.os.Debug;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.Message;
+import android.os.SystemClock;
+
+import com.android.internal.util.State;
+import com.android.internal.util.StateMachine;
+import com.android.internal.util.StateMachine.ProcessedMessageInfo;
+
+import android.test.suitebuilder.annotation.MediumTest;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.util.Log;
+
+import junit.framework.TestCase;
+
+/**
+ * Test for StateMachine.
+ */
+public class StateMachineTest 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 = false;
+ private static final String TAG = "StateMachineTest";
+
+ /**
+ * Tests that we can quit the state machine.
+ */
+ class StateMachineQuitTest extends StateMachine {
+ 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 State {
+ @Override
+ public boolean processMessage(Message message) {
+ if (isQuit(message)) {
+ mQuitCount += 1;
+ if (mQuitCount > 2) {
+ // Returning NOT_HANDLED to actually quit
+ return NOT_HANDLED;
+ } else {
+ // Do NOT quit
+ return HANDLED;
+ }
+ } else {
+ // All other message are handled
+ return HANDLED;
+ }
+ }
+ }
+
+ @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);
+
+ ProcessedMessageInfo pmi;
+
+ // The first two message didn't quit and were handled by mS1
+ pmi = smQuitTest.getProcessedMessageInfo(6);
+ assertEquals(StateMachine.SM_QUIT_CMD, pmi.getWhat());
+ assertEquals(smQuitTest.mS1, pmi.getState());
+ assertEquals(smQuitTest.mS1, pmi.getOriginalState());
+
+ pmi = smQuitTest.getProcessedMessageInfo(7);
+ assertEquals(StateMachine.SM_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.getProcessedMessageInfo(8);
+ assertEquals(StateMachine.SM_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 StateMachine {
+ 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 State {
+ @Override
+ public void enter() {
+ // Test that message is HSM_INIT_CMD
+ assertEquals(SM_INIT_CMD, getCurrentMessage().what);
+
+ // Test that a transition in enter and the initial state works
+ mS1EnterCount += 1;
+ transitionTo(mS2);
+ Log.d(TAG, "S1.enter");
+ }
+ @Override
+ public void exit() {
+ // Test that message is HSM_INIT_CMD
+ assertEquals(SM_INIT_CMD, getCurrentMessage().what);
+
+ mS1ExitCount += 1;
+ Log.d(TAG, "S1.exit");
+ }
+ }
+
+ class S2 extends State {
+ @Override
+ public void enter() {
+ // Test that message is HSM_INIT_CMD
+ assertEquals(SM_INIT_CMD, getCurrentMessage().what);
+
+ mS2EnterCount += 1;
+ Log.d(TAG, "S2.enter");
+ }
+ @Override
+ public void exit() {
+ // Test that message is TEST_CMD_1
+ assertEquals(TEST_CMD_1, getCurrentMessage().what);
+
+ // Test transition in exit work
+ mS2ExitCount += 1;
+ transitionTo(mS4);
+ Log.d(TAG, "S2.exit");
+ }
+ @Override
+ public boolean processMessage(Message message) {
+ // Start a transition to S3 but it will be
+ // changed to a transition to S4 in exit
+ transitionTo(mS3);
+ Log.d(TAG, "S2.processMessage");
+ return HANDLED;
+ }
+ }
+
+ class S3 extends State {
+ @Override
+ public void enter() {
+ // Test that we can do halting in an enter/exit
+ transitionToHaltingState();
+ mS3EnterCount += 1;
+ Log.d(TAG, "S3.enter");
+ }
+ @Override
+ public void exit() {
+ mS3ExitCount += 1;
+ Log.d(TAG, "S3.exit");
+ }
+ }
+
+
+ class S4 extends State {
+ @Override
+ public void enter() {
+ // Test that we can do halting in an enter/exit
+ transitionToHaltingState();
+ mS4EnterCount += 1;
+ Log.d(TAG, "S4.enter");
+ }
+ @Override
+ public 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(TEST_CMD_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);
+
+ ProcessedMessageInfo pmi;
+
+ // Message should be handled by mS2.
+ pmi = smEnterExitTranstionToTest.getProcessedMessageInfo(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 StateMachine {
+ 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 State {
+ @Override
+ public boolean processMessage(Message message) {
+ if (message.what == TEST_CMD_6) {
+ transitionToHaltingState();
+ }
+ return HANDLED;
+ }
+ }
+
+ @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);
+
+ ProcessedMessageInfo pmi;
+ pmi = sm0.getProcessedMessageInfo(0);
+ assertEquals(TEST_CMD_4, pmi.getWhat());
+ assertEquals(sm0.mS1, pmi.getState());
+ assertEquals(sm0.mS1, pmi.getOriginalState());
+
+ pmi = sm0.getProcessedMessageInfo(1);
+ assertEquals(TEST_CMD_5, pmi.getWhat());
+ assertEquals(sm0.mS1, pmi.getState());
+ assertEquals(sm0.mS1, pmi.getOriginalState());
+
+ pmi = sm0.getProcessedMessageInfo(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 StateMachine {
+ 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 State {
+ @Override
+ public void enter() {
+ mEnterCount++;
+ }
+ @Override
+ public void exit() {
+ mExitCount++;
+ }
+ @Override
+ public 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 HANDLED;
+ }
+ }
+
+ @Override
+ protected void halting() {
+ synchronized (mThisSm) {
+ mThisSm.notifyAll();
+ }
+ }
+
+ private StateMachine1 mThisSm;
+ private S1 mS1 = new S1();
+
+ private int mEnterCount;
+ private int mExitCount;
+ }
+
+ @MediumTest
+ 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);
+
+ ProcessedMessageInfo pmi;
+ pmi = sm1.getProcessedMessageInfo(0);
+ assertEquals(TEST_CMD_1, pmi.getWhat());
+ assertEquals(sm1.mS1, pmi.getState());
+ assertEquals(sm1.mS1, pmi.getOriginalState());
+
+ pmi = sm1.getProcessedMessageInfo(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 StateMachine {
+ 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 State {
+ @Override
+ public void enter() {
+ mDidEnter = true;
+ }
+ @Override
+ public void exit() {
+ mDidExit = true;
+ }
+ @Override
+ public boolean processMessage(Message message) {
+ deferMessage(message);
+ if (message.what == TEST_CMD_2) {
+ transitionTo(mS2);
+ }
+ return HANDLED;
+ }
+ }
+
+ class S2 extends State {
+ @Override
+ public boolean processMessage(Message message) {
+ if (message.what == TEST_CMD_2) {
+ transitionToHaltingState();
+ }
+ return HANDLED;
+ }
+ }
+
+ @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;
+ }
+
+ @MediumTest
+ 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);
+
+ ProcessedMessageInfo pmi;
+ pmi = sm2.getProcessedMessageInfo(0);
+ assertEquals(TEST_CMD_1, pmi.getWhat());
+ assertEquals(sm2.mS1, pmi.getState());
+
+ pmi = sm2.getProcessedMessageInfo(1);
+ assertEquals(TEST_CMD_2, pmi.getWhat());
+ assertEquals(sm2.mS1, pmi.getState());
+
+ pmi = sm2.getProcessedMessageInfo(2);
+ assertEquals(TEST_CMD_1, pmi.getWhat());
+ assertEquals(sm2.mS2, pmi.getState());
+
+ pmi = sm2.getProcessedMessageInfo(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 StateMachine {
+ 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 State {
+ @Override
+ public boolean processMessage(Message message) {
+ if (message.what == TEST_CMD_2) {
+ transitionToHaltingState();
+ }
+ return HANDLED;
+ }
+ }
+
+ class ChildState extends State {
+ @Override
+ public boolean processMessage(Message message) {
+ return NOT_HANDLED;
+ }
+ }
+
+ @Override
+ protected void halting() {
+ synchronized (mThisSm) {
+ mThisSm.notifyAll();
+ }
+ }
+
+ private StateMachine3 mThisSm;
+ private ParentState mParentState = new ParentState();
+ private ChildState mChildState = new ChildState();
+ }
+
+ @MediumTest
+ 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);
+
+ ProcessedMessageInfo pmi;
+ pmi = sm3.getProcessedMessageInfo(0);
+ assertEquals(TEST_CMD_1, pmi.getWhat());
+ assertEquals(sm3.mParentState, pmi.getState());
+ assertEquals(sm3.mChildState, pmi.getOriginalState());
+
+ pmi = sm3.getProcessedMessageInfo(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 StateMachine {
+ 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 State {
+ @Override
+ public boolean processMessage(Message message) {
+ if (message.what == TEST_CMD_2) {
+ transitionToHaltingState();
+ }
+ return HANDLED;
+ }
+ }
+
+ class ChildState1 extends State {
+ @Override
+ public boolean processMessage(Message message) {
+ transitionTo(mChildState2);
+ return HANDLED;
+ }
+ }
+
+ class ChildState2 extends State {
+ @Override
+ public boolean processMessage(Message message) {
+ return NOT_HANDLED;
+ }
+ }
+
+ @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();
+ }
+
+ @MediumTest
+ 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);
+
+ ProcessedMessageInfo pmi;
+ pmi = sm4.getProcessedMessageInfo(0);
+ assertEquals(TEST_CMD_1, pmi.getWhat());
+ assertEquals(sm4.mChildState1, pmi.getState());
+ assertEquals(sm4.mChildState1, pmi.getOriginalState());
+
+ pmi = sm4.getProcessedMessageInfo(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 StateMachine {
+ 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 State {
+ @Override
+ public void enter() {
+ mParentState1EnterCount += 1;
+ }
+ @Override
+ public void exit() {
+ mParentState1ExitCount += 1;
+ }
+ @Override
+ public boolean processMessage(Message message) {
+ return HANDLED;
+ }
+ }
+
+ class ChildState1 extends State {
+ @Override
+ public void enter() {
+ mChildState1EnterCount += 1;
+ }
+ @Override
+ public void exit() {
+ mChildState1ExitCount += 1;
+ }
+ @Override
+ public 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 HANDLED;
+ }
+ }
+
+ class ChildState2 extends State {
+ @Override
+ public void enter() {
+ mChildState2EnterCount += 1;
+ }
+ @Override
+ public void exit() {
+ mChildState2ExitCount += 1;
+ }
+ @Override
+ public 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 HANDLED;
+ }
+ }
+
+ class ParentState2 extends State {
+ @Override
+ public void enter() {
+ mParentState2EnterCount += 1;
+ }
+ @Override
+ public void exit() {
+ mParentState2ExitCount += 1;
+ }
+ @Override
+ public 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 HANDLED;
+ }
+ }
+
+ class ChildState3 extends State {
+ @Override
+ public void enter() {
+ mChildState3EnterCount += 1;
+ }
+ @Override
+ public void exit() {
+ mChildState3ExitCount += 1;
+ }
+ @Override
+ public 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 HANDLED;
+ }
+ }
+
+ class ChildState4 extends State {
+ @Override
+ public void enter() {
+ mChildState4EnterCount += 1;
+ }
+ @Override
+ public void exit() {
+ mChildState4ExitCount += 1;
+ }
+ @Override
+ public 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 HANDLED;
+ }
+ }
+
+ class ChildState5 extends State {
+ @Override
+ public void enter() {
+ mChildState5EnterCount += 1;
+ }
+ @Override
+ public void exit() {
+ mChildState5ExitCount += 1;
+ }
+ @Override
+ public 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 HANDLED;
+ }
+ }
+
+ @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;
+ }
+
+ @MediumTest
+ 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);
+
+ ProcessedMessageInfo pmi;
+ pmi = sm5.getProcessedMessageInfo(0);
+ assertEquals(TEST_CMD_1, pmi.getWhat());
+ assertEquals(sm5.mChildState1, pmi.getState());
+ assertEquals(sm5.mChildState1, pmi.getOriginalState());
+
+ pmi = sm5.getProcessedMessageInfo(1);
+ assertEquals(TEST_CMD_2, pmi.getWhat());
+ assertEquals(sm5.mChildState2, pmi.getState());
+ assertEquals(sm5.mChildState2, pmi.getOriginalState());
+
+ pmi = sm5.getProcessedMessageInfo(2);
+ assertEquals(TEST_CMD_3, pmi.getWhat());
+ assertEquals(sm5.mChildState5, pmi.getState());
+ assertEquals(sm5.mChildState5, pmi.getOriginalState());
+
+ pmi = sm5.getProcessedMessageInfo(3);
+ assertEquals(TEST_CMD_4, pmi.getWhat());
+ assertEquals(sm5.mChildState3, pmi.getState());
+ assertEquals(sm5.mChildState3, pmi.getOriginalState());
+
+ pmi = sm5.getProcessedMessageInfo(4);
+ assertEquals(TEST_CMD_5, pmi.getWhat());
+ assertEquals(sm5.mChildState4, pmi.getState());
+ assertEquals(sm5.mChildState4, pmi.getOriginalState());
+
+ pmi = sm5.getProcessedMessageInfo(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 StateMachine {
+ 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 State {
+ @Override
+ public void enter() {
+ sendMessage(TEST_CMD_1);
+ }
+ @Override
+ public 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 HANDLED;
+ }
+ }
+
+ @Override
+ protected void halting() {
+ synchronized (mThisSm) {
+ mThisSm.notifyAll();
+ }
+ }
+
+ private StateMachine6 mThisSm;
+ private S1 mS1 = new S1();
+
+ private long mArrivalTimeMsg1;
+ private long mArrivalTimeMsg2;
+ }
+
+ @MediumTest
+ 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 StateMachine {
+ 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 State {
+ @Override
+ public void exit() {
+ sendMessage(TEST_CMD_2);
+ }
+ @Override
+ public boolean processMessage(Message message) {
+ transitionTo(mS2);
+ return HANDLED;
+ }
+ }
+
+ class S2 extends State {
+ @Override
+ public void enter() {
+ // Send a delayed message as a watch dog
+ sendMessageDelayed(TEST_CMD_3, SM7_DELAY_TIME);
+ }
+ @Override
+ public 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 HANDLED;
+ }
+ }
+
+ @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;
+ }
+
+ @MediumTest
+ 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 StateMachine {
+ StateMachineUnhandledMessage(String name) {
+ super(name);
+ mThisSm = this;
+ setDbg(DBG);
+
+ // Setup state machine with 1 state
+ addState(mS1);
+
+ // Set the initial state
+ setInitialState(mS1);
+ }
+ @Override
+ public void unhandledMessage(Message message) {
+ mUnhandledMessageCount += 1;
+ }
+
+ class S1 extends State {
+ @Override
+ public boolean processMessage(Message message) {
+ if (message.what == TEST_CMD_2) {
+ transitionToHaltingState();
+ }
+ return NOT_HANDLED;
+ }
+ }
+
+ @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 StateMachine {
+ 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 State {
+ @Override
+ public boolean processMessage(Message message) {
+ if (message.what == TEST_CMD_4) {
+ transitionToHaltingState();
+ }
+ return HANDLED;
+ }
+ }
+
+ @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();
+
+ @MediumTest
+ 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++) {
+ ProcessedMessageInfo pmi = sm.getProcessedMessageInfo(i);
+ assertEquals(i+1, pmi.getWhat());
+ assertEquals(sm.mS1, pmi.getState());
+ assertEquals(sm.mS1, pmi.getOriginalState());
+ }
+ }
+
+ if (DBG) Log.d(TAG, "testStateMachineSharedThread X");
+ }
+
+ @MediumTest
+ 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());
+ ProcessedMessageInfo pmi = sm.getProcessedMessageInfo(0);
+ assertEquals(Hsm1.CMD_1, pmi.getWhat());
+ assertEquals(sm.mS1, pmi.getState());
+ assertEquals(sm.mS1, pmi.getOriginalState());
+
+ pmi = sm.getProcessedMessageInfo(1);
+ assertEquals(Hsm1.CMD_2, pmi.getWhat());
+ assertEquals(sm.mP1, pmi.getState());
+ assertEquals(sm.mS1, pmi.getOriginalState());
+
+ pmi = sm.getProcessedMessageInfo(2);
+ assertEquals(Hsm1.CMD_2, pmi.getWhat());
+ assertEquals(sm.mS2, pmi.getState());
+ assertEquals(sm.mS2, pmi.getOriginalState());
+
+ pmi = sm.getProcessedMessageInfo(3);
+ assertEquals(Hsm1.CMD_3, pmi.getWhat());
+ assertEquals(sm.mS2, pmi.getState());
+ assertEquals(sm.mS2, pmi.getOriginalState());
+
+ pmi = sm.getProcessedMessageInfo(4);
+ assertEquals(Hsm1.CMD_3, pmi.getWhat());
+ assertEquals(sm.mP2, pmi.getState());
+ assertEquals(sm.mP2, pmi.getOriginalState());
+
+ pmi = sm.getProcessedMessageInfo(5);
+ assertEquals(Hsm1.CMD_4, pmi.getWhat());
+ assertEquals(sm.mP2, pmi.getState());
+ assertEquals(sm.mP2, pmi.getOriginalState());
+
+ pmi = sm.getProcessedMessageInfo(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 StateMachine {
+ 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 State {
+ @Override
+ public void enter() {
+ Log.d(TAG, "P1.enter");
+ }
+ @Override
+ public void exit() {
+ Log.d(TAG, "P1.exit");
+ }
+ @Override
+ public 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;
+ }
+ }
+
+ class S1 extends State {
+ @Override
+ public void enter() {
+ Log.d(TAG, "S1.enter");
+ }
+ @Override
+ public void exit() {
+ Log.d(TAG, "S1.exit");
+ }
+ @Override
+ public 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 HANDLED;
+ } else {
+ // Let parent process all other messages
+ return NOT_HANDLED;
+ }
+ }
+ }
+
+ class S2 extends State {
+ @Override
+ public void enter() {
+ Log.d(TAG, "S2.enter");
+ }
+ @Override
+ public void exit() {
+ Log.d(TAG, "S2.exit");
+ }
+ @Override
+ public 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;
+ }
+ }
+
+ class P2 extends State {
+ @Override
+ public void enter() {
+ Log.d(TAG, "P2.enter");
+ sendMessage(CMD_5);
+ }
+ @Override
+ public void exit() {
+ Log.d(TAG, "P2.exit");
+ }
+ @Override
+ public 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 HANDLED;
+ }
+ }
+
+ @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();
+}