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