• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /**
2  * Copyright (C) 2009 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.internal.util;
18 
19 import android.compat.annotation.UnsupportedAppUsage;
20 import android.os.Build;
21 import android.os.Handler;
22 import android.os.HandlerThread;
23 import android.os.Looper;
24 import android.os.Message;
25 import android.text.TextUtils;
26 import android.util.Log;
27 
28 import com.android.internal.annotations.VisibleForTesting;
29 
30 import java.io.FileDescriptor;
31 import java.io.PrintWriter;
32 import java.util.ArrayList;
33 import java.util.Calendar;
34 import java.util.Collection;
35 import java.util.HashMap;
36 import java.util.Iterator;
37 import java.util.Vector;
38 
39 /**
40  * {@hide}
41  *
42  * <p>The state machine defined here is a hierarchical state machine which processes messages
43  * and can have states arranged hierarchically.</p>
44  *
45  * <p>A state is a <code>State</code> object and must implement
46  * <code>processMessage</code> and optionally <code>enter/exit/getName</code>.
47  * The enter/exit methods are equivalent to the construction and destruction
48  * in Object Oriented programming and are used to perform initialization and
49  * cleanup of the state respectively. The <code>getName</code> method returns the
50  * name of the state; the default implementation returns the class name. It may be
51  * desirable to have <code>getName</code> return the state instance name instead,
52  * in particular if a particular state class has multiple instances.</p>
53  *
54  * <p>When a state machine is created, <code>addState</code> is used to build the
55  * hierarchy and <code>setInitialState</code> is used to identify which of these
56  * is the initial state. After construction the programmer calls <code>start</code>
57  * which initializes and starts the state machine. The first action the StateMachine
58  * is to the invoke <code>enter</code> for all of the initial state's hierarchy,
59  * starting at its eldest parent. The calls to enter will be done in the context
60  * of the StateMachine's Handler, not in the context of the call to start, and they
61  * will be invoked before any messages are processed. For example, given the simple
62  * state machine below, mP1.enter will be invoked and then mS1.enter. Finally,
63  * messages sent to the state machine will be processed by the current state;
64  * in our simple state machine below that would initially be mS1.processMessage.</p>
65 <pre>
66         mP1
67        /   \
68       mS2   mS1 ----&gt; initial state
69 </pre>
70  * <p>After the state machine is created and started, messages are sent to a state
71  * machine using <code>sendMessage</code> and the messages are created using
72  * <code>obtainMessage</code>. When the state machine receives a message the
73  * current state's <code>processMessage</code> is invoked. In the above example
74  * mS1.processMessage will be invoked first. The state may use <code>transitionTo</code>
75  * to change the current state to a new state.</p>
76  *
77  * <p>Each state in the state machine may have a zero or one parent states. If
78  * a child state is unable to handle a message it may have the message processed
79  * by its parent by returning false or NOT_HANDLED. If a message is not handled by
80  * a child state or any of its ancestors, <code>unhandledMessage</code> will be invoked
81  * to give one last chance for the state machine to process the message.</p>
82  *
83  * <p>When all processing is completed a state machine may choose to call
84  * <code>transitionToHaltingState</code>. When the current <code>processingMessage</code>
85  * returns the state machine will transfer to an internal <code>HaltingState</code>
86  * and invoke <code>halting</code>. Any message subsequently received by the state
87  * machine will cause <code>haltedProcessMessage</code> to be invoked.</p>
88  *
89  * <p>If it is desirable to completely stop the state machine call <code>quit</code> or
90  * <code>quitNow</code>. These will call <code>exit</code> of the current state and its parents,
91  * call <code>onQuitting</code> and then exit Thread/Loopers.</p>
92  *
93  * <p>In addition to <code>processMessage</code> each <code>State</code> has
94  * an <code>enter</code> method and <code>exit</code> method which may be overridden.</p>
95  *
96  * <p>Since the states are arranged in a hierarchy transitioning to a new state
97  * causes current states to be exited and new states to be entered. To determine
98  * the list of states to be entered/exited the common parent closest to
99  * the current state is found. We then exit from the current state and its
100  * parent's up to but not including the common parent state and then enter all
101  * of the new states below the common parent down to the destination state.
102  * If there is no common parent all states are exited and then the new states
103  * are entered.</p>
104  *
105  * <p>Two other methods that states can use are <code>deferMessage</code> and
106  * <code>sendMessageAtFrontOfQueue</code>. The <code>sendMessageAtFrontOfQueue</code> sends
107  * a message but places it on the front of the queue rather than the back. The
108  * <code>deferMessage</code> causes the message to be saved on a list until a
109  * transition is made to a new state. At which time all of the deferred messages
110  * will be put on the front of the state machine queue with the oldest message
111  * at the front. These will then be processed by the new current state before
112  * any other messages that are on the queue or might be added later. Both of
113  * these are protected and may only be invoked from within a state machine.</p>
114  *
115  * <p>To illustrate some of these properties we'll use state machine with an 8
116  * state hierarchy:</p>
117 <pre>
118           mP0
119          /   \
120         mP1   mS0
121        /   \
122       mS2   mS1
123      /  \    \
124     mS3  mS4  mS5  ---&gt; initial state
125 </pre>
126  * <p>After starting mS5 the list of active states is mP0, mP1, mS1 and mS5.
127  * So the order of calling processMessage when a message is received is mS5,
128  * mS1, mP1, mP0 assuming each processMessage indicates it can't handle this
129  * message by returning false or NOT_HANDLED.</p>
130  *
131  * <p>Now assume mS5.processMessage receives a message it can handle, and during
132  * the handling determines the machine should change states. It could call
133  * transitionTo(mS4) and return true or HANDLED. Immediately after returning from
134  * processMessage the state machine runtime will find the common parent,
135  * which is mP1. It will then call mS5.exit, mS1.exit, mS2.enter and then
136  * mS4.enter. The new list of active states is mP0, mP1, mS2 and mS4. So
137  * when the next message is received mS4.processMessage will be invoked.</p>
138  *
139  * <p>Now for some concrete examples, here is the canonical HelloWorld as a state machine.
140  * It responds with "Hello World" being printed to the log for every message.</p>
141 <pre>
142 class HelloWorld extends StateMachine {
143     HelloWorld(String name) {
144         super(name);
145         addState(mState1);
146         setInitialState(mState1);
147     }
148 
149     public static HelloWorld makeHelloWorld() {
150         HelloWorld hw = new HelloWorld("hw");
151         hw.start();
152         return hw;
153     }
154 
155     class State1 extends State {
156         &#64;Override public boolean processMessage(Message message) {
157             log("Hello World");
158             return HANDLED;
159         }
160     }
161     State1 mState1 = new State1();
162 }
163 
164 void testHelloWorld() {
165     HelloWorld hw = makeHelloWorld();
166     hw.sendMessage(hw.obtainMessage());
167 }
168 </pre>
169  * <p>A more interesting state machine is one with four states
170  * with two independent parent states.</p>
171 <pre>
172         mP1      mP2
173        /   \
174       mS2   mS1
175 </pre>
176  * <p>Here is a description of this state machine using pseudo code.</p>
177  <pre>
178 state mP1 {
179      enter { log("mP1.enter"); }
180      exit { log("mP1.exit");  }
181      on msg {
182          CMD_2 {
183              send(CMD_3);
184              defer(msg);
185              transitionTo(mS2);
186              return HANDLED;
187          }
188          return NOT_HANDLED;
189      }
190 }
191 
192 INITIAL
193 state mS1 parent mP1 {
194      enter { log("mS1.enter"); }
195      exit  { log("mS1.exit");  }
196      on msg {
197          CMD_1 {
198              transitionTo(mS1);
199              return HANDLED;
200          }
201          return NOT_HANDLED;
202      }
203 }
204 
205 state mS2 parent mP1 {
206      enter { log("mS2.enter"); }
207      exit  { log("mS2.exit");  }
208      on msg {
209          CMD_2 {
210              send(CMD_4);
211              return HANDLED;
212          }
213          CMD_3 {
214              defer(msg);
215              transitionTo(mP2);
216              return HANDLED;
217          }
218          return NOT_HANDLED;
219      }
220 }
221 
222 state mP2 {
223      enter {
224          log("mP2.enter");
225          send(CMD_5);
226      }
227      exit { log("mP2.exit"); }
228      on msg {
229          CMD_3, CMD_4 { return HANDLED; }
230          CMD_5 {
231              transitionTo(HaltingState);
232              return HANDLED;
233          }
234          return NOT_HANDLED;
235      }
236 }
237 </pre>
238  * <p>The implementation is below and also in StateMachineTest:</p>
239 <pre>
240 class Hsm1 extends StateMachine {
241     public static final int CMD_1 = 1;
242     public static final int CMD_2 = 2;
243     public static final int CMD_3 = 3;
244     public static final int CMD_4 = 4;
245     public static final int CMD_5 = 5;
246 
247     public static Hsm1 makeHsm1() {
248         log("makeHsm1 E");
249         Hsm1 sm = new Hsm1("hsm1");
250         sm.start();
251         log("makeHsm1 X");
252         return sm;
253     }
254 
255     Hsm1(String name) {
256         super(name);
257         log("ctor E");
258 
259         // Add states, use indentation to show hierarchy
260         addState(mP1);
261             addState(mS1, mP1);
262             addState(mS2, mP1);
263         addState(mP2);
264 
265         // Set the initial state
266         setInitialState(mS1);
267         log("ctor X");
268     }
269 
270     class P1 extends State {
271         &#64;Override public void enter() {
272             log("mP1.enter");
273         }
274         &#64;Override public boolean processMessage(Message message) {
275             boolean retVal;
276             log("mP1.processMessage what=" + message.what);
277             switch(message.what) {
278             case CMD_2:
279                 // CMD_2 will arrive in mS2 before CMD_3
280                 sendMessage(obtainMessage(CMD_3));
281                 deferMessage(message);
282                 transitionTo(mS2);
283                 retVal = HANDLED;
284                 break;
285             default:
286                 // Any message we don't understand in this state invokes unhandledMessage
287                 retVal = NOT_HANDLED;
288                 break;
289             }
290             return retVal;
291         }
292         &#64;Override public void exit() {
293             log("mP1.exit");
294         }
295     }
296 
297     class S1 extends State {
298         &#64;Override public void enter() {
299             log("mS1.enter");
300         }
301         &#64;Override public boolean processMessage(Message message) {
302             log("S1.processMessage what=" + message.what);
303             if (message.what == CMD_1) {
304                 // Transition to ourself to show that enter/exit is called
305                 transitionTo(mS1);
306                 return HANDLED;
307             } else {
308                 // Let parent process all other messages
309                 return NOT_HANDLED;
310             }
311         }
312         &#64;Override public void exit() {
313             log("mS1.exit");
314         }
315     }
316 
317     class S2 extends State {
318         &#64;Override public void enter() {
319             log("mS2.enter");
320         }
321         &#64;Override public boolean processMessage(Message message) {
322             boolean retVal;
323             log("mS2.processMessage what=" + message.what);
324             switch(message.what) {
325             case(CMD_2):
326                 sendMessage(obtainMessage(CMD_4));
327                 retVal = HANDLED;
328                 break;
329             case(CMD_3):
330                 deferMessage(message);
331                 transitionTo(mP2);
332                 retVal = HANDLED;
333                 break;
334             default:
335                 retVal = NOT_HANDLED;
336                 break;
337             }
338             return retVal;
339         }
340         &#64;Override public void exit() {
341             log("mS2.exit");
342         }
343     }
344 
345     class P2 extends State {
346         &#64;Override public void enter() {
347             log("mP2.enter");
348             sendMessage(obtainMessage(CMD_5));
349         }
350         &#64;Override public boolean processMessage(Message message) {
351             log("P2.processMessage what=" + message.what);
352             switch(message.what) {
353             case(CMD_3):
354                 break;
355             case(CMD_4):
356                 break;
357             case(CMD_5):
358                 transitionToHaltingState();
359                 break;
360             }
361             return HANDLED;
362         }
363         &#64;Override public void exit() {
364             log("mP2.exit");
365         }
366     }
367 
368     &#64;Override
369     void onHalting() {
370         log("halting");
371         synchronized (this) {
372             this.notifyAll();
373         }
374     }
375 
376     P1 mP1 = new P1();
377     S1 mS1 = new S1();
378     S2 mS2 = new S2();
379     P2 mP2 = new P2();
380 }
381 </pre>
382  * <p>If this is executed by sending two messages CMD_1 and CMD_2
383  * (Note the synchronize is only needed because we use hsm.wait())</p>
384 <pre>
385 Hsm1 hsm = makeHsm1();
386 synchronize(hsm) {
387      hsm.sendMessage(obtainMessage(hsm.CMD_1));
388      hsm.sendMessage(obtainMessage(hsm.CMD_2));
389      try {
390           // wait for the messages to be handled
391           hsm.wait();
392      } catch (InterruptedException e) {
393           loge("exception while waiting " + e.getMessage());
394      }
395 }
396 </pre>
397  * <p>The output is:</p>
398 <pre>
399 D/hsm1    ( 1999): makeHsm1 E
400 D/hsm1    ( 1999): ctor E
401 D/hsm1    ( 1999): ctor X
402 D/hsm1    ( 1999): mP1.enter
403 D/hsm1    ( 1999): mS1.enter
404 D/hsm1    ( 1999): makeHsm1 X
405 D/hsm1    ( 1999): mS1.processMessage what=1
406 D/hsm1    ( 1999): mS1.exit
407 D/hsm1    ( 1999): mS1.enter
408 D/hsm1    ( 1999): mS1.processMessage what=2
409 D/hsm1    ( 1999): mP1.processMessage what=2
410 D/hsm1    ( 1999): mS1.exit
411 D/hsm1    ( 1999): mS2.enter
412 D/hsm1    ( 1999): mS2.processMessage what=2
413 D/hsm1    ( 1999): mS2.processMessage what=3
414 D/hsm1    ( 1999): mS2.exit
415 D/hsm1    ( 1999): mP1.exit
416 D/hsm1    ( 1999): mP2.enter
417 D/hsm1    ( 1999): mP2.processMessage what=3
418 D/hsm1    ( 1999): mP2.processMessage what=4
419 D/hsm1    ( 1999): mP2.processMessage what=5
420 D/hsm1    ( 1999): mP2.exit
421 D/hsm1    ( 1999): halting
422 </pre>
423  */
424 public class StateMachine {
425     // Name of the state machine and used as logging tag
426     private String mName;
427 
428     /** Message.what value when quitting */
429     private static final int SM_QUIT_CMD = -1;
430 
431     /** Message.what value when initializing */
432     private static final int SM_INIT_CMD = -2;
433 
434     /**
435      * Convenience constant that maybe returned by processMessage
436      * to indicate the message was processed and is not to be
437      * processed by parent states
438      */
439     public static final boolean HANDLED = true;
440 
441     /**
442      * Convenience constant that maybe returned by processMessage
443      * to indicate the message was NOT processed and is to be
444      * processed by parent states
445      */
446     public static final boolean NOT_HANDLED = false;
447 
448     /**
449      * StateMachine logging record.
450      * {@hide}
451      */
452     public static class LogRec {
453         private StateMachine mSm;
454         private long mTime;
455         private int mWhat;
456         private String mInfo;
457         private IState mState;
458         private IState mOrgState;
459         private IState mDstState;
460 
461         /**
462          * Constructor
463          *
464          * @param msg
465          * @param state the state which handled the message
466          * @param orgState is the first state the received the message but
467          * did not processes the message.
468          * @param transToState is the state that was transitioned to after the message was
469          * processed.
470          */
LogRec(StateMachine sm, Message msg, String info, IState state, IState orgState, IState transToState)471         LogRec(StateMachine sm, Message msg, String info, IState state, IState orgState,
472                 IState transToState) {
473             update(sm, msg, info, state, orgState, transToState);
474         }
475 
476         /**
477          * Update the information in the record.
478          * @param state that handled the message
479          * @param orgState is the first state the received the message
480          * @param dstState is the state that was the transition target when logging
481          */
update(StateMachine sm, Message msg, String info, IState state, IState orgState, IState dstState)482         public void update(StateMachine sm, Message msg, String info, IState state, IState orgState,
483                 IState dstState) {
484             mSm = sm;
485             mTime = System.currentTimeMillis();
486             mWhat = (msg != null) ? msg.what : 0;
487             mInfo = info;
488             mState = state;
489             mOrgState = orgState;
490             mDstState = dstState;
491         }
492 
493         /**
494          * @return time stamp
495          */
getTime()496         public long getTime() {
497             return mTime;
498         }
499 
500         /**
501          * @return msg.what
502          */
getWhat()503         public long getWhat() {
504             return mWhat;
505         }
506 
507         /**
508          * @return the command that was executing
509          */
getInfo()510         public String getInfo() {
511             return mInfo;
512         }
513 
514         /**
515          * @return the state that handled this message
516          */
getState()517         public IState getState() {
518             return mState;
519         }
520 
521         /**
522          * @return the state destination state if a transition is occurring or null if none.
523          */
getDestState()524         public IState getDestState() {
525             return mDstState;
526         }
527 
528         /**
529          * @return the original state that received the message.
530          */
getOriginalState()531         public IState getOriginalState() {
532             return mOrgState;
533         }
534 
535         @Override
toString()536         public String toString() {
537             StringBuilder sb = new StringBuilder();
538             sb.append("time=");
539             Calendar c = Calendar.getInstance();
540             c.setTimeInMillis(mTime);
541             sb.append(String.format("%tm-%td %tH:%tM:%tS.%tL", c, c, c, c, c, c));
542             sb.append(" processed=");
543             sb.append(mState == null ? "<null>" : mState.getName());
544             sb.append(" org=");
545             sb.append(mOrgState == null ? "<null>" : mOrgState.getName());
546             sb.append(" dest=");
547             sb.append(mDstState == null ? "<null>" : mDstState.getName());
548             sb.append(" what=");
549             String what = mSm != null ? mSm.getWhatToString(mWhat) : "";
550             if (TextUtils.isEmpty(what)) {
551                 sb.append(mWhat);
552                 sb.append("(0x");
553                 sb.append(Integer.toHexString(mWhat));
554                 sb.append(")");
555             } else {
556                 sb.append(what);
557             }
558             if (!TextUtils.isEmpty(mInfo)) {
559                 sb.append(" ");
560                 sb.append(mInfo);
561             }
562             return sb.toString();
563         }
564     }
565 
566     /**
567      * A list of log records including messages recently processed by the state machine.
568      *
569      * The class maintains a list of log records including messages
570      * recently processed. The list is finite and may be set in the
571      * constructor or by calling setSize. The public interface also
572      * includes size which returns the number of recent records,
573      * count which is the number of records processed since the
574      * the last setSize, get which returns a record and
575      * add which adds a record.
576      */
577     private static class LogRecords {
578 
579         private static final int DEFAULT_SIZE = 20;
580 
581         private Vector<LogRec> mLogRecVector = new Vector<LogRec>();
582         private int mMaxSize = DEFAULT_SIZE;
583         private int mOldestIndex = 0;
584         private int mCount = 0;
585         private boolean mLogOnlyTransitions = false;
586 
587         /**
588          * private constructor use add
589          */
LogRecords()590         private LogRecords() {
591         }
592 
593         /**
594          * Set size of messages to maintain and clears all current records.
595          *
596          * @param maxSize number of records to maintain at anyone time.
597         */
setSize(int maxSize)598         synchronized void setSize(int maxSize) {
599             // TODO: once b/28217358 is fixed, add unit tests  to verify that these variables are
600             // cleared after calling this method, and that subsequent calls to get() function as
601             // expected.
602             mMaxSize = maxSize;
603             mOldestIndex = 0;
604             mCount = 0;
605             mLogRecVector.clear();
606         }
607 
setLogOnlyTransitions(boolean enable)608         synchronized void setLogOnlyTransitions(boolean enable) {
609             mLogOnlyTransitions = enable;
610         }
611 
logOnlyTransitions()612         synchronized boolean logOnlyTransitions() {
613             return mLogOnlyTransitions;
614         }
615 
616         /**
617          * @return the number of recent records.
618          */
size()619         synchronized int size() {
620             return mLogRecVector.size();
621         }
622 
623         /**
624          * @return the total number of records processed since size was set.
625          */
count()626         synchronized int count() {
627             return mCount;
628         }
629 
630         /**
631          * Clear the list of records.
632          */
cleanup()633         synchronized void cleanup() {
634             mLogRecVector.clear();
635         }
636 
637         /**
638          * @return the information on a particular record. 0 is the oldest
639          * record and size()-1 is the newest record. If the index is to
640          * large null is returned.
641          */
get(int index)642         synchronized LogRec get(int index) {
643             int nextIndex = mOldestIndex + index;
644             if (nextIndex >= mMaxSize) {
645                 nextIndex -= mMaxSize;
646             }
647             if (nextIndex >= size()) {
648                 return null;
649             } else {
650                 return mLogRecVector.get(nextIndex);
651             }
652         }
653 
654         /**
655          * Add a processed message.
656          *
657          * @param msg
658          * @param messageInfo to be stored
659          * @param state that handled the message
660          * @param orgState is the first state the received the message but
661          * did not processes the message.
662          * @param transToState is the state that was transitioned to after the message was
663          * processed.
664          *
665          */
add(StateMachine sm, Message msg, String messageInfo, IState state, IState orgState, IState transToState)666         synchronized void add(StateMachine sm, Message msg, String messageInfo, IState state,
667                 IState orgState, IState transToState) {
668             mCount += 1;
669             if (mLogRecVector.size() < mMaxSize) {
670                 mLogRecVector.add(new LogRec(sm, msg, messageInfo, state, orgState, transToState));
671             } else {
672                 LogRec pmi = mLogRecVector.get(mOldestIndex);
673                 mOldestIndex += 1;
674                 if (mOldestIndex >= mMaxSize) {
675                     mOldestIndex = 0;
676                 }
677                 pmi.update(sm, msg, messageInfo, state, orgState, transToState);
678             }
679         }
680     }
681 
682     private static class SmHandler extends Handler {
683 
684         /** true if StateMachine has quit */
685         private boolean mHasQuit = false;
686 
687         /** The debug flag */
688         private boolean mDbg = false;
689 
690         /** The SmHandler object, identifies that message is internal */
691         private static final Object mSmHandlerObj = new Object();
692 
693         /** The current message */
694         private Message mMsg;
695 
696         /** A list of log records including messages this state machine has processed */
697         private final LogRecords mLogRecords = new LogRecords();
698 
699         /** true if construction of the state machine has not been completed */
700         private boolean mIsConstructionCompleted;
701 
702         /** Stack used to manage the current hierarchy of states */
703         private StateInfo[] mStateStack;
704 
705         /** Top of mStateStack */
706         private int mStateStackTopIndex = -1;
707 
708         /** A temporary stack used to manage the state stack */
709         private StateInfo[] mTempStateStack;
710 
711         /** The top of the mTempStateStack */
712         private int mTempStateStackCount;
713 
714         /** State used when state machine is halted */
715         private final HaltingState mHaltingState = new HaltingState();
716 
717         /** State used when state machine is quitting */
718         private final QuittingState mQuittingState = new QuittingState();
719 
720         /** Reference to the StateMachine */
721         private StateMachine mSm;
722 
723         /**
724          * Information about a state.
725          * Used to maintain the hierarchy.
726          */
727         private static class StateInfo {
728             /** The state */
729             final State state;
730 
731             /** The parent of this state, null if there is no parent */
732             final StateInfo parentStateInfo;
733 
734             /** True when the state has been entered and on the stack */
735             // Note that this can be initialized on a different thread than it's used as long
736             // as it's only used on one thread. The reason is that it's initialized to false,
737             // which is also the default value for a boolean, so if the member is seen uninitialized
738             // then it's seen with the default value which is also false.
739             boolean active = false;
740 
StateInfo(final State state, final StateInfo parent)741             StateInfo(final State state, final StateInfo parent) {
742                 this.state = state;
743                 this.parentStateInfo = parent;
744             }
745 
746             /**
747              * Convert StateInfo to string
748              */
749             @Override
toString()750             public String toString() {
751                 return "state=" + state.getName() + ",active=" + active + ",parent="
752                         + ((parentStateInfo == null) ? "null" : parentStateInfo.state.getName());
753             }
754         }
755 
756         /** The map of all of the states in the state machine */
757         private final HashMap<State, StateInfo> mStateInfo = new HashMap<>();
758 
759         /** The initial state that will process the first message */
760         private State mInitialState;
761 
762         /** The destination state when transitionTo has been invoked */
763         private State mDestState;
764 
765         /**
766          * Indicates if a transition is in progress
767          *
768          * This will be true for all calls of State.exit and all calls of State.enter except for the
769          * last enter call for the current destination state.
770          */
771         private boolean mTransitionInProgress = false;
772 
773         /** The list of deferred messages */
774         private final ArrayList<Message> mDeferredMessages = new ArrayList<>();
775 
776         /**
777          * State entered when transitionToHaltingState is called.
778          */
779         private class HaltingState extends State {
780             @Override
processMessage(Message msg)781             public boolean processMessage(Message msg) {
782                 mSm.haltedProcessMessage(msg);
783                 return true;
784             }
785         }
786 
787         /**
788          * State entered when a valid quit message is handled.
789          */
790         private static class QuittingState extends State {
791             @Override
processMessage(Message msg)792             public boolean processMessage(Message msg) {
793                 return NOT_HANDLED;
794             }
795         }
796 
797         /**
798          * Handle messages sent to the state machine by calling
799          * the current state's processMessage. It also handles
800          * the enter/exit calls and placing any deferred messages
801          * back onto the queue when transitioning to a new state.
802          */
803         @Override
handleMessage(Message msg)804         public final void handleMessage(Message msg) {
805             if (!mHasQuit) {
806                 if (mSm != null && msg.what != SM_INIT_CMD && msg.what != SM_QUIT_CMD) {
807                     mSm.onPreHandleMessage(msg);
808                 }
809 
810                 if (mDbg) mSm.log("handleMessage: E msg.what=" + msg.what);
811 
812                 // Save the current message
813                 /* Copy the "msg" to "mMsg" as "msg" will be recycled */
814                 mMsg = obtainMessage();
815                 mMsg.copyFrom(msg);
816                 // State that processed the message
817                 State msgProcessedState = null;
818                 if (mIsConstructionCompleted || (msg.what == SM_QUIT_CMD)) {
819                     // Normal path
820                     msgProcessedState = processMsg(msg);
821                 } else if (msg.what == SM_INIT_CMD && msg.obj == mSmHandlerObj) {
822                     // Initial one time path.
823                     mIsConstructionCompleted = true;
824                     invokeEnterMethods(0);
825                 } else {
826                     throw new RuntimeException("StateMachine.handleMessage: "
827                             + "The start method not called, received msg: " + msg);
828                 }
829                 performTransitions(msgProcessedState, msg);
830 
831                 // We need to check if mSm == null here as we could be quitting.
832                 if (mDbg && mSm != null) mSm.log("handleMessage: X");
833 
834                 if (mSm != null && msg.what != SM_INIT_CMD && msg.what != SM_QUIT_CMD) {
835                     mSm.onPostHandleMessage(msg);
836                 }
837             }
838         }
839 
840         /**
841          * Do any transitions
842          * @param msgProcessedState is the state that processed the message
843          */
performTransitions(State msgProcessedState, Message msg)844         private void performTransitions(State msgProcessedState, Message msg) {
845             /*
846              * If transitionTo has been called, exit and then enter
847              * the appropriate states. We loop on this to allow
848              * enter and exit methods to use transitionTo.
849              */
850             final State orgState = mStateStack[mStateStackTopIndex].state;
851 
852             /*
853              * Record whether message needs to be logged before we transition and
854              * and we won't log special messages SM_INIT_CMD or SM_QUIT_CMD which
855              * always set msg.obj to the handler.
856              */
857             boolean recordLogMsg = mSm.recordLogRec(msg) && (msg.obj != mSmHandlerObj);
858 
859             if (mLogRecords.logOnlyTransitions()) {
860                 // Record only if there is a transition
861                 if (mDestState != null) {
862                     mLogRecords.add(mSm, msg, mSm.getLogRecString(msg), msgProcessedState,
863                             orgState, mDestState);
864                 }
865             } else if (recordLogMsg) {
866                 // Record message
867                 mLogRecords.add(mSm, msg, mSm.getLogRecString(msg), msgProcessedState, orgState,
868                         mDestState);
869             }
870 
871             State destState = mDestState;
872             if (destState != null) {
873                 /*
874                  * Process the transitions including transitions in the enter/exit methods
875                  */
876                 while (true) {
877                     if (mDbg) mSm.log("handleMessage: new destination call exit/enter");
878 
879                     /*
880                      * Determine the states to exit and enter and return the
881                      * common ancestor state of the enter/exit states. Then
882                      * invoke the exit methods then the enter methods.
883                      */
884                     StateInfo commonStateInfo = setupTempStateStackWithStatesToEnter(destState);
885                     // flag is cleared in invokeEnterMethods before entering the target state
886                     mTransitionInProgress = true;
887                     invokeExitMethods(commonStateInfo);
888                     int stateStackEnteringIndex = moveTempStateStackToStateStack();
889                     invokeEnterMethods(stateStackEnteringIndex);
890 
891                     /*
892                      * Since we have transitioned to a new state we need to have
893                      * any deferred messages moved to the front of the message queue
894                      * so they will be processed before any other messages in the
895                      * message queue.
896                      */
897                     moveDeferredMessageAtFrontOfQueue();
898 
899                     if (destState != mDestState) {
900                         // A new mDestState so continue looping
901                         destState = mDestState;
902                     } else {
903                         // No change in mDestState so we're done
904                         break;
905                     }
906                 }
907                 mDestState = null;
908             }
909 
910             /*
911              * After processing all transitions check and
912              * see if the last transition was to quit or halt.
913              */
914             if (destState != null) {
915                 if (destState == mQuittingState) {
916                     /*
917                      * Call onQuitting to let subclasses cleanup.
918                      */
919                     mSm.onQuitting();
920                     cleanupAfterQuitting();
921                 } else if (destState == mHaltingState) {
922                     /*
923                      * Call onHalting() if we've transitioned to the halting
924                      * state. All subsequent messages will be processed in
925                      * in the halting state which invokes haltedProcessMessage(msg);
926                      */
927                     mSm.onHalting();
928                 }
929             }
930         }
931 
932         /**
933          * Cleanup all the static variables and the looper after the SM has been quit.
934          */
cleanupAfterQuitting()935         private final void cleanupAfterQuitting() {
936             if (mSm.mSmThread != null) {
937                 // If we made the thread then quit looper which stops the thread.
938                 getLooper().quit();
939                 mSm.mSmThread = null;
940             }
941 
942             mSm.mSmHandler = null;
943             mSm = null;
944             mMsg = null;
945             mLogRecords.cleanup();
946             mStateStack = null;
947             mTempStateStack = null;
948             mStateInfo.clear();
949             mInitialState = null;
950             mDestState = null;
951             mDeferredMessages.clear();
952             mHasQuit = true;
953         }
954 
955         /**
956          * Complete the construction of the state machine.
957          */
completeConstruction()958         private final void completeConstruction() {
959             if (mDbg) mSm.log("completeConstruction: E");
960 
961             /*
962              * Determine the maximum depth of the state hierarchy
963              * so we can allocate the state stacks.
964              */
965             int maxDepth = 0;
966             for (StateInfo si : mStateInfo.values()) {
967                 int depth = 0;
968                 for (StateInfo i = si; i != null; depth++) {
969                     i = i.parentStateInfo;
970                 }
971                 if (maxDepth < depth) {
972                     maxDepth = depth;
973                 }
974             }
975             if (mDbg) mSm.log("completeConstruction: maxDepth=" + maxDepth);
976 
977             mStateStack = new StateInfo[maxDepth];
978             mTempStateStack = new StateInfo[maxDepth];
979             setupInitialStateStack();
980 
981             // Sending SM_INIT_CMD message to invoke enter methods asynchronously
982             sendMessageAtFrontOfQueue(obtainMessage(SM_INIT_CMD, mSmHandlerObj));
983 
984             if (mDbg) mSm.log("completeConstruction: X");
985         }
986 
987         /**
988          * Process the message. If the current state doesn't handle
989          * it, call the states parent and so on. If it is never handled then
990          * call the state machines unhandledMessage method.
991          * @return the state that processed the message
992          */
processMsg(Message msg)993         private final State processMsg(Message msg) {
994             StateInfo curStateInfo = mStateStack[mStateStackTopIndex];
995             if (mDbg) {
996                 mSm.log("processMsg: " + curStateInfo.state.getName());
997             }
998 
999             if (isQuit(msg)) {
1000                 transitionTo(mQuittingState);
1001             } else {
1002                 while (!curStateInfo.state.processMessage(msg)) {
1003                     // Not processed
1004                     curStateInfo = curStateInfo.parentStateInfo;
1005                     if (curStateInfo == null) {
1006                         // No parents left so it's not handled
1007                         mSm.unhandledMessage(msg);
1008                         break;
1009                     }
1010                     if (mDbg) {
1011                         mSm.log("processMsg: " + curStateInfo.state.getName());
1012                     }
1013                 }
1014             }
1015             return (curStateInfo != null) ? curStateInfo.state : null;
1016         }
1017 
1018         /**
1019          * Call the exit method for each state from the top of stack
1020          * up to the common ancestor state.
1021          */
invokeExitMethods(StateInfo commonStateInfo)1022         private final void invokeExitMethods(StateInfo commonStateInfo) {
1023             while ((mStateStackTopIndex >= 0)
1024                     && (mStateStack[mStateStackTopIndex] != commonStateInfo)) {
1025                 State curState = mStateStack[mStateStackTopIndex].state;
1026                 if (mDbg) mSm.log("invokeExitMethods: " + curState.getName());
1027                 curState.exit();
1028                 mStateStack[mStateStackTopIndex].active = false;
1029                 mStateStackTopIndex -= 1;
1030             }
1031         }
1032 
1033         /**
1034          * Invoke the enter method starting at the entering index to top of state stack
1035          */
invokeEnterMethods(int stateStackEnteringIndex)1036         private final void invokeEnterMethods(int stateStackEnteringIndex) {
1037             for (int i = stateStackEnteringIndex; i <= mStateStackTopIndex; i++) {
1038                 if (stateStackEnteringIndex == mStateStackTopIndex) {
1039                     // Last enter state for transition
1040                     mTransitionInProgress = false;
1041                 }
1042                 if (mDbg) mSm.log("invokeEnterMethods: " + mStateStack[i].state.getName());
1043                 mStateStack[i].state.enter();
1044                 mStateStack[i].active = true;
1045             }
1046             mTransitionInProgress = false; // ensure flag set to false if no methods called
1047         }
1048 
1049         /**
1050          * Move the deferred message to the front of the message queue.
1051          */
moveDeferredMessageAtFrontOfQueue()1052         private final void moveDeferredMessageAtFrontOfQueue() {
1053             /*
1054              * The oldest messages on the deferred list must be at
1055              * the front of the queue so start at the back, which
1056              * as the most resent message and end with the oldest
1057              * messages at the front of the queue.
1058              */
1059             for (int i = mDeferredMessages.size() - 1; i >= 0; i--) {
1060                 Message curMsg = mDeferredMessages.get(i);
1061                 if (mDbg) mSm.log("moveDeferredMessageAtFrontOfQueue; what=" + curMsg.what);
1062                 sendMessageAtFrontOfQueue(curMsg);
1063             }
1064             mDeferredMessages.clear();
1065         }
1066 
1067         /**
1068          * Move the contents of the temporary stack to the state stack
1069          * reversing the order of the items on the temporary stack as
1070          * they are moved.
1071          *
1072          * @return index into mStateStack where entering needs to start
1073          */
moveTempStateStackToStateStack()1074         private final int moveTempStateStackToStateStack() {
1075             int startingIndex = mStateStackTopIndex + 1;
1076             int i = mTempStateStackCount - 1;
1077             int j = startingIndex;
1078             while (i >= 0) {
1079                 if (mDbg) mSm.log("moveTempStackToStateStack: i=" + i + ",j=" + j);
1080                 mStateStack[j] = mTempStateStack[i];
1081                 j += 1;
1082                 i -= 1;
1083             }
1084 
1085             mStateStackTopIndex = j - 1;
1086             if (mDbg) {
1087                 mSm.log("moveTempStackToStateStack: X mStateStackTop=" + mStateStackTopIndex
1088                         + ",startingIndex=" + startingIndex + ",Top="
1089                         + mStateStack[mStateStackTopIndex].state.getName());
1090             }
1091             return startingIndex;
1092         }
1093 
1094         /**
1095          * Set up the mTempStateStack with the states we are going to enter.
1096          *
1097          * This is found by searching up the destState's ancestors for a
1098          * state that is already active i.e. StateInfo.active == true.
1099          * The destStae and all of its inactive parents will be on the
1100          * TempStateStack as the list of states to enter.
1101          *
1102          * @return StateInfo of the common ancestor for the destState and
1103          * current state or null if there is no common parent.
1104          */
setupTempStateStackWithStatesToEnter(State destState)1105         private final StateInfo setupTempStateStackWithStatesToEnter(State destState) {
1106             /*
1107              * Search up the parent list of the destination state for an active
1108              * state. Use a do while() loop as the destState must always be entered
1109              * even if it is active. This can happen if we are exiting/entering
1110              * the current state.
1111              */
1112             mTempStateStackCount = 0;
1113             StateInfo curStateInfo = mStateInfo.get(destState);
1114             do {
1115                 mTempStateStack[mTempStateStackCount++] = curStateInfo;
1116                 curStateInfo = curStateInfo.parentStateInfo;
1117             } while ((curStateInfo != null) && !curStateInfo.active);
1118 
1119             if (mDbg) {
1120                 mSm.log("setupTempStateStackWithStatesToEnter: X mTempStateStackCount="
1121                         + mTempStateStackCount + ",curStateInfo: " + curStateInfo);
1122             }
1123             return curStateInfo;
1124         }
1125 
1126         /**
1127          * Initialize StateStack to mInitialState.
1128          */
setupInitialStateStack()1129         private final void setupInitialStateStack() {
1130             if (mDbg) {
1131                 mSm.log("setupInitialStateStack: E mInitialState=" + mInitialState.getName());
1132             }
1133 
1134             StateInfo curStateInfo = mStateInfo.get(mInitialState);
1135             for (mTempStateStackCount = 0; curStateInfo != null; mTempStateStackCount++) {
1136                 mTempStateStack[mTempStateStackCount] = curStateInfo;
1137                 curStateInfo = curStateInfo.parentStateInfo;
1138             }
1139 
1140             // Empty the StateStack
1141             mStateStackTopIndex = -1;
1142 
1143             moveTempStateStackToStateStack();
1144         }
1145 
1146         /**
1147          * @return current message
1148          */
getCurrentMessage()1149         private final Message getCurrentMessage() {
1150             return mMsg;
1151         }
1152 
1153         /**
1154          * @return current state
1155          */
getCurrentState()1156         private final IState getCurrentState() {
1157             return mStateStack[mStateStackTopIndex].state;
1158         }
1159 
1160         /**
1161          * Add a new state to the state machine. Bottom up addition
1162          * of states is allowed but the same state may only exist
1163          * in one hierarchy.
1164          *
1165          * @param state the state to add
1166          * @param parent the parent of state
1167          * @return stateInfo for this state
1168          */
addState(State state, State parent)1169         private final StateInfo addState(State state, State parent) {
1170             if (mDbg) {
1171                 mSm.log("addStateInternal: E state=" + state.getName() + ",parent="
1172                         + ((parent == null) ? "" : parent.getName()));
1173             }
1174             StateInfo parentStateInfo = null;
1175             if (parent != null) {
1176                 parentStateInfo = mStateInfo.get(parent);
1177                 if (parentStateInfo == null) {
1178                     // Recursively add our parent as it's not been added yet.
1179                     parentStateInfo = addState(parent, null);
1180                 }
1181             }
1182             StateInfo stateInfo = mStateInfo.get(state);
1183             if (stateInfo == null) {
1184                 stateInfo = new StateInfo(state, parentStateInfo);
1185                 mStateInfo.put(state, stateInfo);
1186             }
1187 
1188             // Validate that we aren't adding the same state in two different hierarchies.
1189             if ((stateInfo.parentStateInfo != null)
1190                     && (stateInfo.parentStateInfo != parentStateInfo)) {
1191                 throw new RuntimeException("state already added");
1192             }
1193             if (mDbg) mSm.log("addStateInternal: X stateInfo: " + stateInfo);
1194             return stateInfo;
1195         }
1196 
1197         /**
1198          * Remove a state from the state machine. Will not remove the state if it is currently
1199          * active or if it has any children in the hierarchy.
1200          * @param state the state to remove
1201          */
removeState(State state)1202         private void removeState(State state) {
1203             StateInfo stateInfo = mStateInfo.get(state);
1204             if (stateInfo == null || stateInfo.active) {
1205                 return;
1206             }
1207             boolean isParent = mStateInfo.values().stream()
1208                     .anyMatch(si -> si.parentStateInfo == stateInfo);
1209             if (isParent) {
1210                 return;
1211             }
1212             mStateInfo.remove(state);
1213         }
1214 
1215         /**
1216          * Constructor
1217          *
1218          * @param looper for dispatching messages
1219          * @param sm the hierarchical state machine
1220          */
SmHandler(Looper looper, StateMachine sm)1221         private SmHandler(Looper looper, StateMachine sm) {
1222             super(looper);
1223             mSm = sm;
1224 
1225             addState(mHaltingState, null);
1226             addState(mQuittingState, null);
1227         }
1228 
1229         /** @see StateMachine#setInitialState(State) */
setInitialState(State initialState)1230         private final void setInitialState(State initialState) {
1231             if (mDbg) mSm.log("setInitialState: initialState=" + initialState.getName());
1232             mInitialState = initialState;
1233         }
1234 
1235         /** @see StateMachine#transitionTo(IState) */
transitionTo(IState destState)1236         private final void transitionTo(IState destState) {
1237             if (mTransitionInProgress) {
1238                 Log.wtf(mSm.mName, "transitionTo called while transition already in progress to " +
1239                         mDestState + ", new target state=" + destState);
1240             }
1241             mDestState = (State) destState;
1242             if (mDbg) mSm.log("transitionTo: destState=" + mDestState.getName());
1243         }
1244 
1245         /** @see StateMachine#deferMessage(Message) */
deferMessage(Message msg)1246         private final void deferMessage(Message msg) {
1247             if (mDbg) mSm.log("deferMessage: msg=" + msg.what);
1248 
1249             /* Copy the "msg" to "newMsg" as "msg" will be recycled */
1250             Message newMsg = obtainMessage();
1251             newMsg.copyFrom(msg);
1252 
1253             mDeferredMessages.add(newMsg);
1254         }
1255 
1256         /** @see StateMachine#quit() */
quit()1257         private final void quit() {
1258             if (mDbg) mSm.log("quit:");
1259             sendMessage(obtainMessage(SM_QUIT_CMD, mSmHandlerObj));
1260         }
1261 
1262         /** @see StateMachine#quitNow() */
quitNow()1263         private final void quitNow() {
1264             if (mDbg) mSm.log("quitNow:");
1265             sendMessageAtFrontOfQueue(obtainMessage(SM_QUIT_CMD, mSmHandlerObj));
1266         }
1267 
1268         /** Validate that the message was sent by quit or quitNow. */
isQuit(Message msg)1269         private final boolean isQuit(Message msg) {
1270             return (msg.what == SM_QUIT_CMD) && (msg.obj == mSmHandlerObj);
1271         }
1272 
1273         /** @see StateMachine#isDbg() */
isDbg()1274         private final boolean isDbg() {
1275             return mDbg;
1276         }
1277 
1278         /** @see StateMachine#setDbg(boolean) */
setDbg(boolean dbg)1279         private final void setDbg(boolean dbg) {
1280             mDbg = dbg;
1281         }
1282 
1283     }
1284 
1285     private SmHandler mSmHandler;
1286     private HandlerThread mSmThread;
1287 
1288     /**
1289      * Initialize.
1290      *
1291      * @param looper for this state machine
1292      * @param name of the state machine
1293      */
initStateMachine(String name, Looper looper)1294     private void initStateMachine(String name, Looper looper) {
1295         mName = name;
1296         mSmHandler = new SmHandler(looper, this);
1297     }
1298 
1299     /**
1300      * Constructor creates a StateMachine with its own thread.
1301      *
1302      * @param name of the state machine
1303      */
1304     @UnsupportedAppUsage
StateMachine(String name)1305     protected StateMachine(String name) {
1306         mSmThread = new HandlerThread(name);
1307         mSmThread.start();
1308         Looper looper = mSmThread.getLooper();
1309 
1310         initStateMachine(name, looper);
1311     }
1312 
1313     /**
1314      * Constructor creates a StateMachine using the looper.
1315      *
1316      * @param name of the state machine
1317      */
1318     @UnsupportedAppUsage
StateMachine(String name, Looper looper)1319     protected StateMachine(String name, Looper looper) {
1320         initStateMachine(name, looper);
1321     }
1322 
1323     /**
1324      * Constructor creates a StateMachine using the handler.
1325      *
1326      * @param name of the state machine
1327      */
1328     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
StateMachine(String name, Handler handler)1329     protected StateMachine(String name, Handler handler) {
1330         initStateMachine(name, handler.getLooper());
1331     }
1332 
1333     /**
1334      * Notifies subclass that the StateMachine handler is about to process the Message msg
1335      * @param msg The message that is being handled
1336      */
onPreHandleMessage(Message msg)1337     protected void onPreHandleMessage(Message msg) {
1338     }
1339 
1340     /**
1341      * Notifies subclass that the StateMachine handler has finished processing the Message msg and
1342      * has possibly transitioned to a new state.
1343      * @param msg The message that is being handled
1344      */
onPostHandleMessage(Message msg)1345     protected void onPostHandleMessage(Message msg) {
1346     }
1347 
1348     /**
1349      * Add a new state to the state machine
1350      * @param state the state to add
1351      * @param parent the parent of state
1352      */
addState(State state, State parent)1353     public final void addState(State state, State parent) {
1354         mSmHandler.addState(state, parent);
1355     }
1356 
1357     /**
1358      * Add a new state to the state machine, parent will be null
1359      * @param state to add
1360      */
1361     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
addState(State state)1362     public final void addState(State state) {
1363         mSmHandler.addState(state, null);
1364     }
1365 
1366     /**
1367      * Removes a state from the state machine, unless it is currently active or if it has children.
1368      * @param state state to remove
1369      */
removeState(State state)1370     public final void removeState(State state) {
1371         mSmHandler.removeState(state);
1372     }
1373 
1374     /**
1375      * Set the initial state. This must be invoked before
1376      * and messages are sent to the state machine.
1377      *
1378      * @param initialState is the state which will receive the first message.
1379      */
1380     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
setInitialState(State initialState)1381     public final void setInitialState(State initialState) {
1382         mSmHandler.setInitialState(initialState);
1383     }
1384 
1385     /**
1386      * @return current message
1387      */
getCurrentMessage()1388     public final Message getCurrentMessage() {
1389         // mSmHandler can be null if the state machine has quit.
1390         SmHandler smh = mSmHandler;
1391         if (smh == null) return null;
1392         return smh.getCurrentMessage();
1393     }
1394 
1395     /**
1396      * @return current state
1397      */
getCurrentState()1398     public final IState getCurrentState() {
1399         // mSmHandler can be null if the state machine has quit.
1400         SmHandler smh = mSmHandler;
1401         if (smh == null) return null;
1402         return smh.getCurrentState();
1403     }
1404 
1405     /**
1406      * transition to destination state. Upon returning
1407      * from processMessage the current state's exit will
1408      * be executed and upon the next message arriving
1409      * destState.enter will be invoked.
1410      *
1411      * this function can also be called inside the enter function of the
1412      * previous transition target, but the behavior is undefined when it is
1413      * called mid-way through a previous transition (for example, calling this
1414      * in the enter() routine of a intermediate node when the current transition
1415      * target is one of the nodes descendants).
1416      *
1417      * @param destState will be the state that receives the next message.
1418      */
1419     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
transitionTo(IState destState)1420     public final void transitionTo(IState destState) {
1421         mSmHandler.transitionTo(destState);
1422     }
1423 
1424     /**
1425      * transition to halt state. Upon returning
1426      * from processMessage we will exit all current
1427      * states, execute the onHalting() method and then
1428      * for all subsequent messages haltedProcessMessage
1429      * will be called.
1430      */
transitionToHaltingState()1431     public final void transitionToHaltingState() {
1432         mSmHandler.transitionTo(mSmHandler.mHaltingState);
1433     }
1434 
1435     /**
1436      * Defer this message until next state transition.
1437      * Upon transitioning all deferred messages will be
1438      * placed on the queue and reprocessed in the original
1439      * order. (i.e. The next state the oldest messages will
1440      * be processed first)
1441      *
1442      * @param msg is deferred until the next transition.
1443      */
deferMessage(Message msg)1444     public final void deferMessage(Message msg) {
1445         mSmHandler.deferMessage(msg);
1446     }
1447 
1448     /**
1449      * Called when message wasn't handled
1450      *
1451      * @param msg that couldn't be handled.
1452      */
unhandledMessage(Message msg)1453     protected void unhandledMessage(Message msg) {
1454         if (mSmHandler.mDbg) loge(" - unhandledMessage: msg.what=" + msg.what);
1455     }
1456 
1457     /**
1458      * Called for any message that is received after
1459      * transitionToHalting is called.
1460      */
haltedProcessMessage(Message msg)1461     protected void haltedProcessMessage(Message msg) {
1462     }
1463 
1464     /**
1465      * This will be called once after handling a message that called
1466      * transitionToHalting. All subsequent messages will invoke
1467      * {@link StateMachine#haltedProcessMessage(Message)}
1468      */
onHalting()1469     protected void onHalting() {
1470     }
1471 
1472     /**
1473      * This will be called once after a quit message that was NOT handled by
1474      * the derived StateMachine. The StateMachine will stop and any subsequent messages will be
1475      * ignored. In addition, if this StateMachine created the thread, the thread will
1476      * be stopped after this method returns.
1477      */
onQuitting()1478     protected void onQuitting() {
1479     }
1480 
1481     /**
1482      * @return the name
1483      */
getName()1484     public final String getName() {
1485         return mName;
1486     }
1487 
1488     /**
1489      * Set number of log records to maintain and clears all current records.
1490      *
1491      * @param maxSize number of messages to maintain at anyone time.
1492      */
setLogRecSize(int maxSize)1493     public final void setLogRecSize(int maxSize) {
1494         mSmHandler.mLogRecords.setSize(maxSize);
1495     }
1496 
1497     /**
1498      * Set to log only messages that cause a state transition
1499      *
1500      * @param enable {@code true} to enable, {@code false} to disable
1501      */
setLogOnlyTransitions(boolean enable)1502     public final void setLogOnlyTransitions(boolean enable) {
1503         mSmHandler.mLogRecords.setLogOnlyTransitions(enable);
1504     }
1505 
1506     /**
1507      * @return the number of log records currently readable
1508      */
getLogRecSize()1509     public final int getLogRecSize() {
1510         // mSmHandler can be null if the state machine has quit.
1511         SmHandler smh = mSmHandler;
1512         if (smh == null) return 0;
1513         return smh.mLogRecords.size();
1514     }
1515 
1516     /**
1517      * @return the number of log records we can store
1518      */
1519     @VisibleForTesting
getLogRecMaxSize()1520     public final int getLogRecMaxSize() {
1521         // mSmHandler can be null if the state machine has quit.
1522         SmHandler smh = mSmHandler;
1523         if (smh == null) return 0;
1524         return smh.mLogRecords.mMaxSize;
1525     }
1526 
1527     /**
1528      * @return the total number of records processed
1529      */
getLogRecCount()1530     public final int getLogRecCount() {
1531         // mSmHandler can be null if the state machine has quit.
1532         SmHandler smh = mSmHandler;
1533         if (smh == null) return 0;
1534         return smh.mLogRecords.count();
1535     }
1536 
1537     /**
1538      * @return a log record, or null if index is out of range
1539      */
getLogRec(int index)1540     public final LogRec getLogRec(int index) {
1541         // mSmHandler can be null if the state machine has quit.
1542         SmHandler smh = mSmHandler;
1543         if (smh == null) return null;
1544         return smh.mLogRecords.get(index);
1545     }
1546 
1547     /**
1548      * @return a copy of LogRecs as a collection
1549      */
copyLogRecs()1550     public final Collection<LogRec> copyLogRecs() {
1551         Vector<LogRec> vlr = new Vector<>();
1552         SmHandler smh = mSmHandler;
1553         if (smh != null) {
1554             for (LogRec lr : smh.mLogRecords.mLogRecVector) {
1555                 vlr.add(lr);
1556             }
1557         }
1558         return vlr;
1559     }
1560 
1561     /**
1562      * Add the string to LogRecords.
1563      *
1564      * @param string the info message to add
1565      */
addLogRec(String string)1566     public void addLogRec(String string) {
1567         // mSmHandler can be null if the state machine has quit.
1568         SmHandler smh = mSmHandler;
1569         if (smh == null) return;
1570         smh.mLogRecords.add(this, smh.getCurrentMessage(), string, smh.getCurrentState(),
1571                 smh.mStateStack[smh.mStateStackTopIndex].state, smh.mDestState);
1572     }
1573 
1574     /**
1575      * @return true if msg should be saved in the log, default is true.
1576      */
recordLogRec(Message msg)1577     protected boolean recordLogRec(Message msg) {
1578         return true;
1579     }
1580 
1581     /**
1582      * Return a string to be logged by LogRec, default
1583      * is an empty string. Override if additional information is desired.
1584      *
1585      * @param msg that was processed
1586      * @return information to be logged as a String
1587      */
getLogRecString(Message msg)1588     protected String getLogRecString(Message msg) {
1589         return "";
1590     }
1591 
1592     /**
1593      * @return the string for msg.what
1594      */
getWhatToString(int what)1595     protected String getWhatToString(int what) {
1596         return null;
1597     }
1598 
1599     /**
1600      * @return Handler, maybe null if state machine has quit.
1601      */
getHandler()1602     public final Handler getHandler() {
1603         return mSmHandler;
1604     }
1605 
1606     /**
1607      * Get a message and set Message.target state machine handler.
1608      *
1609      * Note: The handler can be null if the state machine has quit,
1610      * which means target will be null and may cause a AndroidRuntimeException
1611      * in MessageQueue#enqueMessage if sent directly or if sent using
1612      * StateMachine#sendMessage the message will just be ignored.
1613      *
1614      * @return  A Message object from the global pool
1615      */
obtainMessage()1616     public final Message obtainMessage() {
1617         return Message.obtain(mSmHandler);
1618     }
1619 
1620     /**
1621      * Get a message and set Message.target state machine handler, what.
1622      *
1623      * Note: The handler can be null if the state machine has quit,
1624      * which means target will be null and may cause a AndroidRuntimeException
1625      * in MessageQueue#enqueMessage if sent directly or if sent using
1626      * StateMachine#sendMessage the message will just be ignored.
1627      *
1628      * @param what is the assigned to Message.what.
1629      * @return  A Message object from the global pool
1630      */
obtainMessage(int what)1631     public final Message obtainMessage(int what) {
1632         return Message.obtain(mSmHandler, what);
1633     }
1634 
1635     /**
1636      * Get a message and set Message.target state machine handler,
1637      * what and obj.
1638      *
1639      * Note: The handler can be null if the state machine has quit,
1640      * which means target will be null and may cause a AndroidRuntimeException
1641      * in MessageQueue#enqueMessage if sent directly or if sent using
1642      * StateMachine#sendMessage the message will just be ignored.
1643      *
1644      * @param what is the assigned to Message.what.
1645      * @param obj is assigned to Message.obj.
1646      * @return  A Message object from the global pool
1647      */
obtainMessage(int what, Object obj)1648     public final Message obtainMessage(int what, Object obj) {
1649         return Message.obtain(mSmHandler, what, obj);
1650     }
1651 
1652     /**
1653      * Get a message and set Message.target state machine handler,
1654      * what, arg1 and arg2
1655      *
1656      * Note: The handler can be null if the state machine has quit,
1657      * which means target will be null and may cause a AndroidRuntimeException
1658      * in MessageQueue#enqueMessage if sent directly or if sent using
1659      * StateMachine#sendMessage the message will just be ignored.
1660      *
1661      * @param what  is assigned to Message.what
1662      * @param arg1  is assigned to Message.arg1
1663      * @return  A Message object from the global pool
1664      */
obtainMessage(int what, int arg1)1665     public final Message obtainMessage(int what, int arg1) {
1666         // use this obtain so we don't match the obtain(h, what, Object) method
1667         return Message.obtain(mSmHandler, what, arg1, 0);
1668     }
1669 
1670     /**
1671      * Get a message and set Message.target state machine handler,
1672      * what, arg1 and arg2
1673      *
1674      * Note: The handler can be null if the state machine has quit,
1675      * which means target will be null and may cause a AndroidRuntimeException
1676      * in MessageQueue#enqueMessage if sent directly or if sent using
1677      * StateMachine#sendMessage the message will just be ignored.
1678      *
1679      * @param what  is assigned to Message.what
1680      * @param arg1  is assigned to Message.arg1
1681      * @param arg2  is assigned to Message.arg2
1682      * @return  A Message object from the global pool
1683      */
1684     @UnsupportedAppUsage
obtainMessage(int what, int arg1, int arg2)1685     public final Message obtainMessage(int what, int arg1, int arg2) {
1686         return Message.obtain(mSmHandler, what, arg1, arg2);
1687     }
1688 
1689     /**
1690      * Get a message and set Message.target state machine handler,
1691      * what, arg1, arg2 and obj
1692      *
1693      * Note: The handler can be null if the state machine has quit,
1694      * which means target will be null and may cause a AndroidRuntimeException
1695      * in MessageQueue#enqueMessage if sent directly or if sent using
1696      * StateMachine#sendMessage the message will just be ignored.
1697      *
1698      * @param what  is assigned to Message.what
1699      * @param arg1  is assigned to Message.arg1
1700      * @param arg2  is assigned to Message.arg2
1701      * @param obj is assigned to Message.obj
1702      * @return  A Message object from the global pool
1703      */
1704     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
obtainMessage(int what, int arg1, int arg2, Object obj)1705     public final Message obtainMessage(int what, int arg1, int arg2, Object obj) {
1706         return Message.obtain(mSmHandler, what, arg1, arg2, obj);
1707     }
1708 
1709     /**
1710      * Enqueue a message to this state machine.
1711      *
1712      * Message is ignored if state machine has quit.
1713      */
1714     @UnsupportedAppUsage
sendMessage(int what)1715     public void sendMessage(int what) {
1716         // mSmHandler can be null if the state machine has quit.
1717         SmHandler smh = mSmHandler;
1718         if (smh == null) return;
1719 
1720         smh.sendMessage(obtainMessage(what));
1721     }
1722 
1723     /**
1724      * Enqueue a message to this state machine.
1725      *
1726      * Message is ignored if state machine has quit.
1727      */
1728     @UnsupportedAppUsage
sendMessage(int what, Object obj)1729     public void sendMessage(int what, Object obj) {
1730         // mSmHandler can be null if the state machine has quit.
1731         SmHandler smh = mSmHandler;
1732         if (smh == null) return;
1733 
1734         smh.sendMessage(obtainMessage(what, obj));
1735     }
1736 
1737     /**
1738      * Enqueue a message to this state machine.
1739      *
1740      * Message is ignored if state machine has quit.
1741      */
1742     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
sendMessage(int what, int arg1)1743     public void sendMessage(int what, int arg1) {
1744         // mSmHandler can be null if the state machine has quit.
1745         SmHandler smh = mSmHandler;
1746         if (smh == null) return;
1747 
1748         smh.sendMessage(obtainMessage(what, arg1));
1749     }
1750 
1751     /**
1752      * Enqueue a message to this state machine.
1753      *
1754      * Message is ignored if state machine has quit.
1755      */
sendMessage(int what, int arg1, int arg2)1756     public void sendMessage(int what, int arg1, int arg2) {
1757         // mSmHandler can be null if the state machine has quit.
1758         SmHandler smh = mSmHandler;
1759         if (smh == null) return;
1760 
1761         smh.sendMessage(obtainMessage(what, arg1, arg2));
1762     }
1763 
1764     /**
1765      * Enqueue a message to this state machine.
1766      *
1767      * Message is ignored if state machine has quit.
1768      */
1769     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
sendMessage(int what, int arg1, int arg2, Object obj)1770     public void sendMessage(int what, int arg1, int arg2, Object obj) {
1771         // mSmHandler can be null if the state machine has quit.
1772         SmHandler smh = mSmHandler;
1773         if (smh == null) return;
1774 
1775         smh.sendMessage(obtainMessage(what, arg1, arg2, obj));
1776     }
1777 
1778     /**
1779      * Enqueue a message to this state machine.
1780      *
1781      * Message is ignored if state machine has quit.
1782      */
1783     @UnsupportedAppUsage
sendMessage(Message msg)1784     public void sendMessage(Message msg) {
1785         // mSmHandler can be null if the state machine has quit.
1786         SmHandler smh = mSmHandler;
1787         if (smh == null) return;
1788 
1789         smh.sendMessage(msg);
1790     }
1791 
1792     /**
1793      * Enqueue a message to this state machine after a delay.
1794      *
1795      * Message is ignored if state machine has quit.
1796      */
sendMessageDelayed(int what, long delayMillis)1797     public void sendMessageDelayed(int what, long delayMillis) {
1798         // mSmHandler can be null if the state machine has quit.
1799         SmHandler smh = mSmHandler;
1800         if (smh == null) return;
1801 
1802         smh.sendMessageDelayed(obtainMessage(what), delayMillis);
1803     }
1804 
1805     /**
1806      * Enqueue a message to this state machine after a delay.
1807      *
1808      * Message is ignored if state machine has quit.
1809      */
sendMessageDelayed(int what, Object obj, long delayMillis)1810     public void sendMessageDelayed(int what, Object obj, long delayMillis) {
1811         // mSmHandler can be null if the state machine has quit.
1812         SmHandler smh = mSmHandler;
1813         if (smh == null) return;
1814 
1815         smh.sendMessageDelayed(obtainMessage(what, obj), delayMillis);
1816     }
1817 
1818     /**
1819      * Enqueue a message to this state machine after a delay.
1820      *
1821      * Message is ignored if state machine has quit.
1822      */
sendMessageDelayed(int what, int arg1, long delayMillis)1823     public void sendMessageDelayed(int what, int arg1, long delayMillis) {
1824         // mSmHandler can be null if the state machine has quit.
1825         SmHandler smh = mSmHandler;
1826         if (smh == null) return;
1827 
1828         smh.sendMessageDelayed(obtainMessage(what, arg1), delayMillis);
1829     }
1830 
1831     /**
1832      * Enqueue a message to this state machine after a delay.
1833      *
1834      * Message is ignored if state machine has quit.
1835      */
sendMessageDelayed(int what, int arg1, int arg2, long delayMillis)1836     public void sendMessageDelayed(int what, int arg1, int arg2, long delayMillis) {
1837         // mSmHandler can be null if the state machine has quit.
1838         SmHandler smh = mSmHandler;
1839         if (smh == null) return;
1840 
1841         smh.sendMessageDelayed(obtainMessage(what, arg1, arg2), delayMillis);
1842     }
1843 
1844     /**
1845      * Enqueue a message to this state machine after a delay.
1846      *
1847      * Message is ignored if state machine has quit.
1848      */
sendMessageDelayed(int what, int arg1, int arg2, Object obj, long delayMillis)1849     public void sendMessageDelayed(int what, int arg1, int arg2, Object obj,
1850             long delayMillis) {
1851         // mSmHandler can be null if the state machine has quit.
1852         SmHandler smh = mSmHandler;
1853         if (smh == null) return;
1854 
1855         smh.sendMessageDelayed(obtainMessage(what, arg1, arg2, obj), delayMillis);
1856     }
1857 
1858     /**
1859      * Enqueue a message to this state machine after a delay.
1860      *
1861      * Message is ignored if state machine has quit.
1862      */
sendMessageDelayed(Message msg, long delayMillis)1863     public void sendMessageDelayed(Message msg, long delayMillis) {
1864         // mSmHandler can be null if the state machine has quit.
1865         SmHandler smh = mSmHandler;
1866         if (smh == null) return;
1867 
1868         smh.sendMessageDelayed(msg, delayMillis);
1869     }
1870 
1871     /**
1872      * Enqueue a message to the front of the queue for this state machine.
1873      * Protected, may only be called by instances of StateMachine.
1874      *
1875      * Message is ignored if state machine has quit.
1876      */
sendMessageAtFrontOfQueue(int what)1877     protected final void sendMessageAtFrontOfQueue(int what) {
1878         // mSmHandler can be null if the state machine has quit.
1879         SmHandler smh = mSmHandler;
1880         if (smh == null) return;
1881 
1882         smh.sendMessageAtFrontOfQueue(obtainMessage(what));
1883     }
1884 
1885     /**
1886      * Enqueue a message to the front of the queue for this state machine.
1887      * Protected, may only be called by instances of StateMachine.
1888      *
1889      * Message is ignored if state machine has quit.
1890      */
sendMessageAtFrontOfQueue(int what, Object obj)1891     protected final void sendMessageAtFrontOfQueue(int what, Object obj) {
1892         // mSmHandler can be null if the state machine has quit.
1893         SmHandler smh = mSmHandler;
1894         if (smh == null) return;
1895 
1896         smh.sendMessageAtFrontOfQueue(obtainMessage(what, obj));
1897     }
1898 
1899     /**
1900      * Enqueue a message to the front of the queue for this state machine.
1901      * Protected, may only be called by instances of StateMachine.
1902      *
1903      * Message is ignored if state machine has quit.
1904      */
sendMessageAtFrontOfQueue(int what, int arg1)1905     protected final void sendMessageAtFrontOfQueue(int what, int arg1) {
1906         // mSmHandler can be null if the state machine has quit.
1907         SmHandler smh = mSmHandler;
1908         if (smh == null) return;
1909 
1910         smh.sendMessageAtFrontOfQueue(obtainMessage(what, arg1));
1911     }
1912 
1913 
1914     /**
1915      * Enqueue a message to the front of the queue for this state machine.
1916      * Protected, may only be called by instances of StateMachine.
1917      *
1918      * Message is ignored if state machine has quit.
1919      */
sendMessageAtFrontOfQueue(int what, int arg1, int arg2)1920     protected final void sendMessageAtFrontOfQueue(int what, int arg1, int arg2) {
1921         // mSmHandler can be null if the state machine has quit.
1922         SmHandler smh = mSmHandler;
1923         if (smh == null) return;
1924 
1925         smh.sendMessageAtFrontOfQueue(obtainMessage(what, arg1, arg2));
1926     }
1927 
1928     /**
1929      * Enqueue a message to the front of the queue for this state machine.
1930      * Protected, may only be called by instances of StateMachine.
1931      *
1932      * Message is ignored if state machine has quit.
1933      */
sendMessageAtFrontOfQueue(int what, int arg1, int arg2, Object obj)1934     protected final void sendMessageAtFrontOfQueue(int what, int arg1, int arg2, Object obj) {
1935         // mSmHandler can be null if the state machine has quit.
1936         SmHandler smh = mSmHandler;
1937         if (smh == null) return;
1938 
1939         smh.sendMessageAtFrontOfQueue(obtainMessage(what, arg1, arg2, obj));
1940     }
1941 
1942     /**
1943      * Enqueue a message to the front of the queue for this state machine.
1944      * Protected, may only be called by instances of StateMachine.
1945      *
1946      * Message is ignored if state machine has quit.
1947      */
sendMessageAtFrontOfQueue(Message msg)1948     protected final void sendMessageAtFrontOfQueue(Message msg) {
1949         // mSmHandler can be null if the state machine has quit.
1950         SmHandler smh = mSmHandler;
1951         if (smh == null) return;
1952 
1953         smh.sendMessageAtFrontOfQueue(msg);
1954     }
1955 
1956     /**
1957      * Removes a message from the message queue.
1958      * Protected, may only be called by instances of StateMachine.
1959      */
removeMessages(int what)1960     protected final void removeMessages(int what) {
1961         // mSmHandler can be null if the state machine has quit.
1962         SmHandler smh = mSmHandler;
1963         if (smh == null) return;
1964 
1965         smh.removeMessages(what);
1966     }
1967 
1968     /**
1969      * Removes a message from the deferred messages queue.
1970      */
removeDeferredMessages(int what)1971     protected final void removeDeferredMessages(int what) {
1972         SmHandler smh = mSmHandler;
1973         if (smh == null) return;
1974 
1975         Iterator<Message> iterator = smh.mDeferredMessages.iterator();
1976         while (iterator.hasNext()) {
1977             Message msg = iterator.next();
1978             if (msg.what == what) iterator.remove();
1979         }
1980     }
1981 
1982     /**
1983      * Check if there are any pending messages with code 'what' in deferred messages queue.
1984      */
hasDeferredMessages(int what)1985     protected final boolean hasDeferredMessages(int what) {
1986         SmHandler smh = mSmHandler;
1987         if (smh == null) return false;
1988 
1989         Iterator<Message> iterator = smh.mDeferredMessages.iterator();
1990         while (iterator.hasNext()) {
1991             Message msg = iterator.next();
1992             if (msg.what == what) return true;
1993         }
1994 
1995         return false;
1996     }
1997 
1998     /**
1999      * Check if there are any pending posts of messages with code 'what' in
2000      * the message queue. This does NOT check messages in deferred message queue.
2001      */
hasMessages(int what)2002     protected final boolean hasMessages(int what) {
2003         SmHandler smh = mSmHandler;
2004         if (smh == null) return false;
2005 
2006         return smh.hasMessages(what);
2007     }
2008 
2009     /**
2010      * Validate that the message was sent by
2011      * {@link StateMachine#quit} or {@link StateMachine#quitNow}.
2012      */
isQuit(Message msg)2013     protected final boolean isQuit(Message msg) {
2014         // mSmHandler can be null if the state machine has quit.
2015         SmHandler smh = mSmHandler;
2016         if (smh == null) return msg.what == SM_QUIT_CMD;
2017 
2018         return smh.isQuit(msg);
2019     }
2020 
2021     /**
2022      * Quit the state machine after all currently queued up messages are processed.
2023      */
quit()2024     public final void quit() {
2025         // mSmHandler can be null if the state machine is already stopped.
2026         SmHandler smh = mSmHandler;
2027         if (smh == null) return;
2028 
2029         smh.quit();
2030     }
2031 
2032     /**
2033      * Quit the state machine immediately all currently queued messages will be discarded.
2034      */
quitNow()2035     public final void quitNow() {
2036         // mSmHandler can be null if the state machine is already stopped.
2037         SmHandler smh = mSmHandler;
2038         if (smh == null) return;
2039 
2040         smh.quitNow();
2041     }
2042 
2043     /**
2044      * @return if debugging is enabled
2045      */
isDbg()2046     public boolean isDbg() {
2047         // mSmHandler can be null if the state machine has quit.
2048         SmHandler smh = mSmHandler;
2049         if (smh == null) return false;
2050 
2051         return smh.isDbg();
2052     }
2053 
2054     /**
2055      * Set debug enable/disabled.
2056      *
2057      * @param dbg is true to enable debugging.
2058      */
setDbg(boolean dbg)2059     public void setDbg(boolean dbg) {
2060         // mSmHandler can be null if the state machine has quit.
2061         SmHandler smh = mSmHandler;
2062         if (smh == null) return;
2063 
2064         smh.setDbg(dbg);
2065     }
2066 
2067     /**
2068      * Start the state machine.
2069      */
2070     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
start()2071     public void start() {
2072         // mSmHandler can be null if the state machine has quit.
2073         SmHandler smh = mSmHandler;
2074         if (smh == null) return;
2075 
2076         // Send the complete construction message
2077         smh.completeConstruction();
2078     }
2079 
2080     /**
2081      * Dump the current state.
2082      *
2083      * @param fd the fd to dump to
2084      * @param pw the writer
2085      * @param args arguments passed to the dump command
2086      */
2087     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
dump(FileDescriptor fd, PrintWriter pw, String[] args)2088     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
2089         pw.println(getName() + ":");
2090         pw.println(" total records=" + getLogRecCount());
2091         for (int i = 0; i < getLogRecSize(); i++) {
2092             pw.println(" rec[" + i + "]: " + getLogRec(i));
2093             pw.flush();
2094         }
2095         final IState curState = getCurrentState();
2096         pw.println("curState=" + (curState == null ? "<QUIT>" : curState.getName()));
2097     }
2098 
2099     @Override
toString()2100     public String toString() {
2101         String state = "null";
2102         try {
2103             state = mSmHandler.getCurrentState().getName().toString();
2104         } catch (NullPointerException | ArrayIndexOutOfBoundsException e) {
2105             // Will use default(s) initialized above.
2106         }
2107         return "name=" + mName + " state=" + state;
2108     }
2109 
2110     /**
2111      * Log with debug and add to the LogRecords.
2112      *
2113      * @param s is string log
2114      */
logAndAddLogRec(String s)2115     protected void logAndAddLogRec(String s) {
2116         addLogRec(s);
2117         log(s);
2118     }
2119 
2120     /**
2121      * Log with debug
2122      *
2123      * @param s is string log
2124      */
log(String s)2125     protected void log(String s) {
2126         Log.d(mName, s);
2127     }
2128 
2129     /**
2130      * Log with debug attribute
2131      *
2132      * @param s is string log
2133      */
logd(String s)2134     protected void logd(String s) {
2135         Log.d(mName, s);
2136     }
2137 
2138     /**
2139      * Log with verbose attribute
2140      *
2141      * @param s is string log
2142      */
logv(String s)2143     protected void logv(String s) {
2144         Log.v(mName, s);
2145     }
2146 
2147     /**
2148      * Log with info attribute
2149      *
2150      * @param s is string log
2151      */
logi(String s)2152     protected void logi(String s) {
2153         Log.i(mName, s);
2154     }
2155 
2156     /**
2157      * Log with warning attribute
2158      *
2159      * @param s is string log
2160      */
logw(String s)2161     protected void logw(String s) {
2162         Log.w(mName, s);
2163     }
2164 
2165     /**
2166      * Log with error attribute
2167      *
2168      * @param s is string log
2169      */
loge(String s)2170     protected void loge(String s) {
2171         Log.e(mName, s);
2172     }
2173 
2174     /**
2175      * Log with error attribute
2176      *
2177      * @param s is string log
2178      * @param e is a Throwable which logs additional information.
2179      */
loge(String s, Throwable e)2180     protected void loge(String s, Throwable e) {
2181         Log.e(mName, s, e);
2182     }
2183 }
2184