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 17 package com.android.internal.util; 18 19 import android.os.Handler; 20 import android.os.HandlerThread; 21 import android.os.Looper; 22 import android.os.Message; 23 import android.text.TextUtils; 24 import android.util.Log; 25 26 import java.io.FileDescriptor; 27 import java.io.PrintWriter; 28 import java.util.ArrayList; 29 import java.util.Calendar; 30 import java.util.HashMap; 31 import 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> 136 class 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 @Override public boolean processMessage(Message message) { 151 Log.d(TAG, "Hello World"); 152 return HANDLED; 153 } 154 } 155 State1 mState1 = new State1(); 156 } 157 158 void 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> 172 state 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 186 INITIAL 187 state 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 199 state 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 216 state 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> 234 class 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 @Override public void enter() { 268 Log.d(TAG, "mP1.enter"); 269 } 270 @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 @Override public void exit() { 289 Log.d(TAG, "mP1.exit"); 290 } 291 } 292 293 class S1 extends State { 294 @Override public void enter() { 295 Log.d(TAG, "mS1.enter"); 296 } 297 @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 @Override public void exit() { 309 Log.d(TAG, "mS1.exit"); 310 } 311 } 312 313 class S2 extends State { 314 @Override public void enter() { 315 Log.d(TAG, "mS2.enter"); 316 } 317 @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 @Override public void exit() { 337 Log.d(TAG, "mS2.exit"); 338 } 339 } 340 341 class P2 extends State { 342 @Override public void enter() { 343 Log.d(TAG, "mP2.enter"); 344 sendMessage(obtainMessage(CMD_5)); 345 } 346 @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 @Override public void exit() { 360 Log.d(TAG, "mP2.exit"); 361 } 362 } 363 364 @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> 381 Hsm1 hsm = makeHsm1(); 382 synchronize(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> 395 D/hsm1 ( 1999): makeHsm1 E 396 D/hsm1 ( 1999): ctor E 397 D/hsm1 ( 1999): ctor X 398 D/hsm1 ( 1999): mP1.enter 399 D/hsm1 ( 1999): mS1.enter 400 D/hsm1 ( 1999): makeHsm1 X 401 D/hsm1 ( 1999): mS1.processMessage what=1 402 D/hsm1 ( 1999): mS1.exit 403 D/hsm1 ( 1999): mS1.enter 404 D/hsm1 ( 1999): mS1.processMessage what=2 405 D/hsm1 ( 1999): mP1.processMessage what=2 406 D/hsm1 ( 1999): mS1.exit 407 D/hsm1 ( 1999): mS2.enter 408 D/hsm1 ( 1999): mS2.processMessage what=2 409 D/hsm1 ( 1999): mS2.processMessage what=3 410 D/hsm1 ( 1999): mS2.exit 411 D/hsm1 ( 1999): mP1.exit 412 D/hsm1 ( 1999): mP2.enter 413 D/hsm1 ( 1999): mP2.processMessage what=3 414 D/hsm1 ( 1999): mP2.processMessage what=4 415 D/hsm1 ( 1999): mP2.processMessage what=5 416 D/hsm1 ( 1999): mP2.exit 417 D/hsm1 ( 1999): halting 418 </code> 419 */ 420 public 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