Merge "Allow transitionTo in enter/exit."
diff --git a/core/java/com/android/internal/util/HierarchicalState.java b/core/java/com/android/internal/util/HierarchicalState.java
index 002338a..b37f46c 100644
--- a/core/java/com/android/internal/util/HierarchicalState.java
+++ b/core/java/com/android/internal/util/HierarchicalState.java
@@ -21,10 +21,9 @@
 /**
  * {@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 @@
      *         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 7d7f130..9911f48 100644
--- a/core/java/com/android/internal/util/HierarchicalStateMachine.java
+++ b/core/java/com/android/internal/util/HierarchicalStateMachine.java
@@ -574,23 +574,41 @@
             }
 
             /**
-             * 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 @@
                  * 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 @@
             mIsConstructionCompleted = true;
             invokeEnterMethods(0);
 
+            /**
+             * Perform any transitions requested by the enter methods
+             */
+            performTransitions();
+
             if (mDbg) Log.d(TAG, "completeConstruction: X");
         }
 
@@ -1167,7 +1196,6 @@
         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 c51fecc..89b3fb6 100644
--- a/core/tests/coretests/src/android/os/HierarchicalStateMachineTest.java
+++ b/core/tests/coretests/src/android/os/HierarchicalStateMachineTest.java
@@ -48,7 +48,7 @@
     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";
 
     /**
@@ -152,6 +152,154 @@
     }
 
     /**
+     * Test enter/exit can use transitionTo
+     */
+    class StateMachineEnterExitTransitionToTest extends HierarchicalStateMachine {
+        StateMachineEnterExitTransitionToTest(String name) {
+            super(name);
+            mThisSm = this;
+            setDbg(DBG);
+
+            // Setup state machine with 1 state
+            addState(mS1);
+            addState(mS2);
+            addState(mS3);
+            addState(mS4);
+
+            // Set the initial state
+            setInitialState(mS1);
+        }
+
+        class S1 extends HierarchicalState {
+            @Override protected void enter() {
+                // Test that a transition in enter and the initial state works
+                mS1EnterCount += 1;
+                transitionTo(mS2);
+                Log.d(TAG, "S1.enter");
+            }
+            @Override protected void exit() {
+                mS1ExitCount += 1;
+                Log.d(TAG, "S1.exit");
+            }
+        }
+
+        class S2 extends HierarchicalState {
+            @Override protected void enter() {
+                mS2EnterCount += 1;
+                Log.d(TAG, "S2.enter");
+            }
+            @Override protected void exit() {
+                // Test transition in exit work
+                mS2ExitCount += 1;
+                transitionTo(mS4);
+                Log.d(TAG, "S2.exit");
+            }
+            @Override protected boolean processMessage(Message message) {
+                // Start a transition to S3 but it will be
+                // changed to a transition to S4
+                transitionTo(mS3);
+                Log.d(TAG, "S2.processMessage");
+                return true;
+            }
+        }
+
+        class S3 extends HierarchicalState {
+            @Override protected void enter() {
+                // Test that we can do halting in an enter/exit
+                transitionToHaltingState();
+                mS3EnterCount += 1;
+                Log.d(TAG, "S3.enter");
+            }
+            @Override protected void exit() {
+                mS3ExitCount += 1;
+                Log.d(TAG, "S3.exit");
+            }
+        }
+
+
+        class S4 extends HierarchicalState {
+            @Override protected void enter() {
+                // Test that we can do halting in an enter/exit
+                transitionToHaltingState();
+                mS4EnterCount += 1;
+                Log.d(TAG, "S4.enter");
+            }
+            @Override protected void exit() {
+                mS4ExitCount += 1;
+                Log.d(TAG, "S4.exit");
+            }
+        }
+
+        @Override
+        protected void halting() {
+            synchronized (mThisSm) {
+                mThisSm.notifyAll();
+            }
+        }
+
+        private StateMachineEnterExitTransitionToTest mThisSm;
+        private S1 mS1 = new S1();
+        private S2 mS2 = new S2();
+        private S3 mS3 = new S3();
+        private S4 mS4 = new S4();
+        private int mS1EnterCount = 0;
+        private int mS1ExitCount = 0;
+        private int mS2EnterCount = 0;
+        private int mS2ExitCount = 0;
+        private int mS3EnterCount = 0;
+        private int mS3ExitCount = 0;
+        private int mS4EnterCount = 0;
+        private int mS4ExitCount = 0;
+    }
+
+    @SmallTest
+    public void testStateMachineEnterExitTransitionToTest() throws Exception {
+        //if (WAIT_FOR_DEBUGGER) Debug.waitForDebugger();
+
+        StateMachineEnterExitTransitionToTest smEnterExitTranstionToTest =
+            new StateMachineEnterExitTransitionToTest("smEnterExitTranstionToTest");
+        smEnterExitTranstionToTest.start();
+        if (smEnterExitTranstionToTest.isDbg()) {
+            Log.d(TAG, "testStateMachineEnterExitTransitionToTest E");
+        }
+
+        synchronized (smEnterExitTranstionToTest) {
+            smEnterExitTranstionToTest.sendMessage(1);
+
+            try {
+                // wait for the messages to be handled
+                smEnterExitTranstionToTest.wait();
+            } catch (InterruptedException e) {
+                Log.e(TAG, "testStateMachineEnterExitTransitionToTest: exception while waiting "
+                    + e.getMessage());
+            }
+        }
+
+        assertTrue(smEnterExitTranstionToTest.getProcessedMessagesCount() == 1);
+
+        ProcessedMessages.Info pmi;
+
+        // Message should be handled by mS2.
+        pmi = smEnterExitTranstionToTest.getProcessedMessage(0);
+        assertEquals(TEST_CMD_1, pmi.getWhat());
+        assertEquals(smEnterExitTranstionToTest.mS2, pmi.getState());
+        assertEquals(smEnterExitTranstionToTest.mS2, pmi.getOriginalState());
+
+        assertEquals(smEnterExitTranstionToTest.mS1EnterCount, 1);
+        assertEquals(smEnterExitTranstionToTest.mS1ExitCount, 1);
+        assertEquals(smEnterExitTranstionToTest.mS2EnterCount, 1);
+        assertEquals(smEnterExitTranstionToTest.mS2ExitCount, 1);
+        assertEquals(smEnterExitTranstionToTest.mS3EnterCount, 1);
+        assertEquals(smEnterExitTranstionToTest.mS3ExitCount, 1);
+        assertEquals(smEnterExitTranstionToTest.mS3EnterCount, 1);
+        assertEquals(smEnterExitTranstionToTest.mS3ExitCount, 1);
+
+        if (smEnterExitTranstionToTest.isDbg()) {
+            Log.d(TAG, "testStateMachineEnterExitTransitionToTest X");
+        }
+    }
+
+    /**
      * Tests that ProcessedMessage works as a circular buffer.
      */
     class StateMachine0 extends HierarchicalStateMachine {