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