1/**
2 * Copyright (C) 2009 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.os;
18
19import com.android.internal.util.HierarchicalState;
20import com.android.internal.util.HierarchicalStateMachine;
21import com.android.internal.util.ProcessedMessages;
22
23import android.test.suitebuilder.annotation.MediumTest;
24import android.test.suitebuilder.annotation.SmallTest;
25import android.util.Log;
26
27import junit.framework.TestCase;
28
29/**
30 * Test for HierarchicalStateMachine.
31 *
32 * @author wink@google.com (Wink Saville)
33 */
34public class HierarchicalStateMachineTest extends TestCase {
35    private static final int TEST_CMD_1 = 1;
36    private static final int TEST_CMD_2 = 2;
37    private static final int TEST_CMD_3 = 3;
38    private static final int TEST_CMD_4 = 4;
39    private static final int TEST_CMD_5 = 5;
40    private static final int TEST_CMD_6 = 6;
41
42    private static final boolean DBG = true;
43    private static final boolean WAIT_FOR_DEBUGGER = true;
44    private static final String TAG = "HierarchicalStateMachineTest";
45
46    /**
47     * Tests that we can quit the state machine.
48     */
49    class StateMachineQuitTest extends HierarchicalStateMachine {
50        private int mQuitCount = 0;
51
52        StateMachineQuitTest(String name) {
53            super(name);
54            mThisSm = this;
55            setDbg(DBG);
56
57            // Setup state machine with 1 state
58            addState(mS1);
59
60            // Set the initial state
61            setInitialState(mS1);
62        }
63
64        class S1 extends HierarchicalState {
65            @Override protected boolean processMessage(Message message) {
66                if (isQuit(message)) {
67                    mQuitCount += 1;
68                    if (mQuitCount > 2) {
69                        // Returning NOT_HANDLED to actually quit
70                        return NOT_HANDLED;
71                    } else {
72                        // Do NOT quit
73                        return HANDLED;
74                    }
75                } else  {
76                    // All other message are handled
77                    return HANDLED;
78                }
79            }
80        }
81
82        @Override
83        protected void quitting() {
84            synchronized (mThisSm) {
85                mThisSm.notifyAll();
86            }
87        }
88
89        private StateMachineQuitTest mThisSm;
90        private S1 mS1 = new S1();
91    }
92
93    @SmallTest
94    public void testStateMachineQuitTest() throws Exception {
95        //if (WAIT_FOR_DEBUGGER) Debug.waitForDebugger();
96
97        StateMachineQuitTest smQuitTest = new StateMachineQuitTest("smQuitTest");
98        smQuitTest.start();
99        if (smQuitTest.isDbg()) Log.d(TAG, "testStateMachineQuitTest E");
100
101        synchronized (smQuitTest) {
102            // Send 6 messages
103            for (int i = 1; i <= 6; i++) {
104                smQuitTest.sendMessage(i);
105            }
106
107            // First two are ignored
108            smQuitTest.quit();
109            smQuitTest.quit();
110
111            // Now we will quit
112            smQuitTest.quit();
113
114            try {
115                // wait for the messages to be handled
116                smQuitTest.wait();
117            } catch (InterruptedException e) {
118                Log.e(TAG, "testStateMachineQuitTest: exception while waiting " + e.getMessage());
119            }
120        }
121
122        assertTrue(smQuitTest.getProcessedMessagesCount() == 9);
123
124        ProcessedMessages.Info pmi;
125
126        // The first two message didn't quit and were handled by mS1
127        pmi = smQuitTest.getProcessedMessage(6);
128        assertEquals(HierarchicalStateMachine.HSM_QUIT_CMD, pmi.getWhat());
129        assertEquals(smQuitTest.mS1, pmi.getState());
130        assertEquals(smQuitTest.mS1, pmi.getOriginalState());
131
132        pmi = smQuitTest.getProcessedMessage(7);
133        assertEquals(HierarchicalStateMachine.HSM_QUIT_CMD, pmi.getWhat());
134        assertEquals(smQuitTest.mS1, pmi.getState());
135        assertEquals(smQuitTest.mS1, pmi.getOriginalState());
136
137        // The last message was never handled so the states are null
138        pmi = smQuitTest.getProcessedMessage(8);
139        assertEquals(HierarchicalStateMachine.HSM_QUIT_CMD, pmi.getWhat());
140        assertEquals(null, pmi.getState());
141        assertEquals(null, pmi.getOriginalState());
142
143        if (smQuitTest.isDbg()) Log.d(TAG, "testStateMachineQuitTest X");
144    }
145
146    /**
147     * Test enter/exit can use transitionTo
148     */
149    class StateMachineEnterExitTransitionToTest extends HierarchicalStateMachine {
150        StateMachineEnterExitTransitionToTest(String name) {
151            super(name);
152            mThisSm = this;
153            setDbg(DBG);
154
155            // Setup state machine with 1 state
156            addState(mS1);
157            addState(mS2);
158            addState(mS3);
159            addState(mS4);
160
161            // Set the initial state
162            setInitialState(mS1);
163        }
164
165        class S1 extends HierarchicalState {
166            @Override protected void enter() {
167                // Test that message is HSM_INIT_CMD
168                assertEquals(HSM_INIT_CMD, getCurrentMessage().what);
169
170                // Test that a transition in enter and the initial state works
171                mS1EnterCount += 1;
172                transitionTo(mS2);
173                Log.d(TAG, "S1.enter");
174            }
175            @Override protected void exit() {
176                // Test that message is HSM_INIT_CMD
177                assertEquals(HSM_INIT_CMD, getCurrentMessage().what);
178
179                mS1ExitCount += 1;
180                Log.d(TAG, "S1.exit");
181            }
182        }
183
184        class S2 extends HierarchicalState {
185            @Override protected void enter() {
186                // Test that message is HSM_INIT_CMD
187                assertEquals(HSM_INIT_CMD, getCurrentMessage().what);
188
189                mS2EnterCount += 1;
190                Log.d(TAG, "S2.enter");
191            }
192            @Override protected void exit() {
193                // Test that message is TEST_CMD_1
194                assertEquals(TEST_CMD_1, getCurrentMessage().what);
195
196                // Test transition in exit work
197                mS2ExitCount += 1;
198                transitionTo(mS4);
199                Log.d(TAG, "S2.exit");
200            }
201            @Override protected boolean processMessage(Message message) {
202                // Start a transition to S3 but it will be
203                // changed to a transition to S4 in exit
204                transitionTo(mS3);
205                Log.d(TAG, "S2.processMessage");
206                return HANDLED;
207            }
208        }
209
210        class S3 extends HierarchicalState {
211            @Override protected void enter() {
212                // Test that we can do halting in an enter/exit
213                transitionToHaltingState();
214                mS3EnterCount += 1;
215                Log.d(TAG, "S3.enter");
216            }
217            @Override protected void exit() {
218                mS3ExitCount += 1;
219                Log.d(TAG, "S3.exit");
220            }
221        }
222
223
224        class S4 extends HierarchicalState {
225            @Override protected void enter() {
226                // Test that we can do halting in an enter/exit
227                transitionToHaltingState();
228                mS4EnterCount += 1;
229                Log.d(TAG, "S4.enter");
230            }
231            @Override protected void exit() {
232                mS4ExitCount += 1;
233                Log.d(TAG, "S4.exit");
234            }
235        }
236
237        @Override
238        protected void halting() {
239            synchronized (mThisSm) {
240                mThisSm.notifyAll();
241            }
242        }
243
244        private StateMachineEnterExitTransitionToTest mThisSm;
245        private S1 mS1 = new S1();
246        private S2 mS2 = new S2();
247        private S3 mS3 = new S3();
248        private S4 mS4 = new S4();
249        private int mS1EnterCount = 0;
250        private int mS1ExitCount = 0;
251        private int mS2EnterCount = 0;
252        private int mS2ExitCount = 0;
253        private int mS3EnterCount = 0;
254        private int mS3ExitCount = 0;
255        private int mS4EnterCount = 0;
256        private int mS4ExitCount = 0;
257    }
258
259    @SmallTest
260    public void testStateMachineEnterExitTransitionToTest() throws Exception {
261        //if (WAIT_FOR_DEBUGGER) Debug.waitForDebugger();
262
263        StateMachineEnterExitTransitionToTest smEnterExitTranstionToTest =
264            new StateMachineEnterExitTransitionToTest("smEnterExitTranstionToTest");
265        smEnterExitTranstionToTest.start();
266        if (smEnterExitTranstionToTest.isDbg()) {
267            Log.d(TAG, "testStateMachineEnterExitTransitionToTest E");
268        }
269
270        synchronized (smEnterExitTranstionToTest) {
271            smEnterExitTranstionToTest.sendMessage(TEST_CMD_1);
272
273            try {
274                // wait for the messages to be handled
275                smEnterExitTranstionToTest.wait();
276            } catch (InterruptedException e) {
277                Log.e(TAG, "testStateMachineEnterExitTransitionToTest: exception while waiting "
278                    + e.getMessage());
279            }
280        }
281
282        assertTrue(smEnterExitTranstionToTest.getProcessedMessagesCount() == 1);
283
284        ProcessedMessages.Info pmi;
285
286        // Message should be handled by mS2.
287        pmi = smEnterExitTranstionToTest.getProcessedMessage(0);
288        assertEquals(TEST_CMD_1, pmi.getWhat());
289        assertEquals(smEnterExitTranstionToTest.mS2, pmi.getState());
290        assertEquals(smEnterExitTranstionToTest.mS2, pmi.getOriginalState());
291
292        assertEquals(smEnterExitTranstionToTest.mS1EnterCount, 1);
293        assertEquals(smEnterExitTranstionToTest.mS1ExitCount, 1);
294        assertEquals(smEnterExitTranstionToTest.mS2EnterCount, 1);
295        assertEquals(smEnterExitTranstionToTest.mS2ExitCount, 1);
296        assertEquals(smEnterExitTranstionToTest.mS3EnterCount, 1);
297        assertEquals(smEnterExitTranstionToTest.mS3ExitCount, 1);
298        assertEquals(smEnterExitTranstionToTest.mS3EnterCount, 1);
299        assertEquals(smEnterExitTranstionToTest.mS3ExitCount, 1);
300
301        if (smEnterExitTranstionToTest.isDbg()) {
302            Log.d(TAG, "testStateMachineEnterExitTransitionToTest X");
303        }
304    }
305
306    /**
307     * Tests that ProcessedMessage works as a circular buffer.
308     */
309    class StateMachine0 extends HierarchicalStateMachine {
310        StateMachine0(String name) {
311            super(name);
312            mThisSm = this;
313            setDbg(DBG);
314            setProcessedMessagesSize(3);
315
316            // Setup state machine with 1 state
317            addState(mS1);
318
319            // Set the initial state
320            setInitialState(mS1);
321        }
322
323        class S1 extends HierarchicalState {
324            @Override protected boolean processMessage(Message message) {
325                if (message.what == TEST_CMD_6) {
326                    transitionToHaltingState();
327                }
328                return HANDLED;
329            }
330        }
331
332        @Override
333        protected void halting() {
334            synchronized (mThisSm) {
335                mThisSm.notifyAll();
336            }
337        }
338
339        private StateMachine0 mThisSm;
340        private S1 mS1 = new S1();
341    }
342
343    @SmallTest
344    public void testStateMachine0() throws Exception {
345        //if (WAIT_FOR_DEBUGGER) Debug.waitForDebugger();
346
347        StateMachine0 sm0 = new StateMachine0("sm0");
348        sm0.start();
349        if (sm0.isDbg()) Log.d(TAG, "testStateMachine0 E");
350
351        synchronized (sm0) {
352            // Send 6 messages
353            for (int i = 1; i <= 6; i++) {
354                sm0.sendMessage(sm0.obtainMessage(i));
355            }
356
357            try {
358                // wait for the messages to be handled
359                sm0.wait();
360            } catch (InterruptedException e) {
361                Log.e(TAG, "testStateMachine0: exception while waiting " + e.getMessage());
362            }
363        }
364
365        assertTrue(sm0.getProcessedMessagesCount() == 6);
366        assertTrue(sm0.getProcessedMessagesSize() == 3);
367
368        ProcessedMessages.Info pmi;
369        pmi = sm0.getProcessedMessage(0);
370        assertEquals(TEST_CMD_4, pmi.getWhat());
371        assertEquals(sm0.mS1, pmi.getState());
372        assertEquals(sm0.mS1, pmi.getOriginalState());
373
374        pmi = sm0.getProcessedMessage(1);
375        assertEquals(TEST_CMD_5, pmi.getWhat());
376        assertEquals(sm0.mS1, pmi.getState());
377        assertEquals(sm0.mS1, pmi.getOriginalState());
378
379        pmi = sm0.getProcessedMessage(2);
380        assertEquals(TEST_CMD_6, pmi.getWhat());
381        assertEquals(sm0.mS1, pmi.getState());
382        assertEquals(sm0.mS1, pmi.getOriginalState());
383
384        if (sm0.isDbg()) Log.d(TAG, "testStateMachine0 X");
385    }
386
387    /**
388     * This tests enter/exit and transitions to the same state.
389     * The state machine has one state, it receives two messages
390     * in state mS1. With the first message it transitions to
391     * itself which causes it to be exited and reentered.
392     */
393    class StateMachine1 extends HierarchicalStateMachine {
394        StateMachine1(String name) {
395            super(name);
396            mThisSm = this;
397            setDbg(DBG);
398
399            // Setup state machine with 1 state
400            addState(mS1);
401
402            // Set the initial state
403            setInitialState(mS1);
404            if (DBG) Log.d(TAG, "StateMachine1: ctor X");
405        }
406
407        class S1 extends HierarchicalState {
408            @Override protected void enter() {
409                mEnterCount++;
410            }
411
412            @Override protected boolean processMessage(Message message) {
413                if (message.what == TEST_CMD_1) {
414                    assertEquals(1, mEnterCount);
415                    assertEquals(0, mExitCount);
416                    transitionTo(mS1);
417                } else if (message.what == TEST_CMD_2) {
418                    assertEquals(2, mEnterCount);
419                    assertEquals(1, mExitCount);
420                    transitionToHaltingState();
421                }
422                return HANDLED;
423            }
424
425            @Override protected void exit() {
426                mExitCount++;
427            }
428        }
429
430        @Override
431        protected void halting() {
432            synchronized (mThisSm) {
433                mThisSm.notifyAll();
434            }
435        }
436
437        private StateMachine1 mThisSm;
438        private S1 mS1 = new S1();
439
440        private int mEnterCount;
441        private int mExitCount;
442    }
443
444    @MediumTest
445    public void testStateMachine1() throws Exception {
446        StateMachine1 sm1 = new StateMachine1("sm1");
447        sm1.start();
448        if (sm1.isDbg()) Log.d(TAG, "testStateMachine1 E");
449
450        synchronized (sm1) {
451            // Send two messages
452            sm1.sendMessage(TEST_CMD_1);
453            sm1.sendMessage(TEST_CMD_2);
454
455            try {
456                // wait for the messages to be handled
457                sm1.wait();
458            } catch (InterruptedException e) {
459                Log.e(TAG, "testStateMachine1: exception while waiting " + e.getMessage());
460            }
461        }
462
463        assertEquals(2, sm1.mEnterCount);
464        assertEquals(2, sm1.mExitCount);
465
466        assertTrue(sm1.getProcessedMessagesSize() == 2);
467
468        ProcessedMessages.Info pmi;
469        pmi = sm1.getProcessedMessage(0);
470        assertEquals(TEST_CMD_1, pmi.getWhat());
471        assertEquals(sm1.mS1, pmi.getState());
472        assertEquals(sm1.mS1, pmi.getOriginalState());
473
474        pmi = sm1.getProcessedMessage(1);
475        assertEquals(TEST_CMD_2, pmi.getWhat());
476        assertEquals(sm1.mS1, pmi.getState());
477        assertEquals(sm1.mS1, pmi.getOriginalState());
478
479        assertEquals(2, sm1.mEnterCount);
480        assertEquals(2, sm1.mExitCount);
481
482        if (sm1.isDbg()) Log.d(TAG, "testStateMachine1 X");
483    }
484
485    /**
486     * Test deferring messages and states with no parents. The state machine
487     * has two states, it receives two messages in state mS1 deferring them
488     * until what == TEST_CMD_2 and then transitions to state mS2. State
489     * mS2 then receives both of the deferred messages first TEST_CMD_1 and
490     * then TEST_CMD_2.
491     */
492    class StateMachine2 extends HierarchicalStateMachine {
493        StateMachine2(String name) {
494            super(name);
495            mThisSm = this;
496            setDbg(DBG);
497
498            // Setup the hierarchy
499            addState(mS1);
500            addState(mS2);
501
502            // Set the initial state
503            setInitialState(mS1);
504            if (DBG) Log.d(TAG, "StateMachine2: ctor X");
505        }
506
507        class S1 extends HierarchicalState {
508            @Override protected void enter() {
509                mDidEnter = true;
510            }
511
512            @Override protected boolean processMessage(Message message) {
513                deferMessage(message);
514                if (message.what == TEST_CMD_2) {
515                    transitionTo(mS2);
516                }
517                return HANDLED;
518            }
519
520            @Override protected void exit() {
521                mDidExit = true;
522            }
523        }
524
525        class S2 extends HierarchicalState {
526            @Override protected boolean processMessage(Message message) {
527                if (message.what == TEST_CMD_2) {
528                    transitionToHaltingState();
529                }
530                return HANDLED;
531            }
532        }
533
534        @Override
535        protected void halting() {
536            synchronized (mThisSm) {
537                mThisSm.notifyAll();
538            }
539        }
540
541        private StateMachine2 mThisSm;
542        private S1 mS1 = new S1();
543        private S2 mS2 = new S2();
544
545        private boolean mDidEnter = false;
546        private boolean mDidExit = false;
547    }
548
549    @MediumTest
550    public void testStateMachine2() throws Exception {
551        StateMachine2 sm2 = new StateMachine2("sm2");
552        sm2.start();
553        if (sm2.isDbg()) Log.d(TAG, "testStateMachine2 E");
554
555        synchronized (sm2) {
556            // Send two messages
557            sm2.sendMessage(TEST_CMD_1);
558            sm2.sendMessage(TEST_CMD_2);
559
560            try {
561                // wait for the messages to be handled
562                sm2.wait();
563            } catch (InterruptedException e) {
564                Log.e(TAG, "testStateMachine2: exception while waiting " + e.getMessage());
565            }
566        }
567
568        assertTrue(sm2.getProcessedMessagesSize() == 4);
569
570        ProcessedMessages.Info pmi;
571        pmi = sm2.getProcessedMessage(0);
572        assertEquals(TEST_CMD_1, pmi.getWhat());
573        assertEquals(sm2.mS1, pmi.getState());
574
575        pmi = sm2.getProcessedMessage(1);
576        assertEquals(TEST_CMD_2, pmi.getWhat());
577        assertEquals(sm2.mS1, pmi.getState());
578
579        pmi = sm2.getProcessedMessage(2);
580        assertEquals(TEST_CMD_1, pmi.getWhat());
581        assertEquals(sm2.mS2, pmi.getState());
582
583        pmi = sm2.getProcessedMessage(3);
584        assertEquals(TEST_CMD_2, pmi.getWhat());
585        assertEquals(sm2.mS2, pmi.getState());
586
587        assertTrue(sm2.mDidEnter);
588        assertTrue(sm2.mDidExit);
589
590        if (sm2.isDbg()) Log.d(TAG, "testStateMachine2 X");
591    }
592
593    /**
594     * Test that unhandled messages in a child are handled by the parent.
595     * When TEST_CMD_2 is received.
596     */
597    class StateMachine3 extends HierarchicalStateMachine {
598        StateMachine3(String name) {
599            super(name);
600            mThisSm = this;
601            setDbg(DBG);
602
603            // Setup the simplest hierarchy of two states
604            // mParentState and mChildState.
605            // (Use indentation to help visualize hierarchy)
606            addState(mParentState);
607                addState(mChildState, mParentState);
608
609            // Set the initial state will be the child
610            setInitialState(mChildState);
611            if (DBG) Log.d(TAG, "StateMachine3: ctor X");
612        }
613
614        class ParentState extends HierarchicalState {
615            @Override protected boolean processMessage(Message message) {
616                if (message.what == TEST_CMD_2) {
617                    transitionToHaltingState();
618                }
619                return HANDLED;
620            }
621        }
622
623        class ChildState extends HierarchicalState {
624            @Override protected boolean processMessage(Message message) {
625                return NOT_HANDLED;
626            }
627        }
628
629        @Override
630        protected void halting() {
631            synchronized (mThisSm) {
632                mThisSm.notifyAll();
633            }
634        }
635
636        private StateMachine3 mThisSm;
637        private ParentState mParentState = new ParentState();
638        private ChildState mChildState = new ChildState();
639    }
640
641    @MediumTest
642    public void testStateMachine3() throws Exception {
643        StateMachine3 sm3 = new StateMachine3("sm3");
644        sm3.start();
645        if (sm3.isDbg()) Log.d(TAG, "testStateMachine3 E");
646
647        synchronized (sm3) {
648            // Send two messages
649            sm3.sendMessage(TEST_CMD_1);
650            sm3.sendMessage(TEST_CMD_2);
651
652            try {
653                // wait for the messages to be handled
654                sm3.wait();
655            } catch (InterruptedException e) {
656                Log.e(TAG, "testStateMachine3: exception while waiting " + e.getMessage());
657            }
658        }
659
660        assertTrue(sm3.getProcessedMessagesSize() == 2);
661
662        ProcessedMessages.Info pmi;
663        pmi = sm3.getProcessedMessage(0);
664        assertEquals(TEST_CMD_1, pmi.getWhat());
665        assertEquals(sm3.mParentState, pmi.getState());
666        assertEquals(sm3.mChildState, pmi.getOriginalState());
667
668        pmi = sm3.getProcessedMessage(1);
669        assertEquals(TEST_CMD_2, pmi.getWhat());
670        assertEquals(sm3.mParentState, pmi.getState());
671        assertEquals(sm3.mChildState, pmi.getOriginalState());
672
673        if (sm3.isDbg()) Log.d(TAG, "testStateMachine3 X");
674    }
675
676    /**
677     * Test a hierarchy of 3 states a parent and two children
678     * with transition from child 1 to child 2 and child 2
679     * lets the parent handle the messages.
680     */
681    class StateMachine4 extends HierarchicalStateMachine {
682        StateMachine4(String name) {
683            super(name);
684            mThisSm = this;
685            setDbg(DBG);
686
687            // Setup a hierarchy of three states
688            // mParentState, mChildState1 & mChildState2
689            // (Use indentation to help visualize hierarchy)
690            addState(mParentState);
691                addState(mChildState1, mParentState);
692                addState(mChildState2, mParentState);
693
694            // Set the initial state will be child 1
695            setInitialState(mChildState1);
696            if (DBG) Log.d(TAG, "StateMachine4: ctor X");
697        }
698
699        class ParentState extends HierarchicalState {
700            @Override protected boolean processMessage(Message message) {
701                if (message.what == TEST_CMD_2) {
702                    transitionToHaltingState();
703                }
704                return HANDLED;
705            }
706        }
707
708        class ChildState1 extends HierarchicalState {
709            @Override protected boolean processMessage(Message message) {
710                transitionTo(mChildState2);
711                return HANDLED;
712            }
713        }
714
715        class ChildState2 extends HierarchicalState {
716            @Override protected boolean processMessage(Message message) {
717                return NOT_HANDLED;
718            }
719        }
720
721        @Override
722        protected void halting() {
723            synchronized (mThisSm) {
724                mThisSm.notifyAll();
725            }
726        }
727
728        private StateMachine4 mThisSm;
729        private ParentState mParentState = new ParentState();
730        private ChildState1 mChildState1 = new ChildState1();
731        private ChildState2 mChildState2 = new ChildState2();
732    }
733
734    @MediumTest
735    public void testStateMachine4() throws Exception {
736        StateMachine4 sm4 = new StateMachine4("sm4");
737        sm4.start();
738        if (sm4.isDbg()) Log.d(TAG, "testStateMachine4 E");
739
740        synchronized (sm4) {
741            // Send two messages
742            sm4.sendMessage(TEST_CMD_1);
743            sm4.sendMessage(TEST_CMD_2);
744
745            try {
746                // wait for the messages to be handled
747                sm4.wait();
748            } catch (InterruptedException e) {
749                Log.e(TAG, "testStateMachine4: exception while waiting " + e.getMessage());
750            }
751        }
752
753
754        assertTrue(sm4.getProcessedMessagesSize() == 2);
755
756        ProcessedMessages.Info pmi;
757        pmi = sm4.getProcessedMessage(0);
758        assertEquals(TEST_CMD_1, pmi.getWhat());
759        assertEquals(sm4.mChildState1, pmi.getState());
760        assertEquals(sm4.mChildState1, pmi.getOriginalState());
761
762        pmi = sm4.getProcessedMessage(1);
763        assertEquals(TEST_CMD_2, pmi.getWhat());
764        assertEquals(sm4.mParentState, pmi.getState());
765        assertEquals(sm4.mChildState2, pmi.getOriginalState());
766
767        if (sm4.isDbg()) Log.d(TAG, "testStateMachine4 X");
768    }
769
770    /**
771     * Test transition from one child to another of a "complex"
772     * hierarchy with two parents and multiple children.
773     */
774    class StateMachine5 extends HierarchicalStateMachine {
775        StateMachine5(String name) {
776            super(name);
777            mThisSm = this;
778            setDbg(DBG);
779
780            // Setup a hierarchy with two parents and some children.
781            // (Use indentation to help visualize hierarchy)
782            addState(mParentState1);
783                addState(mChildState1, mParentState1);
784                addState(mChildState2, mParentState1);
785
786            addState(mParentState2);
787                addState(mChildState3, mParentState2);
788                addState(mChildState4, mParentState2);
789                    addState(mChildState5, mChildState4);
790
791            // Set the initial state will be the child
792            setInitialState(mChildState1);
793            if (DBG) Log.d(TAG, "StateMachine5: ctor X");
794        }
795
796        class ParentState1 extends HierarchicalState {
797            @Override protected void enter() {
798                mParentState1EnterCount += 1;
799            }
800            @Override protected boolean processMessage(Message message) {
801                return HANDLED;
802            }
803            @Override protected void exit() {
804                mParentState1ExitCount += 1;
805            }
806        }
807
808        class ChildState1 extends HierarchicalState {
809            @Override protected void enter() {
810                mChildState1EnterCount += 1;
811            }
812            @Override protected boolean processMessage(Message message) {
813                assertEquals(1, mParentState1EnterCount);
814                assertEquals(0, mParentState1ExitCount);
815                assertEquals(1, mChildState1EnterCount);
816                assertEquals(0, mChildState1ExitCount);
817                assertEquals(0, mChildState2EnterCount);
818                assertEquals(0, mChildState2ExitCount);
819                assertEquals(0, mParentState2EnterCount);
820                assertEquals(0, mParentState2ExitCount);
821                assertEquals(0, mChildState3EnterCount);
822                assertEquals(0, mChildState3ExitCount);
823                assertEquals(0, mChildState4EnterCount);
824                assertEquals(0, mChildState4ExitCount);
825                assertEquals(0, mChildState5EnterCount);
826                assertEquals(0, mChildState5ExitCount);
827
828                transitionTo(mChildState2);
829                return HANDLED;
830            }
831            @Override protected void exit() {
832                mChildState1ExitCount += 1;
833            }
834        }
835
836        class ChildState2 extends HierarchicalState {
837            @Override protected void enter() {
838                mChildState2EnterCount += 1;
839            }
840            @Override protected boolean processMessage(Message message) {
841                assertEquals(1, mParentState1EnterCount);
842                assertEquals(0, mParentState1ExitCount);
843                assertEquals(1, mChildState1EnterCount);
844                assertEquals(1, mChildState1ExitCount);
845                assertEquals(1, mChildState2EnterCount);
846                assertEquals(0, mChildState2ExitCount);
847                assertEquals(0, mParentState2EnterCount);
848                assertEquals(0, mParentState2ExitCount);
849                assertEquals(0, mChildState3EnterCount);
850                assertEquals(0, mChildState3ExitCount);
851                assertEquals(0, mChildState4EnterCount);
852                assertEquals(0, mChildState4ExitCount);
853                assertEquals(0, mChildState5EnterCount);
854                assertEquals(0, mChildState5ExitCount);
855
856                transitionTo(mChildState5);
857                return HANDLED;
858            }
859            @Override protected void exit() {
860                mChildState2ExitCount += 1;
861            }
862        }
863
864        class ParentState2 extends HierarchicalState {
865            @Override protected void enter() {
866                mParentState2EnterCount += 1;
867            }
868            @Override protected boolean processMessage(Message message) {
869                assertEquals(1, mParentState1EnterCount);
870                assertEquals(1, mParentState1ExitCount);
871                assertEquals(1, mChildState1EnterCount);
872                assertEquals(1, mChildState1ExitCount);
873                assertEquals(1, mChildState2EnterCount);
874                assertEquals(1, mChildState2ExitCount);
875                assertEquals(2, mParentState2EnterCount);
876                assertEquals(1, mParentState2ExitCount);
877                assertEquals(1, mChildState3EnterCount);
878                assertEquals(1, mChildState3ExitCount);
879                assertEquals(2, mChildState4EnterCount);
880                assertEquals(2, mChildState4ExitCount);
881                assertEquals(1, mChildState5EnterCount);
882                assertEquals(1, mChildState5ExitCount);
883
884                transitionToHaltingState();
885                return HANDLED;
886            }
887            @Override protected void exit() {
888                mParentState2ExitCount += 1;
889            }
890        }
891
892        class ChildState3 extends HierarchicalState {
893            @Override protected void enter() {
894                mChildState3EnterCount += 1;
895            }
896            @Override protected boolean processMessage(Message message) {
897                assertEquals(1, mParentState1EnterCount);
898                assertEquals(1, mParentState1ExitCount);
899                assertEquals(1, mChildState1EnterCount);
900                assertEquals(1, mChildState1ExitCount);
901                assertEquals(1, mChildState2EnterCount);
902                assertEquals(1, mChildState2ExitCount);
903                assertEquals(1, mParentState2EnterCount);
904                assertEquals(0, mParentState2ExitCount);
905                assertEquals(1, mChildState3EnterCount);
906                assertEquals(0, mChildState3ExitCount);
907                assertEquals(1, mChildState4EnterCount);
908                assertEquals(1, mChildState4ExitCount);
909                assertEquals(1, mChildState5EnterCount);
910                assertEquals(1, mChildState5ExitCount);
911
912                transitionTo(mChildState4);
913                return HANDLED;
914            }
915            @Override protected void exit() {
916                mChildState3ExitCount += 1;
917            }
918        }
919
920        class ChildState4 extends HierarchicalState {
921            @Override protected void enter() {
922                mChildState4EnterCount += 1;
923            }
924            @Override protected boolean processMessage(Message message) {
925                assertEquals(1, mParentState1EnterCount);
926                assertEquals(1, mParentState1ExitCount);
927                assertEquals(1, mChildState1EnterCount);
928                assertEquals(1, mChildState1ExitCount);
929                assertEquals(1, mChildState2EnterCount);
930                assertEquals(1, mChildState2ExitCount);
931                assertEquals(1, mParentState2EnterCount);
932                assertEquals(0, mParentState2ExitCount);
933                assertEquals(1, mChildState3EnterCount);
934                assertEquals(1, mChildState3ExitCount);
935                assertEquals(2, mChildState4EnterCount);
936                assertEquals(1, mChildState4ExitCount);
937                assertEquals(1, mChildState5EnterCount);
938                assertEquals(1, mChildState5ExitCount);
939
940                transitionTo(mParentState2);
941                return HANDLED;
942            }
943            @Override protected void exit() {
944                mChildState4ExitCount += 1;
945            }
946        }
947
948        class ChildState5 extends HierarchicalState {
949            @Override protected void enter() {
950                mChildState5EnterCount += 1;
951            }
952            @Override protected boolean processMessage(Message message) {
953                assertEquals(1, mParentState1EnterCount);
954                assertEquals(1, mParentState1ExitCount);
955                assertEquals(1, mChildState1EnterCount);
956                assertEquals(1, mChildState1ExitCount);
957                assertEquals(1, mChildState2EnterCount);
958                assertEquals(1, mChildState2ExitCount);
959                assertEquals(1, mParentState2EnterCount);
960                assertEquals(0, mParentState2ExitCount);
961                assertEquals(0, mChildState3EnterCount);
962                assertEquals(0, mChildState3ExitCount);
963                assertEquals(1, mChildState4EnterCount);
964                assertEquals(0, mChildState4ExitCount);
965                assertEquals(1, mChildState5EnterCount);
966                assertEquals(0, mChildState5ExitCount);
967
968                transitionTo(mChildState3);
969                return HANDLED;
970            }
971            @Override protected void exit() {
972                mChildState5ExitCount += 1;
973            }
974        }
975
976        @Override
977        protected void halting() {
978            synchronized (mThisSm) {
979                mThisSm.notifyAll();
980            }
981        }
982
983        private StateMachine5 mThisSm;
984        private ParentState1 mParentState1 = new ParentState1();
985        private ChildState1 mChildState1 = new ChildState1();
986        private ChildState2 mChildState2 = new ChildState2();
987        private ParentState2 mParentState2 = new ParentState2();
988        private ChildState3 mChildState3 = new ChildState3();
989        private ChildState4 mChildState4 = new ChildState4();
990        private ChildState5 mChildState5 = new ChildState5();
991
992        private int mParentState1EnterCount = 0;
993        private int mParentState1ExitCount = 0;
994        private int mChildState1EnterCount = 0;
995        private int mChildState1ExitCount = 0;
996        private int mChildState2EnterCount = 0;
997        private int mChildState2ExitCount = 0;
998        private int mParentState2EnterCount = 0;
999        private int mParentState2ExitCount = 0;
1000        private int mChildState3EnterCount = 0;
1001        private int mChildState3ExitCount = 0;
1002        private int mChildState4EnterCount = 0;
1003        private int mChildState4ExitCount = 0;
1004        private int mChildState5EnterCount = 0;
1005        private int mChildState5ExitCount = 0;
1006    }
1007
1008    @MediumTest
1009    public void testStateMachine5() throws Exception {
1010        StateMachine5 sm5 = new StateMachine5("sm5");
1011        sm5.start();
1012        if (sm5.isDbg()) Log.d(TAG, "testStateMachine5 E");
1013
1014        synchronized (sm5) {
1015            // Send 6 messages
1016            sm5.sendMessage(TEST_CMD_1);
1017            sm5.sendMessage(TEST_CMD_2);
1018            sm5.sendMessage(TEST_CMD_3);
1019            sm5.sendMessage(TEST_CMD_4);
1020            sm5.sendMessage(TEST_CMD_5);
1021            sm5.sendMessage(TEST_CMD_6);
1022
1023            try {
1024                // wait for the messages to be handled
1025                sm5.wait();
1026            } catch (InterruptedException e) {
1027                Log.e(TAG, "testStateMachine5: exception while waiting " + e.getMessage());
1028            }
1029        }
1030
1031
1032        assertTrue(sm5.getProcessedMessagesSize() == 6);
1033
1034        assertEquals(1, sm5.mParentState1EnterCount);
1035        assertEquals(1, sm5.mParentState1ExitCount);
1036        assertEquals(1, sm5.mChildState1EnterCount);
1037        assertEquals(1, sm5.mChildState1ExitCount);
1038        assertEquals(1, sm5.mChildState2EnterCount);
1039        assertEquals(1, sm5.mChildState2ExitCount);
1040        assertEquals(2, sm5.mParentState2EnterCount);
1041        assertEquals(2, sm5.mParentState2ExitCount);
1042        assertEquals(1, sm5.mChildState3EnterCount);
1043        assertEquals(1, sm5.mChildState3ExitCount);
1044        assertEquals(2, sm5.mChildState4EnterCount);
1045        assertEquals(2, sm5.mChildState4ExitCount);
1046        assertEquals(1, sm5.mChildState5EnterCount);
1047        assertEquals(1, sm5.mChildState5ExitCount);
1048
1049        ProcessedMessages.Info pmi;
1050        pmi = sm5.getProcessedMessage(0);
1051        assertEquals(TEST_CMD_1, pmi.getWhat());
1052        assertEquals(sm5.mChildState1, pmi.getState());
1053        assertEquals(sm5.mChildState1, pmi.getOriginalState());
1054
1055        pmi = sm5.getProcessedMessage(1);
1056        assertEquals(TEST_CMD_2, pmi.getWhat());
1057        assertEquals(sm5.mChildState2, pmi.getState());
1058        assertEquals(sm5.mChildState2, pmi.getOriginalState());
1059
1060        pmi = sm5.getProcessedMessage(2);
1061        assertEquals(TEST_CMD_3, pmi.getWhat());
1062        assertEquals(sm5.mChildState5, pmi.getState());
1063        assertEquals(sm5.mChildState5, pmi.getOriginalState());
1064
1065        pmi = sm5.getProcessedMessage(3);
1066        assertEquals(TEST_CMD_4, pmi.getWhat());
1067        assertEquals(sm5.mChildState3, pmi.getState());
1068        assertEquals(sm5.mChildState3, pmi.getOriginalState());
1069
1070        pmi = sm5.getProcessedMessage(4);
1071        assertEquals(TEST_CMD_5, pmi.getWhat());
1072        assertEquals(sm5.mChildState4, pmi.getState());
1073        assertEquals(sm5.mChildState4, pmi.getOriginalState());
1074
1075        pmi = sm5.getProcessedMessage(5);
1076        assertEquals(TEST_CMD_6, pmi.getWhat());
1077        assertEquals(sm5.mParentState2, pmi.getState());
1078        assertEquals(sm5.mParentState2, pmi.getOriginalState());
1079
1080        if (sm5.isDbg()) Log.d(TAG, "testStateMachine5 X");
1081    }
1082
1083    /**
1084     * Test that the initial state enter is invoked immediately
1085     * after construction and before any other messages arrive and that
1086     * sendMessageDelayed works.
1087     */
1088    class StateMachine6 extends HierarchicalStateMachine {
1089        StateMachine6(String name) {
1090            super(name);
1091            mThisSm = this;
1092            setDbg(DBG);
1093
1094            // Setup state machine with 1 state
1095            addState(mS1);
1096
1097            // Set the initial state
1098            setInitialState(mS1);
1099            if (DBG) Log.d(TAG, "StateMachine6: ctor X");
1100        }
1101
1102        class S1 extends HierarchicalState {
1103
1104            @Override protected void enter() {
1105                sendMessage(TEST_CMD_1);
1106            }
1107
1108            @Override protected boolean processMessage(Message message) {
1109                if (message.what == TEST_CMD_1) {
1110                    mArrivalTimeMsg1 = SystemClock.elapsedRealtime();
1111                } else if (message.what == TEST_CMD_2) {
1112                    mArrivalTimeMsg2 = SystemClock.elapsedRealtime();
1113                    transitionToHaltingState();
1114                }
1115                return HANDLED;
1116            }
1117
1118            @Override protected void exit() {
1119            }
1120        }
1121
1122        @Override
1123        protected void halting() {
1124            synchronized (mThisSm) {
1125                mThisSm.notifyAll();
1126            }
1127        }
1128
1129        private StateMachine6 mThisSm;
1130        private S1 mS1 = new S1();
1131
1132        private long mArrivalTimeMsg1;
1133        private long mArrivalTimeMsg2;
1134    }
1135
1136    @MediumTest
1137    public void testStateMachine6() throws Exception {
1138        long sentTimeMsg2;
1139        final int DELAY_TIME = 250;
1140        final int DELAY_FUDGE = 20;
1141
1142        StateMachine6 sm6 = new StateMachine6("sm6");
1143        sm6.start();
1144        if (sm6.isDbg()) Log.d(TAG, "testStateMachine6 E");
1145
1146        synchronized (sm6) {
1147            // Send a message
1148            sentTimeMsg2 = SystemClock.elapsedRealtime();
1149            sm6.sendMessageDelayed(TEST_CMD_2, DELAY_TIME);
1150
1151            try {
1152                // wait for the messages to be handled
1153                sm6.wait();
1154            } catch (InterruptedException e) {
1155                Log.e(TAG, "testStateMachine6: exception while waiting " + e.getMessage());
1156            }
1157        }
1158
1159        /**
1160         * TEST_CMD_1 was sent in enter and must always have been processed
1161         * immediately after construction and hence the arrival time difference
1162         * should always >= to the DELAY_TIME
1163         */
1164        long arrivalTimeDiff = sm6.mArrivalTimeMsg2 - sm6.mArrivalTimeMsg1;
1165        long expectedDelay = DELAY_TIME - DELAY_FUDGE;
1166        if (sm6.isDbg()) Log.d(TAG, "testStateMachine6: expect " + arrivalTimeDiff
1167                                    + " >= " + expectedDelay);
1168        assertTrue(arrivalTimeDiff >= expectedDelay);
1169
1170        if (sm6.isDbg()) Log.d(TAG, "testStateMachine6 X");
1171    }
1172
1173    /**
1174     * Test that enter is invoked immediately after exit. This validates
1175     * that enter can be used to send a watch dog message for its state.
1176     */
1177    class StateMachine7 extends HierarchicalStateMachine {
1178        private final int SM7_DELAY_TIME = 250;
1179
1180        StateMachine7(String name) {
1181            super(name);
1182            mThisSm = this;
1183            setDbg(DBG);
1184
1185            // Setup state machine with 1 state
1186            addState(mS1);
1187            addState(mS2);
1188
1189            // Set the initial state
1190            setInitialState(mS1);
1191            if (DBG) Log.d(TAG, "StateMachine7: ctor X");
1192        }
1193
1194        class S1 extends HierarchicalState {
1195            @Override protected boolean processMessage(Message message) {
1196                transitionTo(mS2);
1197                return HANDLED;
1198            }
1199            @Override protected void exit() {
1200                sendMessage(TEST_CMD_2);
1201            }
1202        }
1203
1204        class S2 extends HierarchicalState {
1205
1206            @Override protected void enter() {
1207                // Send a delayed message as a watch dog
1208                sendMessageDelayed(TEST_CMD_3, SM7_DELAY_TIME);
1209            }
1210
1211            @Override protected boolean processMessage(Message message) {
1212                if (message.what == TEST_CMD_2) {
1213                    mMsgCount += 1;
1214                    mArrivalTimeMsg2 = SystemClock.elapsedRealtime();
1215                } else if (message.what == TEST_CMD_3) {
1216                    mMsgCount += 1;
1217                    mArrivalTimeMsg3 = SystemClock.elapsedRealtime();
1218                }
1219
1220                if (mMsgCount == 2) {
1221                    transitionToHaltingState();
1222                }
1223                return HANDLED;
1224            }
1225
1226            @Override protected void exit() {
1227            }
1228        }
1229
1230        @Override
1231        protected void halting() {
1232            synchronized (mThisSm) {
1233                mThisSm.notifyAll();
1234            }
1235        }
1236
1237        private StateMachine7 mThisSm;
1238        private S1 mS1 = new S1();
1239        private S2 mS2 = new S2();
1240
1241        private int mMsgCount = 0;
1242        private long mArrivalTimeMsg2;
1243        private long mArrivalTimeMsg3;
1244    }
1245
1246    @MediumTest
1247    public void testStateMachine7() throws Exception {
1248        long sentTimeMsg2;
1249        final int SM7_DELAY_FUDGE = 20;
1250
1251        StateMachine7 sm7 = new StateMachine7("sm7");
1252        sm7.start();
1253        if (sm7.isDbg()) Log.d(TAG, "testStateMachine7 E");
1254
1255        synchronized (sm7) {
1256            // Send a message
1257            sentTimeMsg2 = SystemClock.elapsedRealtime();
1258            sm7.sendMessage(TEST_CMD_1);
1259
1260            try {
1261                // wait for the messages to be handled
1262                sm7.wait();
1263            } catch (InterruptedException e) {
1264                Log.e(TAG, "testStateMachine7: exception while waiting " + e.getMessage());
1265            }
1266        }
1267
1268        /**
1269         * TEST_CMD_3 was sent in S2.enter with a delay and must always have been
1270         * processed immediately after S1.exit. Since S1.exit sent TEST_CMD_2
1271         * without a delay the arrival time difference should always >= to SM7_DELAY_TIME.
1272         */
1273        long arrivalTimeDiff = sm7.mArrivalTimeMsg3 - sm7.mArrivalTimeMsg2;
1274        long expectedDelay = sm7.SM7_DELAY_TIME - SM7_DELAY_FUDGE;
1275        if (sm7.isDbg()) Log.d(TAG, "testStateMachine7: expect " + arrivalTimeDiff
1276                                    + " >= " + expectedDelay);
1277        assertTrue(arrivalTimeDiff >= expectedDelay);
1278
1279        if (sm7.isDbg()) Log.d(TAG, "testStateMachine7 X");
1280    }
1281
1282    /**
1283     * Test unhandledMessage.
1284     */
1285    class StateMachineUnhandledMessage extends HierarchicalStateMachine {
1286        StateMachineUnhandledMessage(String name) {
1287            super(name);
1288            mThisSm = this;
1289            setDbg(DBG);
1290
1291            // Setup state machine with 1 state
1292            addState(mS1);
1293
1294            // Set the initial state
1295            setInitialState(mS1);
1296        }
1297
1298        @Override protected void unhandledMessage(Message message) {
1299            mUnhandledMessageCount += 1;
1300        }
1301
1302        class S1 extends HierarchicalState {
1303            @Override protected boolean processMessage(Message message) {
1304                if (message.what == TEST_CMD_2) {
1305                    transitionToHaltingState();
1306                }
1307                return NOT_HANDLED;
1308            }
1309        }
1310
1311        @Override
1312        protected void halting() {
1313            synchronized (mThisSm) {
1314                mThisSm.notifyAll();
1315            }
1316        }
1317
1318        private StateMachineUnhandledMessage mThisSm;
1319        private int mUnhandledMessageCount;
1320        private S1 mS1 = new S1();
1321    }
1322
1323    @SmallTest
1324    public void testStateMachineUnhandledMessage() throws Exception {
1325
1326        StateMachineUnhandledMessage sm = new StateMachineUnhandledMessage("sm");
1327        sm.start();
1328        if (sm.isDbg()) Log.d(TAG, "testStateMachineUnhandledMessage E");
1329
1330        synchronized (sm) {
1331            // Send 2 messages
1332            for (int i = 1; i <= 2; i++) {
1333                sm.sendMessage(i);
1334            }
1335
1336            try {
1337                // wait for the messages to be handled
1338                sm.wait();
1339            } catch (InterruptedException e) {
1340                Log.e(TAG, "testStateMachineUnhandledMessage: exception while waiting "
1341                        + e.getMessage());
1342            }
1343        }
1344
1345        assertTrue(sm.getProcessedMessagesCount() == 2);
1346        assertEquals(2, sm.mUnhandledMessageCount);
1347
1348        if (sm.isDbg()) Log.d(TAG, "testStateMachineUnhandledMessage X");
1349    }
1350
1351    /**
1352     * Test state machines sharing the same thread/looper. Multiple instances
1353     * of the same state machine will be created. They will all share the
1354     * same thread and thus each can update <code>sharedCounter</code> which
1355     * will be used to notify testStateMachineSharedThread that the test is
1356     * complete.
1357     */
1358    class StateMachineSharedThread extends HierarchicalStateMachine {
1359        StateMachineSharedThread(String name, Looper looper, int maxCount) {
1360            super(name, looper);
1361            mMaxCount = maxCount;
1362            setDbg(DBG);
1363
1364            // Setup state machine with 1 state
1365            addState(mS1);
1366
1367            // Set the initial state
1368            setInitialState(mS1);
1369        }
1370
1371        class S1 extends HierarchicalState {
1372            @Override protected boolean processMessage(Message message) {
1373                if (message.what == TEST_CMD_4) {
1374                    transitionToHaltingState();
1375                }
1376                return HANDLED;
1377            }
1378        }
1379
1380        @Override
1381        protected void halting() {
1382            // Update the shared counter, which is OK since all state
1383            // machines are using the same thread.
1384            sharedCounter += 1;
1385            if (sharedCounter == mMaxCount) {
1386                synchronized (waitObject) {
1387                    waitObject.notifyAll();
1388                }
1389            }
1390        }
1391
1392        private int mMaxCount;
1393        private S1 mS1 = new S1();
1394    }
1395    private static int sharedCounter = 0;
1396    private static Object waitObject = new Object();
1397
1398    @MediumTest
1399    public void testStateMachineSharedThread() throws Exception {
1400        if (DBG) Log.d(TAG, "testStateMachineSharedThread E");
1401
1402        // Create and start the handler thread
1403        HandlerThread smThread = new HandlerThread("testStateMachineSharedThread");
1404        smThread.start();
1405
1406        // Create the state machines
1407        StateMachineSharedThread sms[] = new StateMachineSharedThread[10];
1408        for (int i = 0; i < sms.length; i++) {
1409            sms[i] = new StateMachineSharedThread("sm", smThread.getLooper(), sms.length);
1410            sms[i].start();
1411        }
1412
1413        synchronized (waitObject) {
1414            // Send messages to each of the state machines
1415            for (StateMachineSharedThread sm : sms) {
1416                for (int i = 1; i <= 4; i++) {
1417                    sm.sendMessage(i);
1418                }
1419            }
1420
1421            // Wait for the last state machine to notify its done
1422            try {
1423                waitObject.wait();
1424            } catch (InterruptedException e) {
1425                Log.e(TAG, "testStateMachineSharedThread: exception while waiting "
1426                        + e.getMessage());
1427            }
1428        }
1429
1430        for (StateMachineSharedThread sm : sms) {
1431            assertTrue(sm.getProcessedMessagesCount() == 4);
1432            for (int i = 0; i < sm.getProcessedMessagesCount(); i++) {
1433                ProcessedMessages.Info pmi = sm.getProcessedMessage(i);
1434                assertEquals(i+1, pmi.getWhat());
1435                assertEquals(sm.mS1, pmi.getState());
1436                assertEquals(sm.mS1, pmi.getOriginalState());
1437            }
1438        }
1439
1440        if (DBG) Log.d(TAG, "testStateMachineSharedThread X");
1441    }
1442
1443    @MediumTest
1444    public void testHsm1() throws Exception {
1445        if (DBG) Log.d(TAG, "testHsm1 E");
1446
1447        Hsm1 sm = Hsm1.makeHsm1();
1448
1449        // Send messages
1450        sm.sendMessage(Hsm1.CMD_1);
1451        sm.sendMessage(Hsm1.CMD_2);
1452
1453        synchronized (sm) {
1454            // Wait for the last state machine to notify its done
1455            try {
1456                sm.wait();
1457            } catch (InterruptedException e) {
1458                Log.e(TAG, "testHsm1: exception while waiting " + e.getMessage());
1459            }
1460        }
1461
1462        assertEquals(7, sm.getProcessedMessagesCount());
1463        ProcessedMessages.Info pmi = sm.getProcessedMessage(0);
1464        assertEquals(Hsm1.CMD_1, pmi.getWhat());
1465        assertEquals(sm.mS1, pmi.getState());
1466        assertEquals(sm.mS1, pmi.getOriginalState());
1467
1468        pmi = sm.getProcessedMessage(1);
1469        assertEquals(Hsm1.CMD_2, pmi.getWhat());
1470        assertEquals(sm.mP1, pmi.getState());
1471        assertEquals(sm.mS1, pmi.getOriginalState());
1472
1473        pmi = sm.getProcessedMessage(2);
1474        assertEquals(Hsm1.CMD_2, pmi.getWhat());
1475        assertEquals(sm.mS2, pmi.getState());
1476        assertEquals(sm.mS2, pmi.getOriginalState());
1477
1478        pmi = sm.getProcessedMessage(3);
1479        assertEquals(Hsm1.CMD_3, pmi.getWhat());
1480        assertEquals(sm.mS2, pmi.getState());
1481        assertEquals(sm.mS2, pmi.getOriginalState());
1482
1483        pmi = sm.getProcessedMessage(4);
1484        assertEquals(Hsm1.CMD_3, pmi.getWhat());
1485        assertEquals(sm.mP2, pmi.getState());
1486        assertEquals(sm.mP2, pmi.getOriginalState());
1487
1488        pmi = sm.getProcessedMessage(5);
1489        assertEquals(Hsm1.CMD_4, pmi.getWhat());
1490        assertEquals(sm.mP2, pmi.getState());
1491        assertEquals(sm.mP2, pmi.getOriginalState());
1492
1493        pmi = sm.getProcessedMessage(6);
1494        assertEquals(Hsm1.CMD_5, pmi.getWhat());
1495        assertEquals(sm.mP2, pmi.getState());
1496        assertEquals(sm.mP2, pmi.getOriginalState());
1497
1498        if (DBG) Log.d(TAG, "testStateMachineSharedThread X");
1499    }
1500}
1501
1502class Hsm1 extends HierarchicalStateMachine {
1503    private static final String TAG = "hsm1";
1504
1505    public static final int CMD_1 = 1;
1506    public static final int CMD_2 = 2;
1507    public static final int CMD_3 = 3;
1508    public static final int CMD_4 = 4;
1509    public static final int CMD_5 = 5;
1510
1511    public static Hsm1 makeHsm1() {
1512        Log.d(TAG, "makeHsm1 E");
1513        Hsm1 sm = new Hsm1("hsm1");
1514        sm.start();
1515        Log.d(TAG, "makeHsm1 X");
1516        return sm;
1517    }
1518
1519    Hsm1(String name) {
1520        super(name);
1521        Log.d(TAG, "ctor E");
1522
1523        // Add states, use indentation to show hierarchy
1524        addState(mP1);
1525            addState(mS1, mP1);
1526            addState(mS2, mP1);
1527        addState(mP2);
1528
1529        // Set the initial state
1530        setInitialState(mS1);
1531        Log.d(TAG, "ctor X");
1532    }
1533
1534    class P1 extends HierarchicalState {
1535        @Override protected void enter() {
1536            Log.d(TAG, "P1.enter");
1537        }
1538        @Override protected boolean processMessage(Message message) {
1539            boolean retVal;
1540            Log.d(TAG, "P1.processMessage what=" + message.what);
1541            switch(message.what) {
1542            case CMD_2:
1543                // CMD_2 will arrive in mS2 before CMD_3
1544                sendMessage(CMD_3);
1545                deferMessage(message);
1546                transitionTo(mS2);
1547                retVal = true;
1548                break;
1549            default:
1550                // Any message we don't understand in this state invokes unhandledMessage
1551                retVal = false;
1552                break;
1553            }
1554            return retVal;
1555        }
1556        @Override protected void exit() {
1557            Log.d(TAG, "P1.exit");
1558        }
1559    }
1560
1561    class S1 extends HierarchicalState {
1562        @Override protected void enter() {
1563            Log.d(TAG, "S1.enter");
1564        }
1565        @Override protected boolean processMessage(Message message) {
1566            Log.d(TAG, "S1.processMessage what=" + message.what);
1567            if (message.what == CMD_1) {
1568                // Transition to ourself to show that enter/exit is called
1569                transitionTo(mS1);
1570                return HANDLED;
1571            } else {
1572                // Let parent process all other messages
1573                return NOT_HANDLED;
1574            }
1575        }
1576        @Override protected void exit() {
1577            Log.d(TAG, "S1.exit");
1578        }
1579    }
1580
1581    class S2 extends HierarchicalState {
1582        @Override protected void enter() {
1583            Log.d(TAG, "S2.enter");
1584        }
1585        @Override protected boolean processMessage(Message message) {
1586            boolean retVal;
1587            Log.d(TAG, "S2.processMessage what=" + message.what);
1588            switch(message.what) {
1589            case(CMD_2):
1590                sendMessage(CMD_4);
1591                retVal = true;
1592                break;
1593            case(CMD_3):
1594                deferMessage(message);
1595                transitionTo(mP2);
1596                retVal = true;
1597                break;
1598            default:
1599                retVal = false;
1600                break;
1601            }
1602            return retVal;
1603        }
1604        @Override protected void exit() {
1605            Log.d(TAG, "S2.exit");
1606        }
1607    }
1608
1609    class P2 extends HierarchicalState {
1610        @Override protected void enter() {
1611            Log.d(TAG, "P2.enter");
1612            sendMessage(CMD_5);
1613        }
1614        @Override protected boolean processMessage(Message message) {
1615            Log.d(TAG, "P2.processMessage what=" + message.what);
1616            switch(message.what) {
1617            case(CMD_3):
1618                break;
1619            case(CMD_4):
1620                break;
1621            case(CMD_5):
1622                transitionToHaltingState();
1623                break;
1624            }
1625            return HANDLED;
1626        }
1627        @Override protected void exit() {
1628            Log.d(TAG, "P2.exit");
1629        }
1630    }
1631
1632    @Override
1633    protected void halting() {
1634        Log.d(TAG, "halting");
1635        synchronized (this) {
1636            this.notifyAll();
1637        }
1638    }
1639
1640    P1 mP1 = new P1();
1641    S1 mS1 = new S1();
1642    S2 mS2 = new S2();
1643    P2 mP2 = new P2();
1644}
1645