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