From e7be6a85da5be32348f4e83ede195477a7ec1790 Mon Sep 17 00:00:00 2001 From: Wink Saville Date: Thu, 18 Mar 2010 17:03:30 -0700 Subject: [PATCH] Allow transitionTo in enter/exit. bug: 2435366 Change-Id: Id15c5e2cca49ced5ebbda24887f8c490e717f101 --- .../internal/util/HierarchicalState.java | 9 +- .../util/HierarchicalStateMachine.java | 58 +++++-- .../os/HierarchicalStateMachineTest.java | 150 +++++++++++++++++- 3 files changed, 197 insertions(+), 20 deletions(-) diff --git a/core/java/com/android/internal/util/HierarchicalState.java b/core/java/com/android/internal/util/HierarchicalState.java index 002338adfe94c..b37f46c1fe435 100644 --- a/core/java/com/android/internal/util/HierarchicalState.java +++ b/core/java/com/android/internal/util/HierarchicalState.java @@ -21,10 +21,9 @@ import android.os.Message; /** * {@hide} * - * The abstract class for implementing states in a - * HierarchicalStateMachine and HandlerStateMachine. + * The class for implementing states in a HierarchicalStateMachine */ -public abstract class HierarchicalState { +public class HierarchicalState { /** * Constructor @@ -54,7 +53,9 @@ public abstract class HierarchicalState { * if the parent state's processMessage should * be invoked. */ - abstract protected boolean processMessage(Message msg); + protected boolean processMessage(Message msg) { + return false; + } /** * Called when a state is exited. diff --git a/core/java/com/android/internal/util/HierarchicalStateMachine.java b/core/java/com/android/internal/util/HierarchicalStateMachine.java index 7d7f1308ba320..9911f486d5ac9 100644 --- a/core/java/com/android/internal/util/HierarchicalStateMachine.java +++ b/core/java/com/android/internal/util/HierarchicalStateMachine.java @@ -574,23 +574,41 @@ public class HierarchicalStateMachine { } /** - * Process the message abiding by the hierarchical semantics. + * Process the message abiding by the hierarchical semantics + * and perform any requested transitions. */ processMsg(msg); + performTransitions(); + if (mDbg) Log.d(TAG, "handleMessage: X"); + } + + /** + * Do any transitions + */ + private void performTransitions() { /** * If transitionTo has been called, exit and then enter - * the appropriate states. + * the appropriate states. We loop on this to allow + * enter and exit methods to use transitionTo. */ - if (mDestState != null) { + HierarchicalState destState = null; + while (mDestState != null) { if (mDbg) Log.d(TAG, "handleMessage: new destination call exit"); + /** + * Save mDestState locally and set to null + * to know if enter/exit use transitionTo. + */ + destState = mDestState; + mDestState = null; + /** * Determine the states to exit and enter and return the * common ancestor state of the enter/exit states. Then * invoke the exit methods then the enter methods. */ - StateInfo commonStateInfo = setupTempStateStackWithStatesToEnter(mDestState); + StateInfo commonStateInfo = setupTempStateStackWithStatesToEnter(destState); invokeExitMethods(commonStateInfo); int stateStackEnteringIndex = moveTempStateStackToStateStack(); invokeEnterMethods(stateStackEnteringIndex); @@ -603,25 +621,31 @@ public class HierarchicalStateMachine { * message queue. */ moveDeferredMessageAtFrontOfQueue(); + } - /** - * Call halting() if we've transitioned to the halting - * state. All subsequent messages will be processed in - * in the halting state which invokes haltedProcessMessage(msg); - */ - if (mDestState == mQuittingState) { + /** + * After processing all transitions check and + * see if the last transition was to quit or halt. + */ + if (destState != null) { + if (destState == mQuittingState) { + /** + * We are quitting so ignore all messages. + */ mHsm.quitting(); if (mHsm.mHsmThread != null) { // If we made the thread then quit looper getLooper().quit(); } - } else if (mDestState == mHaltingState) { + } else if (destState == mHaltingState) { + /** + * Call halting() if we've transitioned to the halting + * state. All subsequent messages will be processed in + * in the halting state which invokes haltedProcessMessage(msg); + */ mHsm.halting(); } - mDestState = null; } - - if (mDbg) Log.d(TAG, "handleMessage: X"); } /** @@ -657,6 +681,11 @@ public class HierarchicalStateMachine { mIsConstructionCompleted = true; invokeEnterMethods(0); + /** + * Perform any transitions requested by the enter methods + */ + performTransitions(); + if (mDbg) Log.d(TAG, "completeConstruction: X"); } @@ -1167,7 +1196,6 @@ public class HierarchicalStateMachine { return Message.obtain(mHsmHandler, what, obj); } - /** * Enqueue a message to this state machine. */ diff --git a/core/tests/coretests/src/android/os/HierarchicalStateMachineTest.java b/core/tests/coretests/src/android/os/HierarchicalStateMachineTest.java index c51fecc42be7d..89b3fb61ddd27 100644 --- a/core/tests/coretests/src/android/os/HierarchicalStateMachineTest.java +++ b/core/tests/coretests/src/android/os/HierarchicalStateMachineTest.java @@ -48,7 +48,7 @@ public class HierarchicalStateMachineTest extends TestCase { 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 boolean WAIT_FOR_DEBUGGER = true; private static final String TAG = "HierarchicalStateMachineTest"; /** @@ -151,6 +151,154 @@ public class HierarchicalStateMachineTest extends TestCase { 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. */