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