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