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