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