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