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