• 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 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 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 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 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 HaltingState mHaltingState = new HaltingState();
716 
717         /** State used when state machine is quitting */
718         private 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 class StateInfo {
728             /** The state */
729             State state;
730 
731             /** The parent of this state, null if there is no parent */
732             StateInfo parentStateInfo;
733 
734             /** True when the state has been entered and on the stack */
735             boolean active;
736 
737             /**
738              * Convert StateInfo to string
739              */
740             @Override
toString()741             public String toString() {
742                 return "state=" + state.getName() + ",active=" + active + ",parent="
743                         + ((parentStateInfo == null) ? "null" : parentStateInfo.state.getName());
744             }
745         }
746 
747         /** The map of all of the states in the state machine */
748         private HashMap<State, StateInfo> mStateInfo = new HashMap<State, StateInfo>();
749 
750         /** The initial state that will process the first message */
751         private State mInitialState;
752 
753         /** The destination state when transitionTo has been invoked */
754         private State mDestState;
755 
756         /**
757          * Indicates if a transition is in progress
758          *
759          * This will be true for all calls of State.exit and all calls of State.enter except for the
760          * last enter call for the current destination state.
761          */
762         private boolean mTransitionInProgress = false;
763 
764         /** The list of deferred messages */
765         private ArrayList<Message> mDeferredMessages = new ArrayList<Message>();
766 
767         /**
768          * State entered when transitionToHaltingState is called.
769          */
770         private class HaltingState extends State {
771             @Override
processMessage(Message msg)772             public boolean processMessage(Message msg) {
773                 mSm.haltedProcessMessage(msg);
774                 return true;
775             }
776         }
777 
778         /**
779          * State entered when a valid quit message is handled.
780          */
781         private class QuittingState extends State {
782             @Override
processMessage(Message msg)783             public boolean processMessage(Message msg) {
784                 return NOT_HANDLED;
785             }
786         }
787 
788         /**
789          * Handle messages sent to the state machine by calling
790          * the current state's processMessage. It also handles
791          * the enter/exit calls and placing any deferred messages
792          * back onto the queue when transitioning to a new state.
793          */
794         @Override
handleMessage(Message msg)795         public final void handleMessage(Message msg) {
796             if (!mHasQuit) {
797                 if (mSm != null && msg.what != SM_INIT_CMD && msg.what != SM_QUIT_CMD) {
798                     mSm.onPreHandleMessage(msg);
799                 }
800 
801                 if (mDbg) mSm.log("handleMessage: E msg.what=" + msg.what);
802 
803                 /** Save the current message */
804                 mMsg = msg;
805 
806                 /** State that processed the message */
807                 State msgProcessedState = null;
808                 if (mIsConstructionCompleted || (mMsg.what == SM_QUIT_CMD)) {
809                     /** Normal path */
810                     msgProcessedState = processMsg(msg);
811                 } else if (!mIsConstructionCompleted && (mMsg.what == SM_INIT_CMD)
812                         && (mMsg.obj == mSmHandlerObj)) {
813                     /** Initial one time path. */
814                     mIsConstructionCompleted = true;
815                     invokeEnterMethods(0);
816                 } else {
817                     throw new RuntimeException("StateMachine.handleMessage: "
818                             + "The start method not called, received msg: " + msg);
819                 }
820                 performTransitions(msgProcessedState, msg);
821 
822                 // We need to check if mSm == null here as we could be quitting.
823                 if (mDbg && mSm != null) mSm.log("handleMessage: X");
824 
825                 if (mSm != null && msg.what != SM_INIT_CMD && msg.what != SM_QUIT_CMD) {
826                     mSm.onPostHandleMessage(msg);
827                 }
828             }
829         }
830 
831         /**
832          * Do any transitions
833          * @param msgProcessedState is the state that processed the message
834          */
performTransitions(State msgProcessedState, Message msg)835         private void performTransitions(State msgProcessedState, Message msg) {
836             /**
837              * If transitionTo has been called, exit and then enter
838              * the appropriate states. We loop on this to allow
839              * enter and exit methods to use transitionTo.
840              */
841             State orgState = mStateStack[mStateStackTopIndex].state;
842 
843             /**
844              * Record whether message needs to be logged before we transition and
845              * and we won't log special messages SM_INIT_CMD or SM_QUIT_CMD which
846              * always set msg.obj to the handler.
847              */
848             boolean recordLogMsg = mSm.recordLogRec(mMsg) && (msg.obj != mSmHandlerObj);
849 
850             if (mLogRecords.logOnlyTransitions()) {
851                 /** Record only if there is a transition */
852                 if (mDestState != null) {
853                     mLogRecords.add(mSm, mMsg, mSm.getLogRecString(mMsg), msgProcessedState,
854                             orgState, mDestState);
855                 }
856             } else if (recordLogMsg) {
857                 /** Record message */
858                 mLogRecords.add(mSm, mMsg, mSm.getLogRecString(mMsg), msgProcessedState, orgState,
859                         mDestState);
860             }
861 
862             State destState = mDestState;
863             if (destState != null) {
864                 /**
865                  * Process the transitions including transitions in the enter/exit methods
866                  */
867                 while (true) {
868                     if (mDbg) mSm.log("handleMessage: new destination call exit/enter");
869 
870                     /**
871                      * Determine the states to exit and enter and return the
872                      * common ancestor state of the enter/exit states. Then
873                      * invoke the exit methods then the enter methods.
874                      */
875                     StateInfo commonStateInfo = setupTempStateStackWithStatesToEnter(destState);
876                     // flag is cleared in invokeEnterMethods before entering the target state
877                     mTransitionInProgress = true;
878                     invokeExitMethods(commonStateInfo);
879                     int stateStackEnteringIndex = moveTempStateStackToStateStack();
880                     invokeEnterMethods(stateStackEnteringIndex);
881 
882                     /**
883                      * Since we have transitioned to a new state we need to have
884                      * any deferred messages moved to the front of the message queue
885                      * so they will be processed before any other messages in the
886                      * message queue.
887                      */
888                     moveDeferredMessageAtFrontOfQueue();
889 
890                     if (destState != mDestState) {
891                         // A new mDestState so continue looping
892                         destState = mDestState;
893                     } else {
894                         // No change in mDestState so we're done
895                         break;
896                     }
897                 }
898                 mDestState = null;
899             }
900 
901             /**
902              * After processing all transitions check and
903              * see if the last transition was to quit or halt.
904              */
905             if (destState != null) {
906                 if (destState == mQuittingState) {
907                     /**
908                      * Call onQuitting to let subclasses cleanup.
909                      */
910                     mSm.onQuitting();
911                     cleanupAfterQuitting();
912                 } else if (destState == mHaltingState) {
913                     /**
914                      * Call onHalting() if we've transitioned to the halting
915                      * state. All subsequent messages will be processed in
916                      * in the halting state which invokes haltedProcessMessage(msg);
917                      */
918                     mSm.onHalting();
919                 }
920             }
921         }
922 
923         /**
924          * Cleanup all the static variables and the looper after the SM has been quit.
925          */
cleanupAfterQuitting()926         private final void cleanupAfterQuitting() {
927             if (mSm.mSmThread != null) {
928                 // If we made the thread then quit looper which stops the thread.
929                 getLooper().quit();
930                 mSm.mSmThread = null;
931             }
932 
933             mSm.mSmHandler = null;
934             mSm = null;
935             mMsg = null;
936             mLogRecords.cleanup();
937             mStateStack = null;
938             mTempStateStack = null;
939             mStateInfo.clear();
940             mInitialState = null;
941             mDestState = null;
942             mDeferredMessages.clear();
943             mHasQuit = true;
944         }
945 
946         /**
947          * Complete the construction of the state machine.
948          */
completeConstruction()949         private final void completeConstruction() {
950             if (mDbg) mSm.log("completeConstruction: E");
951 
952             /**
953              * Determine the maximum depth of the state hierarchy
954              * so we can allocate the state stacks.
955              */
956             int maxDepth = 0;
957             for (StateInfo si : mStateInfo.values()) {
958                 int depth = 0;
959                 for (StateInfo i = si; i != null; depth++) {
960                     i = i.parentStateInfo;
961                 }
962                 if (maxDepth < depth) {
963                     maxDepth = depth;
964                 }
965             }
966             if (mDbg) mSm.log("completeConstruction: maxDepth=" + maxDepth);
967 
968             mStateStack = new StateInfo[maxDepth];
969             mTempStateStack = new StateInfo[maxDepth];
970             setupInitialStateStack();
971 
972             /** Sending SM_INIT_CMD message to invoke enter methods asynchronously */
973             sendMessageAtFrontOfQueue(obtainMessage(SM_INIT_CMD, mSmHandlerObj));
974 
975             if (mDbg) mSm.log("completeConstruction: X");
976         }
977 
978         /**
979          * Process the message. If the current state doesn't handle
980          * it, call the states parent and so on. If it is never handled then
981          * call the state machines unhandledMessage method.
982          * @return the state that processed the message
983          */
processMsg(Message msg)984         private final State processMsg(Message msg) {
985             StateInfo curStateInfo = mStateStack[mStateStackTopIndex];
986             if (mDbg) {
987                 mSm.log("processMsg: " + curStateInfo.state.getName());
988             }
989 
990             if (isQuit(msg)) {
991                 transitionTo(mQuittingState);
992             } else {
993                 while (!curStateInfo.state.processMessage(msg)) {
994                     /**
995                      * Not processed
996                      */
997                     curStateInfo = curStateInfo.parentStateInfo;
998                     if (curStateInfo == null) {
999                         /**
1000                          * No parents left so it's not handled
1001                          */
1002                         mSm.unhandledMessage(msg);
1003                         break;
1004                     }
1005                     if (mDbg) {
1006                         mSm.log("processMsg: " + curStateInfo.state.getName());
1007                     }
1008                 }
1009             }
1010             return (curStateInfo != null) ? curStateInfo.state : null;
1011         }
1012 
1013         /**
1014          * Call the exit method for each state from the top of stack
1015          * up to the common ancestor state.
1016          */
invokeExitMethods(StateInfo commonStateInfo)1017         private final void invokeExitMethods(StateInfo commonStateInfo) {
1018             while ((mStateStackTopIndex >= 0)
1019                     && (mStateStack[mStateStackTopIndex] != commonStateInfo)) {
1020                 State curState = mStateStack[mStateStackTopIndex].state;
1021                 if (mDbg) mSm.log("invokeExitMethods: " + curState.getName());
1022                 curState.exit();
1023                 mStateStack[mStateStackTopIndex].active = false;
1024                 mStateStackTopIndex -= 1;
1025             }
1026         }
1027 
1028         /**
1029          * Invoke the enter method starting at the entering index to top of state stack
1030          */
invokeEnterMethods(int stateStackEnteringIndex)1031         private final void invokeEnterMethods(int stateStackEnteringIndex) {
1032             for (int i = stateStackEnteringIndex; i <= mStateStackTopIndex; i++) {
1033                 if (stateStackEnteringIndex == mStateStackTopIndex) {
1034                     // Last enter state for transition
1035                     mTransitionInProgress = false;
1036                 }
1037                 if (mDbg) mSm.log("invokeEnterMethods: " + mStateStack[i].state.getName());
1038                 mStateStack[i].state.enter();
1039                 mStateStack[i].active = true;
1040             }
1041             mTransitionInProgress = false; // ensure flag set to false if no methods called
1042         }
1043 
1044         /**
1045          * Move the deferred message to the front of the message queue.
1046          */
moveDeferredMessageAtFrontOfQueue()1047         private final void moveDeferredMessageAtFrontOfQueue() {
1048             /**
1049              * The oldest messages on the deferred list must be at
1050              * the front of the queue so start at the back, which
1051              * as the most resent message and end with the oldest
1052              * messages at the front of the queue.
1053              */
1054             for (int i = mDeferredMessages.size() - 1; i >= 0; i--) {
1055                 Message curMsg = mDeferredMessages.get(i);
1056                 if (mDbg) mSm.log("moveDeferredMessageAtFrontOfQueue; what=" + curMsg.what);
1057                 sendMessageAtFrontOfQueue(curMsg);
1058             }
1059             mDeferredMessages.clear();
1060         }
1061 
1062         /**
1063          * Move the contents of the temporary stack to the state stack
1064          * reversing the order of the items on the temporary stack as
1065          * they are moved.
1066          *
1067          * @return index into mStateStack where entering needs to start
1068          */
moveTempStateStackToStateStack()1069         private final int moveTempStateStackToStateStack() {
1070             int startingIndex = mStateStackTopIndex + 1;
1071             int i = mTempStateStackCount - 1;
1072             int j = startingIndex;
1073             while (i >= 0) {
1074                 if (mDbg) mSm.log("moveTempStackToStateStack: i=" + i + ",j=" + j);
1075                 mStateStack[j] = mTempStateStack[i];
1076                 j += 1;
1077                 i -= 1;
1078             }
1079 
1080             mStateStackTopIndex = j - 1;
1081             if (mDbg) {
1082                 mSm.log("moveTempStackToStateStack: X mStateStackTop=" + mStateStackTopIndex
1083                         + ",startingIndex=" + startingIndex + ",Top="
1084                         + mStateStack[mStateStackTopIndex].state.getName());
1085             }
1086             return startingIndex;
1087         }
1088 
1089         /**
1090          * Setup the mTempStateStack with the states we are going to enter.
1091          *
1092          * This is found by searching up the destState's ancestors for a
1093          * state that is already active i.e. StateInfo.active == true.
1094          * The destStae and all of its inactive parents will be on the
1095          * TempStateStack as the list of states to enter.
1096          *
1097          * @return StateInfo of the common ancestor for the destState and
1098          * current state or null if there is no common parent.
1099          */
setupTempStateStackWithStatesToEnter(State destState)1100         private final StateInfo setupTempStateStackWithStatesToEnter(State destState) {
1101             /**
1102              * Search up the parent list of the destination state for an active
1103              * state. Use a do while() loop as the destState must always be entered
1104              * even if it is active. This can happen if we are exiting/entering
1105              * the current state.
1106              */
1107             mTempStateStackCount = 0;
1108             StateInfo curStateInfo = mStateInfo.get(destState);
1109             do {
1110                 mTempStateStack[mTempStateStackCount++] = curStateInfo;
1111                 curStateInfo = curStateInfo.parentStateInfo;
1112             } while ((curStateInfo != null) && !curStateInfo.active);
1113 
1114             if (mDbg) {
1115                 mSm.log("setupTempStateStackWithStatesToEnter: X mTempStateStackCount="
1116                         + mTempStateStackCount + ",curStateInfo: " + curStateInfo);
1117             }
1118             return curStateInfo;
1119         }
1120 
1121         /**
1122          * Initialize StateStack to mInitialState.
1123          */
setupInitialStateStack()1124         private final void setupInitialStateStack() {
1125             if (mDbg) {
1126                 mSm.log("setupInitialStateStack: E mInitialState=" + mInitialState.getName());
1127             }
1128 
1129             StateInfo curStateInfo = mStateInfo.get(mInitialState);
1130             for (mTempStateStackCount = 0; curStateInfo != null; mTempStateStackCount++) {
1131                 mTempStateStack[mTempStateStackCount] = curStateInfo;
1132                 curStateInfo = curStateInfo.parentStateInfo;
1133             }
1134 
1135             // Empty the StateStack
1136             mStateStackTopIndex = -1;
1137 
1138             moveTempStateStackToStateStack();
1139         }
1140 
1141         /**
1142          * @return current message
1143          */
getCurrentMessage()1144         private final Message getCurrentMessage() {
1145             return mMsg;
1146         }
1147 
1148         /**
1149          * @return current state
1150          */
getCurrentState()1151         private final IState getCurrentState() {
1152             return mStateStack[mStateStackTopIndex].state;
1153         }
1154 
1155         /**
1156          * Add a new state to the state machine. Bottom up addition
1157          * of states is allowed but the same state may only exist
1158          * in one hierarchy.
1159          *
1160          * @param state the state to add
1161          * @param parent the parent of state
1162          * @return stateInfo for this state
1163          */
addState(State state, State parent)1164         private final StateInfo addState(State state, State parent) {
1165             if (mDbg) {
1166                 mSm.log("addStateInternal: E state=" + state.getName() + ",parent="
1167                         + ((parent == null) ? "" : parent.getName()));
1168             }
1169             StateInfo parentStateInfo = null;
1170             if (parent != null) {
1171                 parentStateInfo = mStateInfo.get(parent);
1172                 if (parentStateInfo == null) {
1173                     // Recursively add our parent as it's not been added yet.
1174                     parentStateInfo = addState(parent, null);
1175                 }
1176             }
1177             StateInfo stateInfo = mStateInfo.get(state);
1178             if (stateInfo == null) {
1179                 stateInfo = new StateInfo();
1180                 mStateInfo.put(state, stateInfo);
1181             }
1182 
1183             // Validate that we aren't adding the same state in two different hierarchies.
1184             if ((stateInfo.parentStateInfo != null)
1185                     && (stateInfo.parentStateInfo != parentStateInfo)) {
1186                 throw new RuntimeException("state already added");
1187             }
1188             stateInfo.state = state;
1189             stateInfo.parentStateInfo = parentStateInfo;
1190             stateInfo.active = false;
1191             if (mDbg) mSm.log("addStateInternal: X stateInfo: " + stateInfo);
1192             return stateInfo;
1193         }
1194 
1195         /**
1196          * Remove a state from the state machine. Will not remove the state if it is currently
1197          * active or if it has any children in the hierarchy.
1198          * @param state the state to remove
1199          */
removeState(State state)1200         private void removeState(State state) {
1201             StateInfo stateInfo = mStateInfo.get(state);
1202             if (stateInfo == null || stateInfo.active) {
1203                 return;
1204             }
1205             boolean isParent = mStateInfo.values().stream()
1206                     .filter(si -> si.parentStateInfo == stateInfo)
1207                     .findAny()
1208                     .isPresent();
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<LogRec>();
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
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
2084      * @param pw
2085      * @param args
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 name = "(null)";
2102         String state = "(null)";
2103         try {
2104             name = mName.toString();
2105             state = mSmHandler.getCurrentState().getName().toString();
2106         } catch (NullPointerException | ArrayIndexOutOfBoundsException e) {
2107             // Will use default(s) initialized above.
2108         }
2109         return "name=" + name + " state=" + state;
2110     }
2111 
2112     /**
2113      * Log with debug and add to the LogRecords.
2114      *
2115      * @param s is string log
2116      */
logAndAddLogRec(String s)2117     protected void logAndAddLogRec(String s) {
2118         addLogRec(s);
2119         log(s);
2120     }
2121 
2122     /**
2123      * Log with debug
2124      *
2125      * @param s is string log
2126      */
log(String s)2127     protected void log(String s) {
2128         Log.d(mName, s);
2129     }
2130 
2131     /**
2132      * Log with debug attribute
2133      *
2134      * @param s is string log
2135      */
logd(String s)2136     protected void logd(String s) {
2137         Log.d(mName, s);
2138     }
2139 
2140     /**
2141      * Log with verbose attribute
2142      *
2143      * @param s is string log
2144      */
logv(String s)2145     protected void logv(String s) {
2146         Log.v(mName, s);
2147     }
2148 
2149     /**
2150      * Log with info attribute
2151      *
2152      * @param s is string log
2153      */
logi(String s)2154     protected void logi(String s) {
2155         Log.i(mName, s);
2156     }
2157 
2158     /**
2159      * Log with warning attribute
2160      *
2161      * @param s is string log
2162      */
logw(String s)2163     protected void logw(String s) {
2164         Log.w(mName, s);
2165     }
2166 
2167     /**
2168      * Log with error attribute
2169      *
2170      * @param s is string log
2171      */
loge(String s)2172     protected void loge(String s) {
2173         Log.e(mName, s);
2174     }
2175 
2176     /**
2177      * Log with error attribute
2178      *
2179      * @param s is string log
2180      * @param e is a Throwable which logs additional information.
2181      */
loge(String s, Throwable e)2182     protected void loge(String s, Throwable e) {
2183         Log.e(mName, s, e);
2184     }
2185 }
2186