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