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