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