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