summaryrefslogtreecommitdiffstats
path: root/core/java/android/os/HandlerStateMachine.java
blob: 9e7902b5c7e44eaefff5d848e17ef56082984e0a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
/*
 * Copyright (C) 2006 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package android.os;

import android.util.Log;
import android.util.LogPrinter;

/**
 * {@hide}
 *
 * Implement a state machine where each state is an object,
 * HandlerState. Each HandlerState must implement processMessage
 * and optionally enter/exit. When a state machine is created
 * the initial state must be set. When messages are sent to
 * a state machine the current state's processMessage method is
 * invoked. If this is the first message for this state the
 * enter method is called prior to processMessage and when
 * transtionTo is invoked the state's exit method will be
 * called after returning from processMessage.
 *
 * If a message should be handled in a different state the
 * processMessage method may call deferMessage. This causes
 * the message to be saved on a list until transitioning
 * to a new state, at which time all of the deferred messages
 * will be put on the front of the state machines queue and
 * processed by the new current state's processMessage
 * method.
 *
 * Below is an example state machine with two state's, S1 and S2.
 * The initial state is S1 which defers all messages and only
 * transition to S2 when message.what == TEST_WHAT_2. State S2
 * will process each messages until it receives TEST_WHAT_2
 * where it will transition back to S1:
<code>
     class StateMachine1 extends HandlerStateMachine {
        private static final int TEST_WHAT_1 = 1;
        private static final int TEST_WHAT_2 = 2;

        StateMachine1(String name) {
            super(name);
            setInitialState(mS1);
        }

        class S1 extends HandlerState {
            &amp;#064;Override public void enter(Message message) {
            }

            &amp;#064;Override public void processMessage(Message message) {
                deferMessage(message);
                if (message.what == TEST_WHAT_2) {
                    transitionTo(mS2);
                }
            }

            &amp;#064;Override public void exit(Message message) {
            }
        }

        class S2 extends HandlerState {
            &amp;#064;Override public void processMessage(Message message) {
                // Do some processing
                if (message.what == TEST_WHAT_2) {
                    transtionTo(mS1);
                }
            }
        }

        private S1 mS1 = new S1();
        private S2 mS2 = new S2();
    }
</code>
 */
public class HandlerStateMachine {

    private boolean mDbg = false;
    private static final String TAG = "HandlerStateMachine";
    private String mName;
    private SmHandler mHandler;
    private HandlerThread mHandlerThread;

    /**
     * Handle messages sent to the state machine by calling
     * the current state's processMessage. It also handles
     * the enter/exit calls and placing any deferred messages
     * back onto the queue when transitioning to a new state.
     */
    class SmHandler extends Handler {

        SmHandler(Looper looper) {
          super(looper);
        }

        /**
         * This will dispatch the message to the
         * current state's processMessage.
         */
        @Override
        final public void handleMessage(Message msg) {
            if (mDbg) Log.d(TAG, "SmHandler.handleMessage E");
            if (mDestState != null) {
                if (mDbg) Log.d(TAG, "SmHandler.handleMessage; new destation call enter");
                mCurrentState = mDestState;
                mDestState = null;
                mCurrentState.enter(msg);
            }
            if (mCurrentState != null) {
                if (mDbg) Log.d(TAG, "SmHandler.handleMessage; call processMessage");
                mCurrentState.processMessage(msg);
            } else {
                /* Strange no state to execute */
                Log.e(TAG, "handleMessage: no current state, did you call setInitialState");
            }

            if (mDestState != null) {
                if (mDbg) Log.d(TAG, "SmHandler.handleMessage; new destination call exit");
                mCurrentState.exit(msg);

                /**
                 * Place the messages from the deferred queue:t
                 * on to the Handler's message queue in the
                 * same order that they originally arrived.
                 *
                 * We set cur.when = 0 to circumvent the check
                 * that this message has already been sent.
                 */
                while (mDeferredMessages != null) {
                    Message cur = mDeferredMessages;
                    mDeferredMessages = mDeferredMessages.next;
                    cur.when = 0;
                    if (mDbg) Log.d(TAG, "SmHandler.handleMessage; queue deferred message what="
                                                + cur.what + " target=" + cur.target);
                    sendMessageAtFrontOfQueue(cur);
                }
                if (mDbg) Log.d(TAG, "SmHandler.handleMessage X");
            }
        }

        public HandlerState mCurrentState;
        public HandlerState mDestState;
        public Message mDeferredMessages;
    }

    /**
     * Create an active StateMachine, one that has a
     * dedicated thread/looper/queue.
     */
    public HandlerStateMachine(String name) {
        mName = name;
        mHandlerThread =  new HandlerThread(name);
        mHandlerThread.start();
        mHandler = new SmHandler(mHandlerThread.getLooper());
    }

    /**
     * Get a message and set Message.target = this.
     */
    public final Message obtainMessage()
    {
        Message msg = Message.obtain(mHandler);
        if (mDbg) Log.d(TAG, "StateMachine.obtainMessage() EX target=" + msg.target);
        return msg;
    }

    /**
     * Get a message and set Message.target = this and
     * Message.what = what.
     */
    public final Message obtainMessage(int what) {
        Message msg = Message.obtain(mHandler, what);
        if (mDbg) {
            Log.d(TAG, "StateMachine.obtainMessage(what) EX what=" + msg.what +
                       " target=" + msg.target);
        }
        return msg;
    }

    /**
     * Enqueue a message to this state machine.
     */
    public final void sendMessage(Message msg) {
        if (mDbg) Log.d(TAG, "StateMachine.sendMessage EX msg.what=" + msg.what);
        mHandler.sendMessage(msg);
    }

    /**
     * Enqueue a message to this state machine after a delay.
     */
    public final void sendMessageDelayed(Message msg, long delayMillis) {
        if (mDbg) {
            Log.d(TAG, "StateMachine.sendMessageDelayed EX msg.what="
                            + msg.what + " delay=" + delayMillis);
        }
        mHandler.sendMessageDelayed(msg, delayMillis);
    }

    /**
     * Set the initial state. This must be invoked before
     * and messages are sent to the state machine.
     */
    public void setInitialState(HandlerState initialState) {
        if (mDbg) {
            Log.d(TAG, "StateMachine.setInitialState EX initialState"
                            + initialState.getClass().getName());
        }
        mHandler.mDestState = initialState;
    }

    /**
     * transition to destination state. Upon returning
     * from processMessage the current state's exit will
     * be executed and upon the next message arriving
     * destState.enter will be invoked.
     */
    final public void transitionTo(HandlerState destState) {
        if (mDbg) {
            Log.d(TAG, "StateMachine.transitionTo EX destState"
                            + destState.getClass().getName());
        }
        mHandler.mDestState = destState;
    }

    /**
     * Defer this message until next state transition.
     * Upon transitioning all deferred messages will be
     * placed on the queue and reprocessed in the original
     * order. (i.e. The next state the oldest messages will
     * be processed first)
     */
    final public void deferMessage(Message msg) {
        if (mDbg) {
            Log.d(TAG, "StateMachine.deferMessage EX mDeferredMessages="
                            + mHandler.mDeferredMessages);
        }

        /* Copy the "msg" to "newMsg" as "msg" will be recycled */
        Message newMsg = obtainMessage();
        newMsg.copyFrom(msg);

        /* Place on front of queue */
        newMsg.next = mHandler.mDeferredMessages;
        mHandler.mDeferredMessages = newMsg;
    }

    /**
     * @return the name
     */
    public String getName() {
        return mName;
    }

    /**
     * @return Handler
     */
    public Handler getHandler() {
        return mHandler;
    }

    /**
     * @return if debugging is enabled
     */
    public boolean isDbg() {
        return mDbg;
    }

    /**
     * Set debug enable/disabled.
     */
    public void setDbg(boolean dbg) {
        mDbg = dbg;
        if (mDbg) {
            mHandlerThread.getLooper().setMessageLogging(new LogPrinter(Log.VERBOSE, TAG));
        } else {
            mHandlerThread.getLooper().setMessageLogging(null);
        }
   }
}