1 /*
2  * Copyright (C) 2019 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.bluetooth.statemachine;
18 
19 import android.os.Handler;
20 import android.os.HandlerThread;
21 import android.os.Looper;
22 import android.os.Message;
23 import android.text.TextUtils;
24 import android.util.Log;
25 
26 import com.android.internal.annotations.VisibleForTesting;
27 
28 import java.io.FileDescriptor;
29 import java.io.PrintWriter;
30 import java.util.ArrayList;
31 import java.util.Calendar;
32 import java.util.Collection;
33 import java.util.HashMap;
34 import java.util.Iterator;
35 import java.util.Vector;
36 
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 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 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 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      */
450     public static class LogRec {
451         private StateMachine mSm;
452         private long mTime;
453         private int mWhat;
454         private String mInfo;
455         private IState mState;
456         private IState mOrgState;
457         private IState mDstState;
458 
459         /**
460          * Constructor
461          *
462          * @param msg
463          * @param state the state which handled the message
464          * @param orgState is the first state the received the message but
465          * did not processes the message.
466          * @param transToState is the state that was transitioned to after the message was
467          * processed.
468          */
LogRec(StateMachine sm, Message msg, String info, IState state, IState orgState, IState transToState)469         LogRec(StateMachine sm, Message msg, String info, IState state, IState orgState,
470                 IState transToState) {
471             update(sm, msg, info, state, orgState, transToState);
472         }
473 
474         /**
475          * Update the information in the record.
476          * @param state that handled the message
477          * @param orgState is the first state the received the message
478          * @param dstState is the state that was the transition target when logging
479          */
update(StateMachine sm, Message msg, String info, IState state, IState orgState, IState dstState)480         public void update(StateMachine sm, Message msg, String info, IState state, IState orgState,
481                 IState dstState) {
482             mSm = sm;
483             mTime = System.currentTimeMillis();
484             mWhat = (msg != null) ? msg.what : 0;
485             mInfo = info;
486             mState = state;
487             mOrgState = orgState;
488             mDstState = dstState;
489         }
490 
491         /**
492          * @return time stamp
493          */
getTime()494         public long getTime() {
495             return mTime;
496         }
497 
498         /**
499          * @return msg.what
500          */
getWhat()501         public long getWhat() {
502             return mWhat;
503         }
504 
505         /**
506          * @return the command that was executing
507          */
getInfo()508         public String getInfo() {
509             return mInfo;
510         }
511 
512         /**
513          * @return the state that handled this message
514          */
getState()515         public IState getState() {
516             return mState;
517         }
518 
519         /**
520          * @return the state destination state if a transition is occurring or null if none.
521          */
getDestState()522         public IState getDestState() {
523             return mDstState;
524         }
525 
526         /**
527          * @return the original state that received the message.
528          */
getOriginalState()529         public IState getOriginalState() {
530             return mOrgState;
531         }
532 
533         @Override
toString()534         public String toString() {
535             StringBuilder sb = new StringBuilder();
536             sb.append("time=");
537             Calendar c = Calendar.getInstance();
538             c.setTimeInMillis(mTime);
539             sb.append(String.format("%tm-%td %tH:%tM:%tS.%tL", c, c, c, c, c, c));
540             sb.append(" processed=");
541             sb.append(mState == null ? "<null>" : mState.getName());
542             sb.append(" org=");
543             sb.append(mOrgState == null ? "<null>" : mOrgState.getName());
544             sb.append(" dest=");
545             sb.append(mDstState == null ? "<null>" : mDstState.getName());
546             sb.append(" what=");
547             String what = mSm != null ? mSm.getWhatToString(mWhat) : "";
548             if (TextUtils.isEmpty(what)) {
549                 sb.append(mWhat);
550                 sb.append("(0x");
551                 sb.append(Integer.toHexString(mWhat));
552                 sb.append(")");
553             } else {
554                 sb.append(what);
555             }
556             if (!TextUtils.isEmpty(mInfo)) {
557                 sb.append(" ");
558                 sb.append(mInfo);
559             }
560             return sb.toString();
561         }
562     }
563 
564     /**
565      * A list of log records including messages recently processed by the state machine.
566      *
567      * The class maintains a list of log records including messages
568      * recently processed. The list is finite and may be set in the
569      * constructor or by calling setSize. The public interface also
570      * includes size which returns the number of recent records,
571      * count which is the number of records processed since the
572      * the last setSize, get which returns a record and
573      * add which adds a record.
574      */
575     private static class LogRecords {
576 
577         private static final int DEFAULT_SIZE = 20;
578 
579         private Vector<LogRec> mLogRecVector = new Vector<LogRec>();
580         private int mMaxSize = DEFAULT_SIZE;
581         private int mOldestIndex = 0;
582         private int mCount = 0;
583         private boolean mLogOnlyTransitions = false;
584 
585         /**
586          * private constructor use add
587          */
LogRecords()588         private LogRecords() {
589         }
590 
591         /**
592          * Set size of messages to maintain and clears all current records.
593          *
594          * @param maxSize number of records to maintain at anyone time.
595          */
setSize(int maxSize)596         synchronized void setSize(int maxSize) {
597             // TODO: once b/28217358 is fixed, add unit tests  to verify that these variables are
598             // cleared after calling this method, and that subsequent calls to get() function as
599             // expected.
600             mMaxSize = maxSize;
601             mOldestIndex = 0;
602             mCount = 0;
603             mLogRecVector.clear();
604         }
605 
setLogOnlyTransitions(boolean enable)606         synchronized void setLogOnlyTransitions(boolean enable) {
607             mLogOnlyTransitions = enable;
608         }
609 
logOnlyTransitions()610         synchronized boolean logOnlyTransitions() {
611             return mLogOnlyTransitions;
612         }
613 
614         /**
615          * @return the number of recent records.
616          */
size()617         synchronized int size() {
618             return mLogRecVector.size();
619         }
620 
621         /**
622          * @return the total number of records processed since size was set.
623          */
count()624         synchronized int count() {
625             return mCount;
626         }
627 
628         /**
629          * Clear the list of records.
630          */
cleanup()631         synchronized void cleanup() {
632             mLogRecVector.clear();
633         }
634 
635         /**
636          * @return the information on a particular record. 0 is the oldest
637          * record and size()-1 is the newest record. If the index is to
638          * large null is returned.
639          */
get(int index)640         synchronized LogRec get(int index) {
641             int nextIndex = mOldestIndex + index;
642             if (nextIndex >= mMaxSize) {
643                 nextIndex -= mMaxSize;
644             }
645             if (nextIndex >= size()) {
646                 return null;
647             } else {
648                 return mLogRecVector.get(nextIndex);
649             }
650         }
651 
652         /**
653          * Add a processed message.
654          *
655          * @param msg
656          * @param messageInfo to be stored
657          * @param state that handled the message
658          * @param orgState is the first state the received the message but
659          * did not processes the message.
660          * @param transToState is the state that was transitioned to after the message was
661          * processed.
662          *
663          */
add(StateMachine sm, Message msg, String messageInfo, IState state, IState orgState, IState transToState)664         synchronized void add(StateMachine sm, Message msg, String messageInfo, IState state,
665                 IState orgState, IState transToState) {
666             mCount += 1;
667             if (mLogRecVector.size() < mMaxSize) {
668                 mLogRecVector.add(new LogRec(sm, msg, messageInfo, state, orgState, transToState));
669             } else {
670                 LogRec pmi = mLogRecVector.get(mOldestIndex);
671                 mOldestIndex += 1;
672                 if (mOldestIndex >= mMaxSize) {
673                     mOldestIndex = 0;
674                 }
675                 pmi.update(sm, msg, messageInfo, state, orgState, transToState);
676             }
677         }
678     }
679 
680     private static class SmHandler extends Handler {
681 
682         /** true if StateMachine has quit */
683         private boolean mHasQuit = false;
684 
685         /** The debug flag */
686         private boolean mDbg = false;
687 
688         /** The SmHandler object, identifies that message is internal */
689         private static final Object mSmHandlerObj = new Object();
690 
691         /** The current message */
692         private Message mMsg;
693 
694         /** A list of log records including messages this state machine has processed */
695         private LogRecords mLogRecords = new LogRecords();
696 
697         /** true if construction of the state machine has not been completed */
698         private boolean mIsConstructionCompleted;
699 
700         /** Stack used to manage the current hierarchy of states */
701         private StateInfo mStateStack[];
702 
703         /** Top of mStateStack */
704         private int mStateStackTopIndex = -1;
705 
706         /** A temporary stack used to manage the state stack */
707         private StateInfo mTempStateStack[];
708 
709         /** The top of the mTempStateStack */
710         private int mTempStateStackCount;
711 
712         /** State used when state machine is halted */
713         private HaltingState mHaltingState = new HaltingState();
714 
715         /** State used when state machine is quitting */
716         private QuittingState mQuittingState = new QuittingState();
717 
718         /** Reference to the StateMachine */
719         private StateMachine mSm;
720 
721         /**
722          * Information about a state.
723          * Used to maintain the hierarchy.
724          */
725         private class StateInfo {
726             /** The state */
727             State state;
728 
729             /** The parent of this state, null if there is no parent */
730             StateInfo parentStateInfo;
731 
732             /** True when the state has been entered and on the stack */
733             boolean active;
734 
735             /**
736              * Convert StateInfo to string
737              */
738             @Override
toString()739             public String toString() {
740                 return "state=" + state.getName() + ",active=" + active + ",parent="
741                         + ((parentStateInfo == null) ? "null" : parentStateInfo.state.getName());
742             }
743         }
744 
745         /** The map of all of the states in the state machine */
746         private HashMap<State, StateInfo> mStateInfo = new HashMap<State, StateInfo>();
747 
748         /** The initial state that will process the first message */
749         private State mInitialState;
750 
751         /** The destination state when transitionTo has been invoked */
752         private State mDestState;
753 
754         /**
755          * Indicates if a transition is in progress
756          *
757          * This will be true for all calls of State.exit and all calls of State.enter except for the
758          * last enter call for the current destination state.
759          */
760         private boolean mTransitionInProgress = false;
761 
762         /** The list of deferred messages */
763         private ArrayList<Message> mDeferredMessages = new ArrayList<Message>();
764 
765         /**
766          * State entered when transitionToHaltingState is called.
767          */
768         private class HaltingState extends State {
769             @Override
processMessage(Message msg)770             public boolean processMessage(Message msg) {
771                 mSm.haltedProcessMessage(msg);
772                 return true;
773             }
774         }
775 
776         /**
777          * State entered when a valid quit message is handled.
778          */
779         private class QuittingState extends State {
780             @Override
processMessage(Message msg)781             public boolean processMessage(Message msg) {
782                 return NOT_HANDLED;
783             }
784         }
785 
786         /**
787          * Handle messages sent to the state machine by calling
788          * the current state's processMessage. It also handles
789          * the enter/exit calls and placing any deferred messages
790          * back onto the queue when transitioning to a new state.
791          */
792         @Override
handleMessage(Message msg)793         public final void handleMessage(Message msg) {
794             if (!mHasQuit) {
795                 if (mSm != null && msg.what != SM_INIT_CMD && msg.what != SM_QUIT_CMD) {
796                     mSm.onPreHandleMessage(msg);
797                 }
798 
799                 if (mDbg) mSm.log("handleMessage: E msg.what=" + msg.what);
800 
801                 /** Save the current message */
802                 mMsg = msg;
803 
804                 /** State that processed the message */
805                 State msgProcessedState = null;
806                 if (mIsConstructionCompleted || (mMsg.what == SM_QUIT_CMD)) {
807                     /** Normal path */
808                     msgProcessedState = processMsg(msg);
809                 } else if (!mIsConstructionCompleted && (mMsg.what == SM_INIT_CMD)
810                         && (mMsg.obj == mSmHandlerObj)) {
811                     /** Initial one time path. */
812                     mIsConstructionCompleted = true;
813                     invokeEnterMethods(0);
814                 } else {
815                     throw new RuntimeException("StateMachine.handleMessage: "
816                             + "The start method not called, received msg: " + msg);
817                 }
818                 performTransitions(msgProcessedState, msg);
819 
820                 // We need to check if mSm == null here as we could be quitting.
821                 if (mDbg && mSm != null) mSm.log("handleMessage: X");
822 
823                 if (mSm != null && msg.what != SM_INIT_CMD && msg.what != SM_QUIT_CMD) {
824                     mSm.onPostHandleMessage(msg);
825                 }
826             }
827         }
828 
829         /**
830          * Do any transitions
831          * @param msgProcessedState is the state that processed the message
832          */
performTransitions(State msgProcessedState, Message msg)833         private void performTransitions(State msgProcessedState, Message msg) {
834             /**
835              * If transitionTo has been called, exit and then enter
836              * the appropriate states. We loop on this to allow
837              * enter and exit methods to use transitionTo.
838              */
839             State orgState = mStateStack[mStateStackTopIndex].state;
840 
841             /**
842              * Record whether message needs to be logged before we transition and
843              * and we won't log special messages SM_INIT_CMD or SM_QUIT_CMD which
844              * always set msg.obj to the handler.
845              */
846             boolean recordLogMsg = mSm.recordLogRec(mMsg) && (msg.obj != mSmHandlerObj);
847 
848             if (mLogRecords.logOnlyTransitions()) {
849                 /** Record only if there is a transition */
850                 if (mDestState != null) {
851                     mLogRecords.add(mSm, mMsg, mSm.getLogRecString(mMsg), msgProcessedState,
852                             orgState, mDestState);
853                 }
854             } else if (recordLogMsg) {
855                 /** Record message */
856                 mLogRecords.add(mSm, mMsg, mSm.getLogRecString(mMsg), msgProcessedState, orgState,
857                         mDestState);
858             }
859 
860             State destState = mDestState;
861             if (destState != null) {
862                 /**
863                  * Process the transitions including transitions in the enter/exit methods
864                  */
865                 while (true) {
866                     if (mDbg) mSm.log("handleMessage: new destination call exit/enter");
867 
868                     /**
869                      * Determine the states to exit and enter and return the
870                      * common ancestor state of the enter/exit states. Then
871                      * invoke the exit methods then the enter methods.
872                      */
873                     StateInfo commonStateInfo = setupTempStateStackWithStatesToEnter(destState);
874                     // flag is cleared in invokeEnterMethods before entering the target state
875                     mTransitionInProgress = true;
876                     invokeExitMethods(commonStateInfo);
877                     int stateStackEnteringIndex = moveTempStateStackToStateStack();
878                     invokeEnterMethods(stateStackEnteringIndex);
879 
880                     /**
881                      * Since we have transitioned to a new state we need to have
882                      * any deferred messages moved to the front of the message queue
883                      * so they will be processed before any other messages in the
884                      * message queue.
885                      */
886                     moveDeferredMessageAtFrontOfQueue();
887 
888                     if (destState != mDestState) {
889                         // A new mDestState so continue looping
890                         destState = mDestState;
891                     } else {
892                         // No change in mDestState so we're done
893                         break;
894                     }
895                 }
896                 mDestState = null;
897             }
898 
899             /**
900              * After processing all transitions check and
901              * see if the last transition was to quit or halt.
902              */
903             if (destState != null) {
904                 if (destState == mQuittingState) {
905                     /**
906                      * Call onQuitting to let subclasses cleanup.
907                      */
908                     mSm.onQuitting();
909                     cleanupAfterQuitting();
910                 } else if (destState == mHaltingState) {
911                     /**
912                      * Call onHalting() if we've transitioned to the halting
913                      * state. All subsequent messages will be processed in
914                      * in the halting state which invokes haltedProcessMessage(msg);
915                      */
916                     mSm.onHalting();
917                 }
918             }
919         }
920 
921         /**
922          * Cleanup all the static variables and the looper after the SM has been quit.
923          */
cleanupAfterQuitting()924         private final void cleanupAfterQuitting() {
925             if (mSm.mSmThread != null) {
926                 // If we made the thread then quit looper which stops the thread.
927                 getLooper().quit();
928                 mSm.mSmThread = null;
929             }
930 
931             mSm.mSmHandler = null;
932             mSm = null;
933             mMsg = null;
934             mLogRecords.cleanup();
935             mStateStack = null;
936             mTempStateStack = null;
937             mStateInfo.clear();
938             mInitialState = null;
939             mDestState = null;
940             mDeferredMessages.clear();
941             mHasQuit = true;
942         }
943 
944         /**
945          * Complete the construction of the state machine.
946          */
completeConstruction()947         private final void completeConstruction() {
948             if (mDbg) mSm.log("completeConstruction: E");
949 
950             /**
951              * Determine the maximum depth of the state hierarchy
952              * so we can allocate the state stacks.
953              */
954             int maxDepth = 0;
955             for (StateInfo si : mStateInfo.values()) {
956                 int depth = 0;
957                 for (StateInfo i = si; i != null; depth++) {
958                     i = i.parentStateInfo;
959                 }
960                 if (maxDepth < depth) {
961                     maxDepth = depth;
962                 }
963             }
964             if (mDbg) mSm.log("completeConstruction: maxDepth=" + maxDepth);
965 
966             mStateStack = new StateInfo[maxDepth];
967             mTempStateStack = new StateInfo[maxDepth];
968             setupInitialStateStack();
969 
970             /** Sending SM_INIT_CMD message to invoke enter methods asynchronously */
971             sendMessageAtFrontOfQueue(obtainMessage(SM_INIT_CMD, mSmHandlerObj));
972 
973             if (mDbg) mSm.log("completeConstruction: X");
974         }
975 
976         /**
977          * Process the message. If the current state doesn't handle
978          * it, call the states parent and so on. If it is never handled then
979          * call the state machines unhandledMessage method.
980          * @return the state that processed the message
981          */
processMsg(Message msg)982         private final State processMsg(Message msg) {
983             StateInfo curStateInfo = mStateStack[mStateStackTopIndex];
984             if (mDbg) {
985                 mSm.log("processMsg: " + curStateInfo.state.getName());
986             }
987 
988             if (isQuit(msg)) {
989                 transitionTo(mQuittingState);
990             } else {
991                 while (!curStateInfo.state.processMessage(msg)) {
992                     /**
993                      * Not processed
994                      */
995                     curStateInfo = curStateInfo.parentStateInfo;
996                     if (curStateInfo == null) {
997                         /**
998                          * No parents left so it's not handled
999                          */
1000                         mSm.unhandledMessage(msg);
1001                         break;
1002                     }
1003                     if (mDbg) {
1004                         mSm.log("processMsg: " + curStateInfo.state.getName());
1005                     }
1006                 }
1007             }
1008             return (curStateInfo != null) ? curStateInfo.state : null;
1009         }
1010 
1011         /**
1012          * Call the exit method for each state from the top of stack
1013          * up to the common ancestor state.
1014          */
invokeExitMethods(StateInfo commonStateInfo)1015         private final void invokeExitMethods(StateInfo commonStateInfo) {
1016             while ((mStateStackTopIndex >= 0)
1017                     && (mStateStack[mStateStackTopIndex] != commonStateInfo)) {
1018                 State curState = mStateStack[mStateStackTopIndex].state;
1019                 if (mDbg) mSm.log("invokeExitMethods: " + curState.getName());
1020                 curState.exit();
1021                 mStateStack[mStateStackTopIndex].active = false;
1022                 mStateStackTopIndex -= 1;
1023             }
1024         }
1025 
1026         /**
1027          * Invoke the enter method starting at the entering index to top of state stack
1028          */
invokeEnterMethods(int stateStackEnteringIndex)1029         private final void invokeEnterMethods(int stateStackEnteringIndex) {
1030             for (int i = stateStackEnteringIndex; i <= mStateStackTopIndex; i++) {
1031                 if (stateStackEnteringIndex == mStateStackTopIndex) {
1032                     // Last enter state for transition
1033                     mTransitionInProgress = false;
1034                 }
1035                 if (mDbg) mSm.log("invokeEnterMethods: " + mStateStack[i].state.getName());
1036                 mStateStack[i].state.enter();
1037                 mStateStack[i].active = true;
1038             }
1039             mTransitionInProgress = false; // ensure flag set to false if no methods called
1040         }
1041 
1042         /**
1043          * Move the deferred message to the front of the message queue.
1044          */
moveDeferredMessageAtFrontOfQueue()1045         private final void moveDeferredMessageAtFrontOfQueue() {
1046             /**
1047              * The oldest messages on the deferred list must be at
1048              * the front of the queue so start at the back, which
1049              * as the most resent message and end with the oldest
1050              * messages at the front of the queue.
1051              */
1052             for (int i = mDeferredMessages.size() - 1; i >= 0; i--) {
1053                 Message curMsg = mDeferredMessages.get(i);
1054                 if (mDbg) mSm.log("moveDeferredMessageAtFrontOfQueue; what=" + curMsg.what);
1055                 sendMessageAtFrontOfQueue(curMsg);
1056             }
1057             mDeferredMessages.clear();
1058         }
1059 
1060         /**
1061          * Move the contents of the temporary stack to the state stack
1062          * reversing the order of the items on the temporary stack as
1063          * they are moved.
1064          *
1065          * @return index into mStateStack where entering needs to start
1066          */
moveTempStateStackToStateStack()1067         private final int moveTempStateStackToStateStack() {
1068             int startingIndex = mStateStackTopIndex + 1;
1069             int i = mTempStateStackCount - 1;
1070             int j = startingIndex;
1071             while (i >= 0) {
1072                 if (mDbg) mSm.log("moveTempStackToStateStack: i=" + i + ",j=" + j);
1073                 mStateStack[j] = mTempStateStack[i];
1074                 j += 1;
1075                 i -= 1;
1076             }
1077 
1078             mStateStackTopIndex = j - 1;
1079             if (mDbg) {
1080                 mSm.log("moveTempStackToStateStack: X mStateStackTop=" + mStateStackTopIndex
1081                         + ",startingIndex=" + startingIndex + ",Top="
1082                         + mStateStack[mStateStackTopIndex].state.getName());
1083             }
1084             return startingIndex;
1085         }
1086 
1087         /**
1088          * Setup the mTempStateStack with the states we are going to enter.
1089          *
1090          * This is found by searching up the destState's ancestors for a
1091          * state that is already active i.e. StateInfo.active == true.
1092          * The destStae and all of its inactive parents will be on the
1093          * TempStateStack as the list of states to enter.
1094          *
1095          * @return StateInfo of the common ancestor for the destState and
1096          * current state or null if there is no common parent.
1097          */
setupTempStateStackWithStatesToEnter(State destState)1098         private final StateInfo setupTempStateStackWithStatesToEnter(State destState) {
1099             /**
1100              * Search up the parent list of the destination state for an active
1101              * state. Use a do while() loop as the destState must always be entered
1102              * even if it is active. This can happen if we are exiting/entering
1103              * the current state.
1104              */
1105             mTempStateStackCount = 0;
1106             StateInfo curStateInfo = mStateInfo.get(destState);
1107             do {
1108                 mTempStateStack[mTempStateStackCount++] = curStateInfo;
1109                 curStateInfo = curStateInfo.parentStateInfo;
1110             } while ((curStateInfo != null) && !curStateInfo.active);
1111 
1112             if (mDbg) {
1113                 mSm.log("setupTempStateStackWithStatesToEnter: X mTempStateStackCount="
1114                         + mTempStateStackCount + ",curStateInfo: " + curStateInfo);
1115             }
1116             return curStateInfo;
1117         }
1118 
1119         /**
1120          * Initialize StateStack to mInitialState.
1121          */
setupInitialStateStack()1122         private final void setupInitialStateStack() {
1123             if (mDbg) {
1124                 mSm.log("setupInitialStateStack: E mInitialState=" + mInitialState.getName());
1125             }
1126 
1127             StateInfo curStateInfo = mStateInfo.get(mInitialState);
1128             for (mTempStateStackCount = 0; curStateInfo != null; mTempStateStackCount++) {
1129                 mTempStateStack[mTempStateStackCount] = curStateInfo;
1130                 curStateInfo = curStateInfo.parentStateInfo;
1131             }
1132 
1133             // Empty the StateStack
1134             mStateStackTopIndex = -1;
1135 
1136             moveTempStateStackToStateStack();
1137         }
1138 
1139         /**
1140          * @return current message
1141          */
getCurrentMessage()1142         private final Message getCurrentMessage() {
1143             return mMsg;
1144         }
1145 
1146         /**
1147          * @return current state
1148          */
getCurrentState()1149         private final IState getCurrentState() {
1150             return mStateStack[mStateStackTopIndex].state;
1151         }
1152 
1153         /**
1154          * Add a new state to the state machine. Bottom up addition
1155          * of states is allowed but the same state may only exist
1156          * in one hierarchy.
1157          *
1158          * @param state the state to add
1159          * @param parent the parent of state
1160          * @return stateInfo for this state
1161          */
addState(State state, State parent)1162         private final StateInfo addState(State state, State parent) {
1163             if (mDbg) {
1164                 mSm.log("addStateInternal: E state=" + state.getName() + ",parent="
1165                         + ((parent == null) ? "" : parent.getName()));
1166             }
1167             StateInfo parentStateInfo = null;
1168             if (parent != null) {
1169                 parentStateInfo = mStateInfo.get(parent);
1170                 if (parentStateInfo == null) {
1171                     // Recursively add our parent as it's not been added yet.
1172                     parentStateInfo = addState(parent, null);
1173                 }
1174             }
1175             StateInfo stateInfo = mStateInfo.get(state);
1176             if (stateInfo == null) {
1177                 stateInfo = new StateInfo();
1178                 mStateInfo.put(state, stateInfo);
1179             }
1180 
1181             // Validate that we aren't adding the same state in two different hierarchies.
1182             if ((stateInfo.parentStateInfo != null)
1183                     && (stateInfo.parentStateInfo != parentStateInfo)) {
1184                 throw new RuntimeException("state already added");
1185             }
1186             stateInfo.state = state;
1187             stateInfo.parentStateInfo = parentStateInfo;
1188             stateInfo.active = false;
1189             if (mDbg) mSm.log("addStateInternal: X stateInfo: " + stateInfo);
1190             return stateInfo;
1191         }
1192 
1193         /**
1194          * Remove a state from the state machine. Will not remove the state if it is currently
1195          * active or if it has any children in the hierarchy.
1196          * @param state the state to remove
1197          */
removeState(State state)1198         private void removeState(State state) {
1199             StateInfo stateInfo = mStateInfo.get(state);
1200             if (stateInfo == null || stateInfo.active) {
1201                 return;
1202             }
1203             boolean isParent = mStateInfo.values().stream()
1204                     .filter(si -> si.parentStateInfo == stateInfo)
1205                     .findAny()
1206                     .isPresent();
1207             if (isParent) {
1208                 return;
1209             }
1210             mStateInfo.remove(state);
1211         }
1212 
1213         /**
1214          * Constructor
1215          *
1216          * @param looper for dispatching messages
1217          * @param sm the hierarchical state machine
1218          */
SmHandler(Looper looper, StateMachine sm)1219         private SmHandler(Looper looper, StateMachine sm) {
1220             super(looper);
1221             mSm = sm;
1222 
1223             addState(mHaltingState, null);
1224             addState(mQuittingState, null);
1225         }
1226 
1227         /** @see StateMachine#setInitialState(State) */
setInitialState(State initialState)1228         private final void setInitialState(State initialState) {
1229             if (mDbg) mSm.log("setInitialState: initialState=" + initialState.getName());
1230             mInitialState = initialState;
1231         }
1232 
1233         /** @see StateMachine#transitionTo(IState) */
transitionTo(IState destState)1234         private final void transitionTo(IState destState) {
1235             if (mTransitionInProgress) {
1236                 Log.wtf(mSm.mName, "transitionTo called while transition already in progress to " +
1237                         mDestState + ", new target state=" + destState);
1238             }
1239             mDestState = (State) destState;
1240             if (mDbg) mSm.log("transitionTo: destState=" + mDestState.getName());
1241         }
1242 
1243         /** @see StateMachine#deferMessage(Message) */
deferMessage(Message msg)1244         private final void deferMessage(Message msg) {
1245             if (mDbg) mSm.log("deferMessage: msg=" + msg.what);
1246 
1247             /* Copy the "msg" to "newMsg" as "msg" will be recycled */
1248             Message newMsg = obtainMessage();
1249             newMsg.copyFrom(msg);
1250 
1251             mDeferredMessages.add(newMsg);
1252         }
1253 
1254         /** @see StateMachine#quit() */
quit()1255         private final void quit() {
1256             if (mDbg) mSm.log("quit:");
1257             sendMessage(obtainMessage(SM_QUIT_CMD, mSmHandlerObj));
1258         }
1259 
1260         /** @see StateMachine#quitNow() */
quitNow()1261         private final void quitNow() {
1262             if (mDbg) mSm.log("quitNow:");
1263             sendMessageAtFrontOfQueue(obtainMessage(SM_QUIT_CMD, mSmHandlerObj));
1264         }
1265 
1266         /** Validate that the message was sent by quit or quitNow. */
isQuit(Message msg)1267         private final boolean isQuit(Message msg) {
1268             return (msg.what == SM_QUIT_CMD) && (msg.obj == mSmHandlerObj);
1269         }
1270 
1271         /** @see StateMachine#isDbg() */
isDbg()1272         private final boolean isDbg() {
1273             return mDbg;
1274         }
1275 
1276         /** @see StateMachine#setDbg(boolean) */
setDbg(boolean dbg)1277         private final void setDbg(boolean dbg) {
1278             mDbg = dbg;
1279         }
1280 
1281     }
1282 
1283     private SmHandler mSmHandler;
1284     private HandlerThread mSmThread;
1285 
1286     /**
1287      * Initialize.
1288      *
1289      * @param looper for this state machine
1290      * @param name of the state machine
1291      */
initStateMachine(String name, Looper looper)1292     private void initStateMachine(String name, Looper looper) {
1293         mName = name;
1294         mSmHandler = new SmHandler(looper, this);
1295     }
1296 
1297     /**
1298      * Constructor creates a StateMachine with its own thread.
1299      *
1300      * @param name of the state machine
1301      */
StateMachine(String name)1302     protected StateMachine(String name) {
1303         mSmThread = new HandlerThread(name);
1304         mSmThread.start();
1305         Looper looper = mSmThread.getLooper();
1306 
1307         initStateMachine(name, looper);
1308     }
1309 
1310     /**
1311      * Constructor creates a StateMachine using the looper.
1312      *
1313      * @param name of the state machine
1314      */
StateMachine(String name, Looper looper)1315     protected StateMachine(String name, Looper looper) {
1316         initStateMachine(name, looper);
1317     }
1318 
1319     /**
1320      * Constructor creates a StateMachine using the handler.
1321      *
1322      * @param name of the state machine
1323      */
StateMachine(String name, Handler handler)1324     protected StateMachine(String name, Handler handler) {
1325         initStateMachine(name, handler.getLooper());
1326     }
1327 
1328     /**
1329      * Notifies subclass that the StateMachine handler is about to process the Message msg
1330      * @param msg The message that is being handled
1331      */
onPreHandleMessage(Message msg)1332     protected void onPreHandleMessage(Message msg) {
1333     }
1334 
1335     /**
1336      * Notifies subclass that the StateMachine handler has finished processing the Message msg and
1337      * has possibly transitioned to a new state.
1338      * @param msg The message that is being handled
1339      */
onPostHandleMessage(Message msg)1340     protected void onPostHandleMessage(Message msg) {
1341     }
1342 
1343     /**
1344      * Add a new state to the state machine
1345      * @param state the state to add
1346      * @param parent the parent of state
1347      */
addState(State state, State parent)1348     public final void addState(State state, State parent) {
1349         mSmHandler.addState(state, parent);
1350     }
1351 
1352     /**
1353      * Add a new state to the state machine, parent will be null
1354      * @param state to add
1355      */
addState(State state)1356     public final void addState(State state) {
1357         mSmHandler.addState(state, null);
1358     }
1359 
1360     /**
1361      * Removes a state from the state machine, unless it is currently active or if it has children.
1362      * @param state state to remove
1363      */
removeState(State state)1364     public final void removeState(State state) {
1365         mSmHandler.removeState(state);
1366     }
1367 
1368     /**
1369      * Set the initial state. This must be invoked before
1370      * and messages are sent to the state machine.
1371      *
1372      * @param initialState is the state which will receive the first message.
1373      */
setInitialState(State initialState)1374     public final void setInitialState(State initialState) {
1375         mSmHandler.setInitialState(initialState);
1376     }
1377 
1378     /**
1379      * @return current message
1380      */
getCurrentMessage()1381     public final Message getCurrentMessage() {
1382         // mSmHandler can be null if the state machine has quit.
1383         SmHandler smh = mSmHandler;
1384         if (smh == null) return null;
1385         return smh.getCurrentMessage();
1386     }
1387 
1388     /**
1389      * @return current state
1390      */
getCurrentState()1391     public final IState getCurrentState() {
1392         // mSmHandler can be null if the state machine has quit.
1393         SmHandler smh = mSmHandler;
1394         if (smh == null) return null;
1395         return smh.getCurrentState();
1396     }
1397 
1398     /**
1399      * transition to destination state. Upon returning
1400      * from processMessage the current state's exit will
1401      * be executed and upon the next message arriving
1402      * destState.enter will be invoked.
1403      *
1404      * this function can also be called inside the enter function of the
1405      * previous transition target, but the behavior is undefined when it is
1406      * called mid-way through a previous transition (for example, calling this
1407      * in the enter() routine of a intermediate node when the current transition
1408      * target is one of the nodes descendants).
1409      *
1410      * @param destState will be the state that receives the next message.
1411      */
transitionTo(IState destState)1412     public final void transitionTo(IState destState) {
1413         mSmHandler.transitionTo(destState);
1414     }
1415 
1416     /**
1417      * transition to halt state. Upon returning
1418      * from processMessage we will exit all current
1419      * states, execute the onHalting() method and then
1420      * for all subsequent messages haltedProcessMessage
1421      * will be called.
1422      */
transitionToHaltingState()1423     public final void transitionToHaltingState() {
1424         mSmHandler.transitionTo(mSmHandler.mHaltingState);
1425     }
1426 
1427     /**
1428      * Defer this message until next state transition.
1429      * Upon transitioning all deferred messages will be
1430      * placed on the queue and reprocessed in the original
1431      * order. (i.e. The next state the oldest messages will
1432      * be processed first)
1433      *
1434      * @param msg is deferred until the next transition.
1435      */
deferMessage(Message msg)1436     public final void deferMessage(Message msg) {
1437         mSmHandler.deferMessage(msg);
1438     }
1439 
1440     /**
1441      * Called when message wasn't handled
1442      *
1443      * @param msg that couldn't be handled.
1444      */
unhandledMessage(Message msg)1445     protected void unhandledMessage(Message msg) {
1446         if (mSmHandler.mDbg) loge(" - unhandledMessage: msg.what=" + msg.what);
1447     }
1448 
1449     /**
1450      * Called for any message that is received after
1451      * transitionToHalting is called.
1452      */
haltedProcessMessage(Message msg)1453     protected void haltedProcessMessage(Message msg) {
1454     }
1455 
1456     /**
1457      * This will be called once after handling a message that called
1458      * transitionToHalting. All subsequent messages will invoke
1459      * {@link StateMachine#haltedProcessMessage(Message)}
1460      */
onHalting()1461     protected void onHalting() {
1462     }
1463 
1464     /**
1465      * This will be called once after a quit message that was NOT handled by
1466      * the derived StateMachine. The StateMachine will stop and any subsequent messages will be
1467      * ignored. In addition, if this StateMachine created the thread, the thread will
1468      * be stopped after this method returns.
1469      */
onQuitting()1470     protected void onQuitting() {
1471     }
1472 
1473     /**
1474      * @return the name
1475      */
getName()1476     public final String getName() {
1477         return mName;
1478     }
1479 
1480     /**
1481      * Set number of log records to maintain and clears all current records.
1482      *
1483      * @param maxSize number of messages to maintain at anyone time.
1484      */
setLogRecSize(int maxSize)1485     public final void setLogRecSize(int maxSize) {
1486         mSmHandler.mLogRecords.setSize(maxSize);
1487     }
1488 
1489     /**
1490      * Set to log only messages that cause a state transition
1491      *
1492      * @param enable {@code true} to enable, {@code false} to disable
1493      */
setLogOnlyTransitions(boolean enable)1494     public final void setLogOnlyTransitions(boolean enable) {
1495         mSmHandler.mLogRecords.setLogOnlyTransitions(enable);
1496     }
1497 
1498     /**
1499      * @return the number of log records currently readable
1500      */
getLogRecSize()1501     public final int getLogRecSize() {
1502         // mSmHandler can be null if the state machine has quit.
1503         SmHandler smh = mSmHandler;
1504         if (smh == null) return 0;
1505         return smh.mLogRecords.size();
1506     }
1507 
1508     /**
1509      * @return the number of log records we can store
1510      */
1511     @VisibleForTesting
getLogRecMaxSize()1512     public final int getLogRecMaxSize() {
1513         // mSmHandler can be null if the state machine has quit.
1514         SmHandler smh = mSmHandler;
1515         if (smh == null) return 0;
1516         return smh.mLogRecords.mMaxSize;
1517     }
1518 
1519     /**
1520      * @return the total number of records processed
1521      */
getLogRecCount()1522     public final int getLogRecCount() {
1523         // mSmHandler can be null if the state machine has quit.
1524         SmHandler smh = mSmHandler;
1525         if (smh == null) return 0;
1526         return smh.mLogRecords.count();
1527     }
1528 
1529     /**
1530      * @return a log record, or null if index is out of range
1531      */
getLogRec(int index)1532     public final LogRec getLogRec(int index) {
1533         // mSmHandler can be null if the state machine has quit.
1534         SmHandler smh = mSmHandler;
1535         if (smh == null) return null;
1536         return smh.mLogRecords.get(index);
1537     }
1538 
1539     /**
1540      * @return a copy of LogRecs as a collection
1541      */
copyLogRecs()1542     public final Collection<LogRec> copyLogRecs() {
1543         Vector<LogRec> vlr = new Vector<LogRec>();
1544         SmHandler smh = mSmHandler;
1545         if (smh != null) {
1546             for (LogRec lr : smh.mLogRecords.mLogRecVector) {
1547                 vlr.add(lr);
1548             }
1549         }
1550         return vlr;
1551     }
1552 
1553     /**
1554      * Add the string to LogRecords.
1555      *
1556      * @param string
1557      */
addLogRec(String string)1558     public void addLogRec(String string) {
1559         // mSmHandler can be null if the state machine has quit.
1560         SmHandler smh = mSmHandler;
1561         if (smh == null) return;
1562         smh.mLogRecords.add(this, smh.getCurrentMessage(), string, smh.getCurrentState(),
1563                 smh.mStateStack[smh.mStateStackTopIndex].state, smh.mDestState);
1564     }
1565 
1566     /**
1567      * @return true if msg should be saved in the log, default is true.
1568      */
recordLogRec(Message msg)1569     protected boolean recordLogRec(Message msg) {
1570         return true;
1571     }
1572 
1573     /**
1574      * Return a string to be logged by LogRec, default
1575      * is an empty string. Override if additional information is desired.
1576      *
1577      * @param msg that was processed
1578      * @return information to be logged as a String
1579      */
getLogRecString(Message msg)1580     protected String getLogRecString(Message msg) {
1581         return "";
1582     }
1583 
1584     /**
1585      * @return the string for msg.what
1586      */
getWhatToString(int what)1587     protected String getWhatToString(int what) {
1588         return null;
1589     }
1590 
1591     /**
1592      * @return Handler, maybe null if state machine has quit.
1593      */
getHandler()1594     public final Handler getHandler() {
1595         return mSmHandler;
1596     }
1597 
1598     /**
1599      * Get a message and set Message.target state machine handler.
1600      *
1601      * Note: The handler can be null if the state machine has quit,
1602      * which means target will be null and may cause a AndroidRuntimeException
1603      * in MessageQueue#enqueMessage if sent directly or if sent using
1604      * StateMachine#sendMessage the message will just be ignored.
1605      *
1606      * @return  A Message object from the global pool
1607      */
obtainMessage()1608     public final Message obtainMessage() {
1609         return Message.obtain(mSmHandler);
1610     }
1611 
1612     /**
1613      * Get a message and set Message.target state machine handler, what.
1614      *
1615      * Note: The handler can be null if the state machine has quit,
1616      * which means target will be null and may cause a AndroidRuntimeException
1617      * in MessageQueue#enqueMessage if sent directly or if sent using
1618      * StateMachine#sendMessage the message will just be ignored.
1619      *
1620      * @param what is the assigned to Message.what.
1621      * @return  A Message object from the global pool
1622      */
obtainMessage(int what)1623     public final Message obtainMessage(int what) {
1624         return Message.obtain(mSmHandler, what);
1625     }
1626 
1627     /**
1628      * Get a message and set Message.target state machine handler,
1629      * what and obj.
1630      *
1631      * Note: The handler can be null if the state machine has quit,
1632      * which means target will be null and may cause a AndroidRuntimeException
1633      * in MessageQueue#enqueMessage if sent directly or if sent using
1634      * StateMachine#sendMessage the message will just be ignored.
1635      *
1636      * @param what is the assigned to Message.what.
1637      * @param obj is assigned to Message.obj.
1638      * @return  A Message object from the global pool
1639      */
obtainMessage(int what, Object obj)1640     public final Message obtainMessage(int what, Object obj) {
1641         return Message.obtain(mSmHandler, what, obj);
1642     }
1643 
1644     /**
1645      * Get a message and set Message.target state machine handler,
1646      * what, arg1 and arg2
1647      *
1648      * Note: The handler can be null if the state machine has quit,
1649      * which means target will be null and may cause a AndroidRuntimeException
1650      * in MessageQueue#enqueMessage if sent directly or if sent using
1651      * StateMachine#sendMessage the message will just be ignored.
1652      *
1653      * @param what  is assigned to Message.what
1654      * @param arg1  is assigned to Message.arg1
1655      * @return  A Message object from the global pool
1656      */
obtainMessage(int what, int arg1)1657     public final Message obtainMessage(int what, int arg1) {
1658         // use this obtain so we don't match the obtain(h, what, Object) method
1659         return Message.obtain(mSmHandler, what, arg1, 0);
1660     }
1661 
1662     /**
1663      * Get a message and set Message.target state machine handler,
1664      * what, arg1 and arg2
1665      *
1666      * Note: The handler can be null if the state machine has quit,
1667      * which means target will be null and may cause a AndroidRuntimeException
1668      * in MessageQueue#enqueMessage if sent directly or if sent using
1669      * StateMachine#sendMessage the message will just be ignored.
1670      *
1671      * @param what  is assigned to Message.what
1672      * @param arg1  is assigned to Message.arg1
1673      * @param arg2  is assigned to Message.arg2
1674      * @return  A Message object from the global pool
1675      */
obtainMessage(int what, int arg1, int arg2)1676     public final Message obtainMessage(int what, int arg1, int arg2) {
1677         return Message.obtain(mSmHandler, what, arg1, arg2);
1678     }
1679 
1680     /**
1681      * Get a message and set Message.target state machine handler,
1682      * what, arg1, arg2 and obj
1683      *
1684      * Note: The handler can be null if the state machine has quit,
1685      * which means target will be null and may cause a AndroidRuntimeException
1686      * in MessageQueue#enqueMessage if sent directly or if sent using
1687      * StateMachine#sendMessage the message will just be ignored.
1688      *
1689      * @param what  is assigned to Message.what
1690      * @param arg1  is assigned to Message.arg1
1691      * @param arg2  is assigned to Message.arg2
1692      * @param obj is assigned to Message.obj
1693      * @return  A Message object from the global pool
1694      */
obtainMessage(int what, int arg1, int arg2, Object obj)1695     public final Message obtainMessage(int what, int arg1, int arg2, Object obj) {
1696         return Message.obtain(mSmHandler, what, arg1, arg2, obj);
1697     }
1698 
1699     /**
1700      * Enqueue a message to this state machine.
1701      *
1702      * Message is ignored if state machine has quit.
1703      */
sendMessage(int what)1704     public void sendMessage(int what) {
1705         // mSmHandler can be null if the state machine has quit.
1706         SmHandler smh = mSmHandler;
1707         if (smh == null) return;
1708 
1709         smh.sendMessage(obtainMessage(what));
1710     }
1711 
1712     /**
1713      * Enqueue a message to this state machine.
1714      *
1715      * Message is ignored if state machine has quit.
1716      */
sendMessage(int what, Object obj)1717     public void sendMessage(int what, Object obj) {
1718         // mSmHandler can be null if the state machine has quit.
1719         SmHandler smh = mSmHandler;
1720         if (smh == null) return;
1721 
1722         smh.sendMessage(obtainMessage(what, obj));
1723     }
1724 
1725     /**
1726      * Enqueue a message to this state machine.
1727      *
1728      * Message is ignored if state machine has quit.
1729      */
sendMessage(int what, int arg1)1730     public void sendMessage(int what, int arg1) {
1731         // mSmHandler can be null if the state machine has quit.
1732         SmHandler smh = mSmHandler;
1733         if (smh == null) return;
1734 
1735         smh.sendMessage(obtainMessage(what, arg1));
1736     }
1737 
1738     /**
1739      * Enqueue a message to this state machine.
1740      *
1741      * Message is ignored if state machine has quit.
1742      */
sendMessage(int what, int arg1, int arg2)1743     public void sendMessage(int what, int arg1, int arg2) {
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, arg2));
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, Object obj)1756     public void sendMessage(int what, int arg1, int arg2, Object obj) {
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, obj));
1762     }
1763 
1764     /**
1765      * Enqueue a message to this state machine.
1766      *
1767      * Message is ignored if state machine has quit.
1768      */
sendMessage(Message msg)1769     public void sendMessage(Message msg) {
1770         // mSmHandler can be null if the state machine has quit.
1771         SmHandler smh = mSmHandler;
1772         if (smh == null) return;
1773 
1774         smh.sendMessage(msg);
1775     }
1776 
1777     /**
1778      * Enqueue a message to this state machine after a delay.
1779      *
1780      * Message is ignored if state machine has quit.
1781      */
sendMessageDelayed(int what, long delayMillis)1782     public void sendMessageDelayed(int what, long delayMillis) {
1783         // mSmHandler can be null if the state machine has quit.
1784         SmHandler smh = mSmHandler;
1785         if (smh == null) return;
1786 
1787         smh.sendMessageDelayed(obtainMessage(what), delayMillis);
1788     }
1789 
1790     /**
1791      * Enqueue a message to this state machine after a delay.
1792      *
1793      * Message is ignored if state machine has quit.
1794      */
sendMessageDelayed(int what, Object obj, long delayMillis)1795     public void sendMessageDelayed(int what, Object obj, long delayMillis) {
1796         // mSmHandler can be null if the state machine has quit.
1797         SmHandler smh = mSmHandler;
1798         if (smh == null) return;
1799 
1800         smh.sendMessageDelayed(obtainMessage(what, obj), delayMillis);
1801     }
1802 
1803     /**
1804      * Enqueue a message to this state machine after a delay.
1805      *
1806      * Message is ignored if state machine has quit.
1807      */
sendMessageDelayed(int what, int arg1, long delayMillis)1808     public void sendMessageDelayed(int what, int arg1, long delayMillis) {
1809         // mSmHandler can be null if the state machine has quit.
1810         SmHandler smh = mSmHandler;
1811         if (smh == null) return;
1812 
1813         smh.sendMessageDelayed(obtainMessage(what, arg1), delayMillis);
1814     }
1815 
1816     /**
1817      * Enqueue a message to this state machine after a delay.
1818      *
1819      * Message is ignored if state machine has quit.
1820      */
sendMessageDelayed(int what, int arg1, int arg2, long delayMillis)1821     public void sendMessageDelayed(int what, int arg1, int arg2, long delayMillis) {
1822         // mSmHandler can be null if the state machine has quit.
1823         SmHandler smh = mSmHandler;
1824         if (smh == null) return;
1825 
1826         smh.sendMessageDelayed(obtainMessage(what, arg1, arg2), delayMillis);
1827     }
1828 
1829     /**
1830      * Enqueue a message to this state machine after a delay.
1831      *
1832      * Message is ignored if state machine has quit.
1833      */
sendMessageDelayed(int what, int arg1, int arg2, Object obj, long delayMillis)1834     public void sendMessageDelayed(int what, int arg1, int arg2, Object obj,
1835             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, obj), 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(Message msg, long delayMillis)1848     public void sendMessageDelayed(Message msg, long delayMillis) {
1849         // mSmHandler can be null if the state machine has quit.
1850         SmHandler smh = mSmHandler;
1851         if (smh == null) return;
1852 
1853         smh.sendMessageDelayed(msg, delayMillis);
1854     }
1855 
1856     /**
1857      * Enqueue a message to the front of the queue for this state machine.
1858      * Protected, may only be called by instances of StateMachine.
1859      *
1860      * Message is ignored if state machine has quit.
1861      */
sendMessageAtFrontOfQueue(int what)1862     protected final void sendMessageAtFrontOfQueue(int what) {
1863         // mSmHandler can be null if the state machine has quit.
1864         SmHandler smh = mSmHandler;
1865         if (smh == null) return;
1866 
1867         smh.sendMessageAtFrontOfQueue(obtainMessage(what));
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, Object obj)1876     protected final void sendMessageAtFrontOfQueue(int what, Object obj) {
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, obj));
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, int arg1)1890     protected final void sendMessageAtFrontOfQueue(int what, int arg1) {
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, arg1));
1896     }
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, int arg2)1905     protected final void sendMessageAtFrontOfQueue(int what, int arg1, int arg2) {
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, arg2));
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, Object obj)1919     protected final void sendMessageAtFrontOfQueue(int what, int arg1, int arg2, Object obj) {
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, obj));
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(Message msg)1933     protected final void sendMessageAtFrontOfQueue(Message msg) {
1934         // mSmHandler can be null if the state machine has quit.
1935         SmHandler smh = mSmHandler;
1936         if (smh == null) return;
1937 
1938         smh.sendMessageAtFrontOfQueue(msg);
1939     }
1940 
1941     /**
1942      * Removes a message from the message queue.
1943      * Protected, may only be called by instances of StateMachine.
1944      */
removeMessages(int what)1945     protected final void removeMessages(int what) {
1946         // mSmHandler can be null if the state machine has quit.
1947         SmHandler smh = mSmHandler;
1948         if (smh == null) return;
1949 
1950         smh.removeMessages(what);
1951     }
1952 
1953     /**
1954      * Removes a message from the deferred messages queue.
1955      */
removeDeferredMessages(int what)1956     protected final void removeDeferredMessages(int what) {
1957         SmHandler smh = mSmHandler;
1958         if (smh == null) return;
1959 
1960         Iterator<Message> iterator = smh.mDeferredMessages.iterator();
1961         while (iterator.hasNext()) {
1962             Message msg = iterator.next();
1963             if (msg.what == what) iterator.remove();
1964         }
1965     }
1966 
1967     /**
1968      * Check if there are any pending messages with code 'what' in deferred messages queue.
1969      */
hasDeferredMessages(int what)1970     protected final boolean hasDeferredMessages(int what) {
1971         SmHandler smh = mSmHandler;
1972         if (smh == null) return false;
1973 
1974         Iterator<Message> iterator = smh.mDeferredMessages.iterator();
1975         while (iterator.hasNext()) {
1976             Message msg = iterator.next();
1977             if (msg.what == what) return true;
1978         }
1979 
1980         return false;
1981     }
1982 
1983     /**
1984      * Check if there are any pending posts of messages with code 'what' in
1985      * the message queue. This does NOT check messages in deferred message queue.
1986      */
hasMessages(int what)1987     protected final boolean hasMessages(int what) {
1988         SmHandler smh = mSmHandler;
1989         if (smh == null) return false;
1990 
1991         return smh.hasMessages(what);
1992     }
1993 
1994     /**
1995      * Validate that the message was sent by
1996      * {@link StateMachine#quit} or {@link StateMachine#quitNow}.
1997      */
isQuit(Message msg)1998     protected final boolean isQuit(Message msg) {
1999         // mSmHandler can be null if the state machine has quit.
2000         SmHandler smh = mSmHandler;
2001         if (smh == null) return msg.what == SM_QUIT_CMD;
2002 
2003         return smh.isQuit(msg);
2004     }
2005 
2006     /**
2007      * Quit the state machine after all currently queued up messages are processed.
2008      */
quit()2009     public final void quit() {
2010         // mSmHandler can be null if the state machine is already stopped.
2011         SmHandler smh = mSmHandler;
2012         if (smh == null) return;
2013 
2014         smh.quit();
2015     }
2016 
2017     /**
2018      * Quit the state machine immediately all currently queued messages will be discarded.
2019      */
quitNow()2020     public final void quitNow() {
2021         // mSmHandler can be null if the state machine is already stopped.
2022         SmHandler smh = mSmHandler;
2023         if (smh == null) return;
2024 
2025         smh.quitNow();
2026     }
2027 
2028     /**
2029      * @return if debugging is enabled
2030      */
isDbg()2031     public boolean isDbg() {
2032         // mSmHandler can be null if the state machine has quit.
2033         SmHandler smh = mSmHandler;
2034         if (smh == null) return false;
2035 
2036         return smh.isDbg();
2037     }
2038 
2039     /**
2040      * Set debug enable/disabled.
2041      *
2042      * @param dbg is true to enable debugging.
2043      */
setDbg(boolean dbg)2044     public void setDbg(boolean dbg) {
2045         // mSmHandler can be null if the state machine has quit.
2046         SmHandler smh = mSmHandler;
2047         if (smh == null) return;
2048 
2049         smh.setDbg(dbg);
2050     }
2051 
2052     /**
2053      * Start the state machine.
2054      */
start()2055     public void start() {
2056         // mSmHandler can be null if the state machine has quit.
2057         SmHandler smh = mSmHandler;
2058         if (smh == null) return;
2059 
2060         /** Send the complete construction message */
2061         smh.completeConstruction();
2062     }
2063 
2064     /**
2065      * Dump the current state.
2066      *
2067      * @param fd
2068      * @param pw
2069      * @param args
2070      */
dump(FileDescriptor fd, PrintWriter pw, String[] args)2071     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
2072         pw.println(getName() + ":");
2073         pw.println(" total records=" + getLogRecCount());
2074         for (int i = 0; i < getLogRecSize(); i++) {
2075             pw.println(" rec[" + i + "]: " + getLogRec(i).toString());
2076             pw.flush();
2077         }
2078         pw.println("curState=" + getCurrentState().getName());
2079     }
2080 
2081     @Override
toString()2082     public String toString() {
2083         String name = "(null)";
2084         String state = "(null)";
2085         try {
2086             name = mName.toString();
2087             state = mSmHandler.getCurrentState().getName().toString();
2088         } catch (NullPointerException | ArrayIndexOutOfBoundsException e) {
2089             // Will use default(s) initialized above.
2090         }
2091         return "name=" + name + " state=" + state;
2092     }
2093 
2094     /**
2095      * Log with debug and add to the LogRecords.
2096      *
2097      * @param s is string log
2098      */
logAndAddLogRec(String s)2099     protected void logAndAddLogRec(String s) {
2100         addLogRec(s);
2101         log(s);
2102     }
2103 
2104     /**
2105      * Log with debug
2106      *
2107      * @param s is string log
2108      */
log(String s)2109     protected void log(String s) {
2110         Log.d(mName, s);
2111     }
2112 
2113     /**
2114      * Log with debug attribute
2115      *
2116      * @param s is string log
2117      */
logd(String s)2118     protected void logd(String s) {
2119         Log.d(mName, s);
2120     }
2121 
2122     /**
2123      * Log with verbose attribute
2124      *
2125      * @param s is string log
2126      */
logv(String s)2127     protected void logv(String s) {
2128         Log.v(mName, s);
2129     }
2130 
2131     /**
2132      * Log with info attribute
2133      *
2134      * @param s is string log
2135      */
logi(String s)2136     protected void logi(String s) {
2137         Log.i(mName, s);
2138     }
2139 
2140     /**
2141      * Log with warning attribute
2142      *
2143      * @param s is string log
2144      */
logw(String s)2145     protected void logw(String s) {
2146         Log.w(mName, s);
2147     }
2148 
2149     /**
2150      * Log with error attribute
2151      *
2152      * @param s is string log
2153      */
loge(String s)2154     protected void loge(String s) {
2155         Log.e(mName, s);
2156     }
2157 
2158     /**
2159      * Log with error attribute
2160      *
2161      * @param s is string log
2162      * @param e is a Throwable which logs additional information.
2163      */
loge(String s, Throwable e)2164     protected void loge(String s, Throwable e) {
2165         Log.e(mName, s, e);
2166     }
2167 }
2168