/** * 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 java.util.Collection; import java.util.Iterator; 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.Suppress; import com.android.internal.util.State; import com.android.internal.util.StateMachine; import com.android.internal.util.StateMachine.LogRec; import android.test.suitebuilder.annotation.MediumTest; import android.test.suitebuilder.annotation.SmallTest; import android.util.Log; import junit.framework.TestCase; /** * Test for StateMachine. */ @Suppress // Failing public class StateMachineTest extends TestCase { private static final String ENTER = "enter"; private static final String EXIT = "exit"; private static final String ON_QUITTING = "ON_QUITTING"; 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"; private void sleep(int millis) { try { Thread.sleep(millis); } catch(InterruptedException e) { } } private void dumpLogRecs(StateMachine sm) { int size = sm.getLogRecSize(); tlog("size=" + size + " count=" + sm.getLogRecCount()); for (int i = 0; i < size; i++) { LogRec lr = sm.getLogRec(i); tlog(lr.toString()); } } private void dumpLogRecs(Collection clr) { int size = clr.size(); tlog("size=" + size); for (LogRec lr : clr) { tlog(lr.toString()); } } /** * Tests {@link StateMachine#quit()}. */ class StateMachineQuitTest extends StateMachine { Collection mLogRecs; StateMachineQuitTest(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 onQuitting() { log("onQuitting"); addLogRec(ON_QUITTING); mLogRecs = mThisSm.copyLogRecs(); synchronized (mThisSm) { mThisSm.notifyAll(); } } class S1 extends State { @Override public void exit() { log("S1.exit"); addLogRec(EXIT); } @Override public boolean processMessage(Message message) { switch(message.what) { // Sleep and assume the other messages will be queued up. case TEST_CMD_1: { log("TEST_CMD_1"); sleep(500); quit(); break; } default: { log("default what=" + message.what); break; } } return HANDLED; } } private StateMachineQuitTest mThisSm; private S1 mS1 = new S1(); } @SmallTest public void testStateMachineQuit() throws Exception { if (WAIT_FOR_DEBUGGER) Debug.waitForDebugger(); StateMachineQuitTest smQuitTest = new StateMachineQuitTest("smQuitTest"); smQuitTest.start(); if (smQuitTest.isDbg()) tlog("testStateMachineQuit E"); synchronized (smQuitTest) { // Send 6 message we'll quit on the first but all 6 should be processed before quitting. for (int i = 1; i <= 6; i++) { smQuitTest.sendMessage(smQuitTest.obtainMessage(i)); } try { // wait for the messages to be handled smQuitTest.wait(); } catch (InterruptedException e) { tloge("testStateMachineQuit: exception while waiting " + e.getMessage()); } } dumpLogRecs(smQuitTest.mLogRecs); assertEquals(8, smQuitTest.mLogRecs.size()); LogRec lr; Iterator itr = smQuitTest.mLogRecs.iterator(); for (int i = 1; i <= 6; i++) { lr = itr.next(); assertEquals(i, lr.getWhat()); assertEquals(smQuitTest.mS1, lr.getState()); assertEquals(smQuitTest.mS1, lr.getOriginalState()); } lr = itr.next(); assertEquals(EXIT, lr.getInfo()); assertEquals(smQuitTest.mS1, lr.getState()); lr = itr.next(); assertEquals(ON_QUITTING, lr.getInfo()); if (smQuitTest.isDbg()) tlog("testStateMachineQuit X"); } /** * Tests {@link StateMachine#quitNow()} */ class StateMachineQuitNowTest extends StateMachine { public Collection mLogRecs = null; StateMachineQuitNowTest(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 onQuitting() { log("onQuitting"); addLogRec(ON_QUITTING); // Get a copy of the log records since we're quitting and they will disappear mLogRecs = mThisSm.copyLogRecs(); synchronized (mThisSm) { mThisSm.notifyAll(); } } class S1 extends State { @Override public void exit() { log("S1.exit"); addLogRec(EXIT); } @Override public boolean processMessage(Message message) { switch(message.what) { // Sleep and assume the other messages will be queued up. case TEST_CMD_1: { log("TEST_CMD_1"); sleep(500); quitNow(); break; } default: { log("default what=" + message.what); break; } } return HANDLED; } } private StateMachineQuitNowTest mThisSm; private S1 mS1 = new S1(); } @SmallTest public void testStateMachineQuitNow() throws Exception { if (WAIT_FOR_DEBUGGER) Debug.waitForDebugger(); StateMachineQuitNowTest smQuitNowTest = new StateMachineQuitNowTest("smQuitNowTest"); smQuitNowTest.start(); if (smQuitNowTest.isDbg()) tlog("testStateMachineQuitNow E"); synchronized (smQuitNowTest) { // Send 6 message we'll QuitNow on the first even though // we send 6 only one will be processed. for (int i = 1; i <= 6; i++) { smQuitNowTest.sendMessage(smQuitNowTest.obtainMessage(i)); } try { // wait for the messages to be handled smQuitNowTest.wait(); } catch (InterruptedException e) { tloge("testStateMachineQuitNow: exception while waiting " + e.getMessage()); } } tlog("testStateMachineQuiteNow: logRecs=" + smQuitNowTest.mLogRecs); assertEquals(3, smQuitNowTest.mLogRecs.size()); Iterator itr = smQuitNowTest.mLogRecs.iterator(); LogRec lr = itr.next(); assertEquals(1, lr.getWhat()); assertEquals(smQuitNowTest.mS1, lr.getState()); assertEquals(smQuitNowTest.mS1, lr.getOriginalState()); lr = itr.next(); assertEquals(EXIT, lr.getInfo()); assertEquals(smQuitNowTest.mS1, lr.getState()); lr = itr.next(); assertEquals(ON_QUITTING, lr.getInfo()); if (smQuitNowTest.isDbg()) tlog("testStateMachineQuitNow 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 transitions in enter on the initial state work addLogRec(ENTER); transitionTo(mS2); log("S1.enter"); } @Override public void exit() { addLogRec(EXIT); log("S1.exit"); } } class S2 extends State { @Override public void enter() { addLogRec(ENTER); log("S2.enter"); } @Override public void exit() { // Test transition in exit work transitionTo(mS4); assertEquals(TEST_CMD_1, getCurrentMessage().what); addLogRec(EXIT); log("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("S2.processMessage"); return HANDLED; } } class S3 extends State { @Override public void enter() { addLogRec(ENTER); log("S3.enter"); } @Override public void exit() { addLogRec(EXIT); log("S3.exit"); } } class S4 extends State { @Override public void enter() { addLogRec(ENTER); // Test that we can do halting in an enter/exit transitionToHaltingState(); log("S4.enter"); } @Override public void exit() { addLogRec(EXIT); log("S4.exit"); } } @Override protected void onHalting() { 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(); } @SmallTest public void testStateMachineEnterExitTransitionToTest() throws Exception { //if (WAIT_FOR_DEBUGGER) Debug.waitForDebugger(); StateMachineEnterExitTransitionToTest smEnterExitTranstionToTest = new StateMachineEnterExitTransitionToTest("smEnterExitTranstionToTest"); smEnterExitTranstionToTest.start(); if (smEnterExitTranstionToTest.isDbg()) { tlog("testStateMachineEnterExitTransitionToTest E"); } synchronized (smEnterExitTranstionToTest) { smEnterExitTranstionToTest.sendMessage(TEST_CMD_1); try { // wait for the messages to be handled smEnterExitTranstionToTest.wait(); } catch (InterruptedException e) { tloge("testStateMachineEnterExitTransitionToTest: exception while waiting " + e.getMessage()); } } dumpLogRecs(smEnterExitTranstionToTest); assertEquals(9, smEnterExitTranstionToTest.getLogRecCount()); LogRec lr; lr = smEnterExitTranstionToTest.getLogRec(0); assertEquals(ENTER, lr.getInfo()); assertEquals(smEnterExitTranstionToTest.mS1, lr.getState()); lr = smEnterExitTranstionToTest.getLogRec(1); assertEquals(EXIT, lr.getInfo()); assertEquals(smEnterExitTranstionToTest.mS1, lr.getState()); lr = smEnterExitTranstionToTest.getLogRec(2); assertEquals(ENTER, lr.getInfo()); assertEquals(smEnterExitTranstionToTest.mS2, lr.getState()); lr = smEnterExitTranstionToTest.getLogRec(3); assertEquals(TEST_CMD_1, lr.getWhat()); assertEquals(smEnterExitTranstionToTest.mS2, lr.getState()); assertEquals(smEnterExitTranstionToTest.mS2, lr.getOriginalState()); assertEquals(smEnterExitTranstionToTest.mS3, lr.getDestState()); lr = smEnterExitTranstionToTest.getLogRec(4); assertEquals(TEST_CMD_1, lr.getWhat()); assertEquals(smEnterExitTranstionToTest.mS2, lr.getState()); assertEquals(smEnterExitTranstionToTest.mS2, lr.getOriginalState()); assertEquals(smEnterExitTranstionToTest.mS4, lr.getDestState()); assertEquals(EXIT, lr.getInfo()); lr = smEnterExitTranstionToTest.getLogRec(5); assertEquals(TEST_CMD_1, lr.getWhat()); assertEquals(ENTER, lr.getInfo()); assertEquals(smEnterExitTranstionToTest.mS3, lr.getState()); assertEquals(smEnterExitTranstionToTest.mS3, lr.getOriginalState()); assertEquals(smEnterExitTranstionToTest.mS4, lr.getDestState()); lr = smEnterExitTranstionToTest.getLogRec(6); assertEquals(TEST_CMD_1, lr.getWhat()); assertEquals(EXIT, lr.getInfo()); assertEquals(smEnterExitTranstionToTest.mS3, lr.getState()); assertEquals(smEnterExitTranstionToTest.mS3, lr.getOriginalState()); assertEquals(smEnterExitTranstionToTest.mS4, lr.getDestState()); lr = smEnterExitTranstionToTest.getLogRec(7); assertEquals(TEST_CMD_1, lr.getWhat()); assertEquals(ENTER, lr.getInfo()); assertEquals(smEnterExitTranstionToTest.mS4, lr.getState()); assertEquals(smEnterExitTranstionToTest.mS4, lr.getOriginalState()); assertEquals(smEnterExitTranstionToTest.mS4, lr.getDestState()); lr = smEnterExitTranstionToTest.getLogRec(8); assertEquals(TEST_CMD_1, lr.getWhat()); assertEquals(EXIT, lr.getInfo()); assertEquals(smEnterExitTranstionToTest.mS4, lr.getState()); assertEquals(smEnterExitTranstionToTest.mS4, lr.getOriginalState()); if (smEnterExitTranstionToTest.isDbg()) { tlog("testStateMachineEnterExitTransitionToTest X"); } } /** * Tests that ProcessedMessage works as a circular buffer. */ class StateMachine0 extends StateMachine { StateMachine0(String name) { super(name); mThisSm = this; setDbg(DBG); setLogRecSize(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 onHalting() { 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()) tlog("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) { tloge("testStateMachine0: exception while waiting " + e.getMessage()); } } assertEquals(6, sm0.getLogRecCount()); assertEquals(3, sm0.getLogRecSize()); dumpLogRecs(sm0); LogRec lr; lr = sm0.getLogRec(0); assertEquals(TEST_CMD_4, lr.getWhat()); assertEquals(sm0.mS1, lr.getState()); assertEquals(sm0.mS1, lr.getOriginalState()); lr = sm0.getLogRec(1); assertEquals(TEST_CMD_5, lr.getWhat()); assertEquals(sm0.mS1, lr.getState()); assertEquals(sm0.mS1, lr.getOriginalState()); lr = sm0.getLogRec(2); assertEquals(TEST_CMD_6, lr.getWhat()); assertEquals(sm0.mS1, lr.getState()); assertEquals(sm0.mS1, lr.getOriginalState()); if (sm0.isDbg()) tlog("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("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 onHalting() { 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()) tlog("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) { tloge("testStateMachine1: exception while waiting " + e.getMessage()); } } assertEquals(2, sm1.mEnterCount); assertEquals(2, sm1.mExitCount); assertEquals(2, sm1.getLogRecSize()); LogRec lr; lr = sm1.getLogRec(0); assertEquals(TEST_CMD_1, lr.getWhat()); assertEquals(sm1.mS1, lr.getState()); assertEquals(sm1.mS1, lr.getOriginalState()); lr = sm1.getLogRec(1); assertEquals(TEST_CMD_2, lr.getWhat()); assertEquals(sm1.mS1, lr.getState()); assertEquals(sm1.mS1, lr.getOriginalState()); assertEquals(2, sm1.mEnterCount); assertEquals(2, sm1.mExitCount); if (sm1.isDbg()) tlog("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("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 onHalting() { 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()) tlog("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) { tloge("testStateMachine2: exception while waiting " + e.getMessage()); } } assertEquals(4, sm2.getLogRecSize()); LogRec lr; lr = sm2.getLogRec(0); assertEquals(TEST_CMD_1, lr.getWhat()); assertEquals(sm2.mS1, lr.getState()); lr = sm2.getLogRec(1); assertEquals(TEST_CMD_2, lr.getWhat()); assertEquals(sm2.mS1, lr.getState()); lr = sm2.getLogRec(2); assertEquals(TEST_CMD_1, lr.getWhat()); assertEquals(sm2.mS2, lr.getState()); lr = sm2.getLogRec(3); assertEquals(TEST_CMD_2, lr.getWhat()); assertEquals(sm2.mS2, lr.getState()); assertTrue(sm2.mDidEnter); assertTrue(sm2.mDidExit); if (sm2.isDbg()) tlog("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("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 onHalting() { 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()) tlog("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) { tloge("testStateMachine3: exception while waiting " + e.getMessage()); } } assertEquals(2, sm3.getLogRecSize()); LogRec lr; lr = sm3.getLogRec(0); assertEquals(TEST_CMD_1, lr.getWhat()); assertEquals(sm3.mParentState, lr.getState()); assertEquals(sm3.mChildState, lr.getOriginalState()); lr = sm3.getLogRec(1); assertEquals(TEST_CMD_2, lr.getWhat()); assertEquals(sm3.mParentState, lr.getState()); assertEquals(sm3.mChildState, lr.getOriginalState()); if (sm3.isDbg()) tlog("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("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 onHalting() { 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()) tlog("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) { tloge("testStateMachine4: exception while waiting " + e.getMessage()); } } assertEquals(2, sm4.getLogRecSize()); LogRec lr; lr = sm4.getLogRec(0); assertEquals(TEST_CMD_1, lr.getWhat()); assertEquals(sm4.mChildState1, lr.getState()); assertEquals(sm4.mChildState1, lr.getOriginalState()); lr = sm4.getLogRec(1); assertEquals(TEST_CMD_2, lr.getWhat()); assertEquals(sm4.mParentState, lr.getState()); assertEquals(sm4.mChildState2, lr.getOriginalState()); if (sm4.isDbg()) tlog("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("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 onHalting() { 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()) tlog("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) { tloge("testStateMachine5: exception while waiting " + e.getMessage()); } } assertEquals(6, sm5.getLogRecSize()); 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); LogRec lr; lr = sm5.getLogRec(0); assertEquals(TEST_CMD_1, lr.getWhat()); assertEquals(sm5.mChildState1, lr.getState()); assertEquals(sm5.mChildState1, lr.getOriginalState()); lr = sm5.getLogRec(1); assertEquals(TEST_CMD_2, lr.getWhat()); assertEquals(sm5.mChildState2, lr.getState()); assertEquals(sm5.mChildState2, lr.getOriginalState()); lr = sm5.getLogRec(2); assertEquals(TEST_CMD_3, lr.getWhat()); assertEquals(sm5.mChildState5, lr.getState()); assertEquals(sm5.mChildState5, lr.getOriginalState()); lr = sm5.getLogRec(3); assertEquals(TEST_CMD_4, lr.getWhat()); assertEquals(sm5.mChildState3, lr.getState()); assertEquals(sm5.mChildState3, lr.getOriginalState()); lr = sm5.getLogRec(4); assertEquals(TEST_CMD_5, lr.getWhat()); assertEquals(sm5.mChildState4, lr.getState()); assertEquals(sm5.mChildState4, lr.getOriginalState()); lr = sm5.getLogRec(5); assertEquals(TEST_CMD_6, lr.getWhat()); assertEquals(sm5.mParentState2, lr.getState()); assertEquals(sm5.mParentState2, lr.getOriginalState()); if (sm5.isDbg()) tlog("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("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 onHalting() { synchronized (mThisSm) { mThisSm.notifyAll(); } } private StateMachine6 mThisSm; private S1 mS1 = new S1(); private long mArrivalTimeMsg1; private long mArrivalTimeMsg2; } @MediumTest public void testStateMachine6() throws Exception { final int DELAY_TIME = 250; final int DELAY_FUDGE = 20; StateMachine6 sm6 = new StateMachine6("sm6"); sm6.start(); if (sm6.isDbg()) tlog("testStateMachine6 E"); synchronized (sm6) { // Send a message sm6.sendMessageDelayed(TEST_CMD_2, DELAY_TIME); try { // wait for the messages to be handled sm6.wait(); } catch (InterruptedException e) { tloge("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()) tlog("testStateMachine6: expect " + arrivalTimeDiff + " >= " + expectedDelay); assertTrue(arrivalTimeDiff >= expectedDelay); if (sm6.isDbg()) tlog("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("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 onHalting() { 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 { final int SM7_DELAY_FUDGE = 20; StateMachine7 sm7 = new StateMachine7("sm7"); sm7.start(); if (sm7.isDbg()) tlog("testStateMachine7 E"); synchronized (sm7) { // Send a message sm7.sendMessage(TEST_CMD_1); try { // wait for the messages to be handled sm7.wait(); } catch (InterruptedException e) { tloge("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()) tlog("testStateMachine7: expect " + arrivalTimeDiff + " >= " + expectedDelay); assertTrue(arrivalTimeDiff >= expectedDelay); if (sm7.isDbg()) tlog("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 onHalting() { 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("smUnhandledMessage"); sm.start(); if (sm.isDbg()) tlog("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) { tloge("testStateMachineUnhandledMessage: exception while waiting " + e.getMessage()); } } assertEquals(2, sm.getLogRecSize()); assertEquals(2, sm.mUnhandledMessageCount); if (sm.isDbg()) tlog("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 sharedCounter 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 onHalting() { // 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) tlog("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("smSharedThread", 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) { tloge("testStateMachineSharedThread: exception while waiting " + e.getMessage()); } } for (StateMachineSharedThread sm : sms) { assertEquals(4, sm.getLogRecCount()); for (int i = 0; i < sm.getLogRecSize(); i++) { LogRec lr = sm.getLogRec(i); assertEquals(i+1, lr.getWhat()); assertEquals(sm.mS1, lr.getState()); assertEquals(sm.mS1, lr.getOriginalState()); } } if (DBG) tlog("testStateMachineSharedThread X"); } static class Hsm1 extends StateMachine { private static final String HSM1_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(HSM1_TAG, "makeHsm1 E"); Hsm1 sm = new Hsm1(HSM1_TAG); sm.start(); Log.d(HSM1_TAG, "makeHsm1 X"); return sm; } Hsm1(String name) { super(name); log("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("ctor X"); } class P1 extends State { @Override public void enter() { log("P1.enter"); } @Override public void exit() { log("P1.exit"); } @Override public boolean processMessage(Message message) { boolean retVal; log("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("S1.enter"); } @Override public void exit() { log("S1.exit"); } @Override public boolean processMessage(Message message) { log("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("S2.enter"); } @Override public void exit() { log("S2.exit"); } @Override public boolean processMessage(Message message) { boolean retVal; log("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("P2.enter"); sendMessage(CMD_5); } @Override public void exit() { log("P2.exit"); } @Override public boolean processMessage(Message message) { log("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 onHalting() { log("halting"); synchronized (this) { this.notifyAll(); } } P1 mP1 = new P1(); S1 mS1 = new S1(); S2 mS2 = new S2(); P2 mP2 = new P2(); } @MediumTest public void testHsm1() throws Exception { if (DBG) tlog("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) { tloge("testHsm1: exception while waiting " + e.getMessage()); } } dumpLogRecs(sm); assertEquals(7, sm.getLogRecCount()); LogRec lr = sm.getLogRec(0); assertEquals(Hsm1.CMD_1, lr.getWhat()); assertEquals(sm.mS1, lr.getState()); assertEquals(sm.mS1, lr.getOriginalState()); lr = sm.getLogRec(1); assertEquals(Hsm1.CMD_2, lr.getWhat()); assertEquals(sm.mP1, lr.getState()); assertEquals(sm.mS1, lr.getOriginalState()); lr = sm.getLogRec(2); assertEquals(Hsm1.CMD_2, lr.getWhat()); assertEquals(sm.mS2, lr.getState()); assertEquals(sm.mS2, lr.getOriginalState()); lr = sm.getLogRec(3); assertEquals(Hsm1.CMD_3, lr.getWhat()); assertEquals(sm.mS2, lr.getState()); assertEquals(sm.mS2, lr.getOriginalState()); lr = sm.getLogRec(4); assertEquals(Hsm1.CMD_3, lr.getWhat()); assertEquals(sm.mP2, lr.getState()); assertEquals(sm.mP2, lr.getOriginalState()); lr = sm.getLogRec(5); assertEquals(Hsm1.CMD_4, lr.getWhat()); assertEquals(sm.mP2, lr.getState()); assertEquals(sm.mP2, lr.getOriginalState()); lr = sm.getLogRec(6); assertEquals(Hsm1.CMD_5, lr.getWhat()); assertEquals(sm.mP2, lr.getState()); assertEquals(sm.mP2, lr.getOriginalState()); if (DBG) tlog("testStateMachineSharedThread X"); } private void tlog(String s) { Log.d(TAG, s); } private void tloge(String s) { Log.e(TAG, s); } }