StateMachine.java revision cea056f5894543819898a0e62f98dc7870b27758
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.util.Log; 24 25import java.util.ArrayList; 26import java.util.HashMap; 27import java.util.Vector; 28 29/** 30 * {@hide} 31 * 32 * <p>The state machine defined here is a hierarchical state machine which processes messages 33 * and can have states arranged hierarchically.</p> 34 * 35 * <p>A state is a <code>State</code> object and must implement 36 * <code>processMessage</code> and optionally <code>enter/exit/getName</code>. 37 * The enter/exit methods are equivalent to the construction and destruction 38 * in Object Oriented programming and are used to perform initialization and 39 * cleanup of the state respectively. The <code>getName</code> method returns the 40 * name of the state the default implementation returns the class name it may be 41 * desirable to have this return the name of the state instance name instead. 42 * In particular if a particular state class has multiple instances.</p> 43 * 44 * <p>When a state machine is created <code>addState</code> is used to build the 45 * hierarchy and <code>setInitialState</code> is used to identify which of these 46 * is the initial state. After construction the programmer calls <code>start</code> 47 * which initializes and starts the state machine. The first action the StateMachine 48 * is to the invoke <code>enter</code> for all of the initial state's hierarchy, 49 * starting at its eldest parent. The calls to enter will be done in the context 50 * of the StateMachines Handler not in the context of the call to start and they 51 * will be invoked before any messages are processed. For example, given the simple 52 * state machine below mP1.enter will be invoked and then mS1.enter. Finally, 53 * messages sent to the state machine will be processed by the current state, 54 * in our simple state machine below that would initially be mS1.processMessage.</p> 55<code> 56 mP1 57 / \ 58 mS2 mS1 ----> initial state 59</code> 60 * <p>After the state machine is created and started, messages are sent to a state 61 * machine using <code>sendMessage</code> and the messages are created using 62 * <code>obtainMessage</code>. When the state machine receives a message the 63 * current state's <code>processMessage</code> is invoked. In the above example 64 * mS1.processMessage will be invoked first. The state may use <code>transitionTo</code> 65 * to change the current state to a new state</p> 66 * 67 * <p>Each state in the state machine may have a zero or one parent states and if 68 * a child state is unable to handle a message it may have the message processed 69 * by its parent by returning false or NOT_HANDLED. If a message is never processed 70 * <code>unhandledMessage</code> will be invoked to give one last chance for the state machine 71 * to process the message.</p> 72 * 73 * <p>When all processing is completed a state machine may choose to call 74 * <code>transitionToHaltingState</code>. When the current <code>processingMessage</code> 75 * returns the state machine will transfer to an internal <code>HaltingState</code> 76 * and invoke <code>halting</code>. Any message subsequently received by the state 77 * machine will cause <code>haltedProcessMessage</code> to be invoked.</p> 78 * 79 * <p>If it is desirable to completely stop the state machine call <code>quit</code>. This 80 * will exit the current state and its parent and then exit from the controlling thread 81 * and no further messages will be processed.</p> 82 * 83 * <p>In addition to <code>processMessage</code> each <code>State</code> has 84 * an <code>enter</code> method and <code>exit</exit> method which may be overridden.</p> 85 * 86 * <p>Since the states are arranged in a hierarchy transitioning to a new state 87 * causes current states to be exited and new states to be entered. To determine 88 * the list of states to be entered/exited the common parent closest to 89 * the current state is found. We then exit from the current state and its 90 * parent's up to but not including the common parent state and then enter all 91 * of the new states below the common parent down to the destination state. 92 * If there is no common parent all states are exited and then the new states 93 * are entered.</p> 94 * 95 * <p>Two other methods that states can use are <code>deferMessage</code> and 96 * <code>sendMessageAtFrontOfQueue</code>. The <code>sendMessageAtFrontOfQueue</code> sends 97 * a message but places it on the front of the queue rather than the back. The 98 * <code>deferMessage</code> causes the message to be saved on a list until a 99 * transition is made to a new state. At which time all of the deferred messages 100 * will be put on the front of the state machine queue with the oldest message 101 * at the front. These will then be processed by the new current state before 102 * any other messages that are on the queue or might be added later. Both of 103 * these are protected and may only be invoked from within a state machine.</p> 104 * 105 * <p>To illustrate some of these properties we'll use state machine with an 8 106 * state hierarchy:</p> 107<code> 108 mP0 109 / \ 110 mP1 mS0 111 / \ 112 mS2 mS1 113 / \ \ 114 mS3 mS4 mS5 ---> initial state 115</code> 116 * <p>After starting mS5 the list of active states is mP0, mP1, mS1 and mS5. 117 * So the order of calling processMessage when a message is received is mS5, 118 * mS1, mP1, mP0 assuming each processMessage indicates it can't handle this 119 * message by returning false or NOT_HANDLED.</p> 120 * 121 * <p>Now assume mS5.processMessage receives a message it can handle, and during 122 * the handling determines the machine should change states. It could call 123 * transitionTo(mS4) and return true or HANDLED. Immediately after returning from 124 * processMessage the state machine runtime will find the common parent, 125 * which is mP1. It will then call mS5.exit, mS1.exit, mS2.enter and then 126 * mS4.enter. The new list of active states is mP0, mP1, mS2 and mS4. So 127 * when the next message is received mS4.processMessage will be invoked.</p> 128 * 129 * <p>Now for some concrete examples, here is the canonical HelloWorld as a state machine. 130 * It responds with "Hello World" being printed to the log for every message.</p> 131<code> 132class HelloWorld extends StateMachine { 133 HelloWorld(String name) { 134 super(name); 135 addState(mState1); 136 setInitialState(mState1); 137 } 138 139 public static HelloWorld makeHelloWorld() { 140 HelloWorld hw = new HelloWorld("hw"); 141 hw.start(); 142 return hw; 143 } 144 145 class State1 extends State { 146 @Override public boolean processMessage(Message message) { 147 Log.d(TAG, "Hello World"); 148 return HANDLED; 149 } 150 } 151 State1 mState1 = new State1(); 152} 153 154void testHelloWorld() { 155 HelloWorld hw = makeHelloWorld(); 156 hw.sendMessage(hw.obtainMessage()); 157} 158</code> 159 * <p>A more interesting state machine is one with four states 160 * with two independent parent states.</p> 161<code> 162 mP1 mP2 163 / \ 164 mS2 mS1 165</code> 166 * <p>Here is a description of this state machine using pseudo code.</p> 167 <code> 168state mP1 { 169 enter { log("mP1.enter"); } 170 exit { log("mP1.exit"); } 171 on msg { 172 CMD_2 { 173 send(CMD_3); 174 defer(msg); 175 transitonTo(mS2); 176 return HANDLED; 177 } 178 return NOT_HANDLED; 179 } 180} 181 182INITIAL 183state mS1 parent mP1 { 184 enter { log("mS1.enter"); } 185 exit { log("mS1.exit"); } 186 on msg { 187 CMD_1 { 188 transitionTo(mS1); 189 return HANDLED; 190 } 191 return NOT_HANDLED; 192 } 193} 194 195state mS2 parent mP1 { 196 enter { log("mS2.enter"); } 197 exit { log("mS2.exit"); } 198 on msg { 199 CMD_2 { 200 send(CMD_4); 201 return HANDLED; 202 } 203 CMD_3 { 204 defer(msg); 205 transitionTo(mP2); 206 return HANDLED; 207 } 208 return NOT_HANDLED; 209 } 210} 211 212state mP2 { 213 enter { 214 log("mP2.enter"); 215 send(CMD_5); 216 } 217 exit { log("mP2.exit"); } 218 on msg { 219 CMD_3, CMD_4 { return HANDLED; } 220 CMD_5 { 221 transitionTo(HaltingState); 222 return HANDLED; 223 } 224 return NOT_HANDLED; 225 } 226} 227</code> 228 * <p>The implementation is below and also in StateMachineTest:</p> 229<code> 230class Hsm1 extends StateMachine { 231 private static final String TAG = "hsm1"; 232 233 public static final int CMD_1 = 1; 234 public static final int CMD_2 = 2; 235 public static final int CMD_3 = 3; 236 public static final int CMD_4 = 4; 237 public static final int CMD_5 = 5; 238 239 public static Hsm1 makeHsm1() { 240 Log.d(TAG, "makeHsm1 E"); 241 Hsm1 sm = new Hsm1("hsm1"); 242 sm.start(); 243 Log.d(TAG, "makeHsm1 X"); 244 return sm; 245 } 246 247 Hsm1(String name) { 248 super(name); 249 Log.d(TAG, "ctor E"); 250 251 // Add states, use indentation to show hierarchy 252 addState(mP1); 253 addState(mS1, mP1); 254 addState(mS2, mP1); 255 addState(mP2); 256 257 // Set the initial state 258 setInitialState(mS1); 259 Log.d(TAG, "ctor X"); 260 } 261 262 class P1 extends State { 263 @Override public void enter() { 264 Log.d(TAG, "mP1.enter"); 265 } 266 @Override public boolean processMessage(Message message) { 267 boolean retVal; 268 Log.d(TAG, "mP1.processMessage what=" + message.what); 269 switch(message.what) { 270 case CMD_2: 271 // CMD_2 will arrive in mS2 before CMD_3 272 sendMessage(obtainMessage(CMD_3)); 273 deferMessage(message); 274 transitionTo(mS2); 275 retVal = HANDLED; 276 break; 277 default: 278 // Any message we don't understand in this state invokes unhandledMessage 279 retVal = NOT_HANDLED; 280 break; 281 } 282 return retVal; 283 } 284 @Override public void exit() { 285 Log.d(TAG, "mP1.exit"); 286 } 287 } 288 289 class S1 extends State { 290 @Override public void enter() { 291 Log.d(TAG, "mS1.enter"); 292 } 293 @Override public boolean processMessage(Message message) { 294 Log.d(TAG, "S1.processMessage what=" + message.what); 295 if (message.what == CMD_1) { 296 // Transition to ourself to show that enter/exit is called 297 transitionTo(mS1); 298 return HANDLED; 299 } else { 300 // Let parent process all other messages 301 return NOT_HANDLED; 302 } 303 } 304 @Override public void exit() { 305 Log.d(TAG, "mS1.exit"); 306 } 307 } 308 309 class S2 extends State { 310 @Override public void enter() { 311 Log.d(TAG, "mS2.enter"); 312 } 313 @Override public boolean processMessage(Message message) { 314 boolean retVal; 315 Log.d(TAG, "mS2.processMessage what=" + message.what); 316 switch(message.what) { 317 case(CMD_2): 318 sendMessage(obtainMessage(CMD_4)); 319 retVal = HANDLED; 320 break; 321 case(CMD_3): 322 deferMessage(message); 323 transitionTo(mP2); 324 retVal = HANDLED; 325 break; 326 default: 327 retVal = NOT_HANDLED; 328 break; 329 } 330 return retVal; 331 } 332 @Override public void exit() { 333 Log.d(TAG, "mS2.exit"); 334 } 335 } 336 337 class P2 extends State { 338 @Override public void enter() { 339 Log.d(TAG, "mP2.enter"); 340 sendMessage(obtainMessage(CMD_5)); 341 } 342 @Override public boolean processMessage(Message message) { 343 Log.d(TAG, "P2.processMessage what=" + message.what); 344 switch(message.what) { 345 case(CMD_3): 346 break; 347 case(CMD_4): 348 break; 349 case(CMD_5): 350 transitionToHaltingState(); 351 break; 352 } 353 return HANDLED; 354 } 355 @Override public void exit() { 356 Log.d(TAG, "mP2.exit"); 357 } 358 } 359 360 @Override 361 void halting() { 362 Log.d(TAG, "halting"); 363 synchronized (this) { 364 this.notifyAll(); 365 } 366 } 367 368 P1 mP1 = new P1(); 369 S1 mS1 = new S1(); 370 S2 mS2 = new S2(); 371 P2 mP2 = new P2(); 372} 373</code> 374 * <p>If this is executed by sending two messages CMD_1 and CMD_2 375 * (Note the synchronize is only needed because we use hsm.wait())</p> 376<code> 377Hsm1 hsm = makeHsm1(); 378synchronize(hsm) { 379 hsm.sendMessage(obtainMessage(hsm.CMD_1)); 380 hsm.sendMessage(obtainMessage(hsm.CMD_2)); 381 try { 382 // wait for the messages to be handled 383 hsm.wait(); 384 } catch (InterruptedException e) { 385 Log.e(TAG, "exception while waiting " + e.getMessage()); 386 } 387} 388</code> 389 * <p>The output is:</p> 390<code> 391D/hsm1 ( 1999): makeHsm1 E 392D/hsm1 ( 1999): ctor E 393D/hsm1 ( 1999): ctor X 394D/hsm1 ( 1999): mP1.enter 395D/hsm1 ( 1999): mS1.enter 396D/hsm1 ( 1999): makeHsm1 X 397D/hsm1 ( 1999): mS1.processMessage what=1 398D/hsm1 ( 1999): mS1.exit 399D/hsm1 ( 1999): mS1.enter 400D/hsm1 ( 1999): mS1.processMessage what=2 401D/hsm1 ( 1999): mP1.processMessage what=2 402D/hsm1 ( 1999): mS1.exit 403D/hsm1 ( 1999): mS2.enter 404D/hsm1 ( 1999): mS2.processMessage what=2 405D/hsm1 ( 1999): mS2.processMessage what=3 406D/hsm1 ( 1999): mS2.exit 407D/hsm1 ( 1999): mP1.exit 408D/hsm1 ( 1999): mP2.enter 409D/hsm1 ( 1999): mP2.processMessage what=3 410D/hsm1 ( 1999): mP2.processMessage what=4 411D/hsm1 ( 1999): mP2.processMessage what=5 412D/hsm1 ( 1999): mP2.exit 413D/hsm1 ( 1999): halting 414</code> 415 */ 416public class StateMachine { 417 418 private static final String TAG = "StateMachine"; 419 private String mName; 420 421 /** Message.what value when quitting */ 422 public static final int SM_QUIT_CMD = -1; 423 424 /** Message.what value when initializing */ 425 public static final int SM_INIT_CMD = -2; 426 427 /** 428 * Convenience constant that maybe returned by processMessage 429 * to indicate the the message was processed and is not to be 430 * processed by parent states 431 */ 432 public static final boolean HANDLED = true; 433 434 /** 435 * Convenience constant that maybe returned by processMessage 436 * to indicate the the message was NOT processed and is to be 437 * processed by parent states 438 */ 439 public static final boolean NOT_HANDLED = false; 440 441 /** 442 * {@hide} 443 * 444 * The information maintained for a processed message. 445 */ 446 public static class ProcessedMessageInfo { 447 private int what; 448 private State state; 449 private State orgState; 450 451 /** 452 * Constructor 453 * @param message 454 * @param state that handled the message 455 * @param orgState is the first state the received the message but 456 * did not processes the message. 457 */ 458 ProcessedMessageInfo(Message message, State state, State orgState) { 459 update(message, state, orgState); 460 } 461 462 /** 463 * Update the information in the record. 464 * @param state that handled the message 465 * @param orgState is the first state the received the message but 466 * did not processes the message. 467 */ 468 public void update(Message message, State state, State orgState) { 469 this.what = message.what; 470 this.state = state; 471 this.orgState = orgState; 472 } 473 474 /** 475 * @return the command that was executing 476 */ 477 public int getWhat() { 478 return what; 479 } 480 481 /** 482 * @return the state that handled this message 483 */ 484 public State getState() { 485 return state; 486 } 487 488 /** 489 * @return the original state that received the message. 490 */ 491 public State getOriginalState() { 492 return orgState; 493 } 494 495 /** 496 * @return as string 497 */ 498 @Override 499 public String toString() { 500 StringBuilder sb = new StringBuilder(); 501 sb.append("what="); 502 sb.append(what); 503 sb.append(" state="); 504 sb.append(cn(state)); 505 sb.append(" orgState="); 506 sb.append(cn(orgState)); 507 return sb.toString(); 508 } 509 510 /** 511 * @return an objects class name 512 */ 513 private String cn(Object n) { 514 if (n == null) { 515 return "null"; 516 } else { 517 String name = n.getClass().getName(); 518 int lastDollar = name.lastIndexOf('$'); 519 return name.substring(lastDollar + 1); 520 } 521 } 522 } 523 524 /** 525 * A list of messages recently processed by the state machine. 526 * 527 * The class maintains a list of messages that have been most 528 * recently processed. The list is finite and may be set in the 529 * constructor or by calling setSize. The public interface also 530 * includes size which returns the number of recent messages, 531 * count which is the number of message processed since the 532 * the last setSize, get which returns a processed message and 533 * add which adds a processed messaged. 534 */ 535 private static class ProcessedMessages { 536 537 private static final int DEFAULT_SIZE = 20; 538 539 private Vector<ProcessedMessageInfo> mMessages = new Vector<ProcessedMessageInfo>(); 540 private int mMaxSize = DEFAULT_SIZE; 541 private int mOldestIndex = 0; 542 private int mCount = 0; 543 544 /** 545 * Constructor 546 */ 547 ProcessedMessages() { 548 } 549 550 /** 551 * Set size of messages to maintain and clears all current messages. 552 * 553 * @param maxSize number of messages to maintain at anyone time. 554 */ 555 void setSize(int maxSize) { 556 mMaxSize = maxSize; 557 mCount = 0; 558 mMessages.clear(); 559 } 560 561 /** 562 * @return the number of recent messages. 563 */ 564 int size() { 565 return mMessages.size(); 566 } 567 568 /** 569 * @return the total number of messages processed since size was set. 570 */ 571 int count() { 572 return mCount; 573 } 574 575 /** 576 * Clear the list of Processed Message Info. 577 */ 578 void cleanup() { 579 mMessages.clear(); 580 } 581 582 /** 583 * @return the information on a particular record. 0 is the oldest 584 * record and size()-1 is the newest record. If the index is to 585 * large null is returned. 586 */ 587 ProcessedMessageInfo get(int index) { 588 int nextIndex = mOldestIndex + index; 589 if (nextIndex >= mMaxSize) { 590 nextIndex -= mMaxSize; 591 } 592 if (nextIndex >= size()) { 593 return null; 594 } else { 595 return mMessages.get(nextIndex); 596 } 597 } 598 599 /** 600 * Add a processed message. 601 * 602 * @param message 603 * @param state that handled the message 604 * @param orgState is the first state the received the message but 605 * did not processes the message. 606 */ 607 void add(Message message, State state, State orgState) { 608 mCount += 1; 609 if (mMessages.size() < mMaxSize) { 610 mMessages.add(new ProcessedMessageInfo(message, state, orgState)); 611 } else { 612 ProcessedMessageInfo pmi = mMessages.get(mOldestIndex); 613 mOldestIndex += 1; 614 if (mOldestIndex >= mMaxSize) { 615 mOldestIndex = 0; 616 } 617 pmi.update(message, state, orgState); 618 } 619 } 620 } 621 622 623 private static class SmHandler extends Handler { 624 625 /** The debug flag */ 626 private boolean mDbg = false; 627 628 /** The SmHandler object, identifies that message is internal */ 629 private static final Object mSmHandlerObj = new Object(); 630 631 /** The current message */ 632 private Message mMsg; 633 634 /** A list of messages that this state machine has processed */ 635 private ProcessedMessages mProcessedMessages = new ProcessedMessages(); 636 637 /** true if construction of the state machine has not been completed */ 638 private boolean mIsConstructionCompleted; 639 640 /** Stack used to manage the current hierarchy of states */ 641 private StateInfo mStateStack[]; 642 643 /** Top of mStateStack */ 644 private int mStateStackTopIndex = -1; 645 646 /** A temporary stack used to manage the state stack */ 647 private StateInfo mTempStateStack[]; 648 649 /** The top of the mTempStateStack */ 650 private int mTempStateStackCount; 651 652 /** State used when state machine is halted */ 653 private HaltingState mHaltingState = new HaltingState(); 654 655 /** State used when state machine is quitting */ 656 private QuittingState mQuittingState = new QuittingState(); 657 658 /** Reference to the StateMachine */ 659 private StateMachine mSm; 660 661 /** 662 * Information about a state. 663 * Used to maintain the hierarchy. 664 */ 665 private class StateInfo { 666 /** The state */ 667 State state; 668 669 /** The parent of this state, null if there is no parent */ 670 StateInfo parentStateInfo; 671 672 /** True when the state has been entered and on the stack */ 673 boolean active; 674 675 /** 676 * Convert StateInfo to string 677 */ 678 @Override 679 public String toString() { 680 return "state=" + state.getName() + ",active=" + active 681 + ",parent=" + ((parentStateInfo == null) ? 682 "null" : parentStateInfo.state.getName()); 683 } 684 } 685 686 /** The map of all of the states in the state machine */ 687 private HashMap<State, StateInfo> mStateInfo = 688 new HashMap<State, StateInfo>(); 689 690 /** The initial state that will process the first message */ 691 private State mInitialState; 692 693 /** The destination state when transitionTo has been invoked */ 694 private State mDestState; 695 696 /** The list of deferred messages */ 697 private ArrayList<Message> mDeferredMessages = new ArrayList<Message>(); 698 699 /** 700 * State entered when transitionToHaltingState is called. 701 */ 702 private class HaltingState extends State { 703 @Override 704 public boolean processMessage(Message msg) { 705 mSm.haltedProcessMessage(msg); 706 return true; 707 } 708 } 709 710 /** 711 * State entered when a valid quit message is handled. 712 */ 713 private class QuittingState extends State { 714 @Override 715 public boolean processMessage(Message msg) { 716 return NOT_HANDLED; 717 } 718 } 719 720 /** 721 * Handle messages sent to the state machine by calling 722 * the current state's processMessage. It also handles 723 * the enter/exit calls and placing any deferred messages 724 * back onto the queue when transitioning to a new state. 725 */ 726 @Override 727 public final void handleMessage(Message msg) { 728 if (mDbg) Log.d(TAG, "handleMessage: E msg.what=" + msg.what); 729 730 /** Save the current message */ 731 mMsg = msg; 732 733 if (mIsConstructionCompleted) { 734 /** Normal path */ 735 processMsg(msg); 736 } else if (!mIsConstructionCompleted && 737 (mMsg.what == SM_INIT_CMD) && (mMsg.obj == mSmHandlerObj)) { 738 /** Initial one time path. */ 739 mIsConstructionCompleted = true; 740 invokeEnterMethods(0); 741 } else { 742 throw new RuntimeException("StateMachine.handleMessage: " + 743 "The start method not called, received msg: " + msg); 744 } 745 performTransitions(); 746 747 if (mDbg) Log.d(TAG, "handleMessage: X"); 748 } 749 750 /** 751 * Do any transitions 752 */ 753 private void performTransitions() { 754 /** 755 * If transitionTo has been called, exit and then enter 756 * the appropriate states. We loop on this to allow 757 * enter and exit methods to use transitionTo. 758 */ 759 State destState = null; 760 while (mDestState != null) { 761 if (mDbg) Log.d(TAG, "handleMessage: new destination call exit"); 762 763 /** 764 * Save mDestState locally and set to null 765 * to know if enter/exit use transitionTo. 766 */ 767 destState = mDestState; 768 mDestState = null; 769 770 /** 771 * Determine the states to exit and enter and return the 772 * common ancestor state of the enter/exit states. Then 773 * invoke the exit methods then the enter methods. 774 */ 775 StateInfo commonStateInfo = setupTempStateStackWithStatesToEnter(destState); 776 invokeExitMethods(commonStateInfo); 777 int stateStackEnteringIndex = moveTempStateStackToStateStack(); 778 invokeEnterMethods(stateStackEnteringIndex); 779 780 781 /** 782 * Since we have transitioned to a new state we need to have 783 * any deferred messages moved to the front of the message queue 784 * so they will be processed before any other messages in the 785 * message queue. 786 */ 787 moveDeferredMessageAtFrontOfQueue(); 788 } 789 790 /** 791 * After processing all transitions check and 792 * see if the last transition was to quit or halt. 793 */ 794 if (destState != null) { 795 if (destState == mQuittingState) { 796 cleanupAfterQuitting(); 797 798 } else if (destState == mHaltingState) { 799 /** 800 * Call halting() if we've transitioned to the halting 801 * state. All subsequent messages will be processed in 802 * in the halting state which invokes haltedProcessMessage(msg); 803 */ 804 mSm.halting(); 805 } 806 } 807 } 808 809 /** 810 * Cleanup all the static variables and the looper after the SM has been quit. 811 */ 812 private final void cleanupAfterQuitting() { 813 mSm.quitting(); 814 if (mSm.mSmThread != null) { 815 // If we made the thread then quit looper which stops the thread. 816 getLooper().quit(); 817 mSm.mSmThread = null; 818 } 819 820 mSm.mSmHandler = null; 821 mSm = null; 822 mMsg = null; 823 mProcessedMessages.cleanup(); 824 mStateStack = null; 825 mTempStateStack = null; 826 mStateInfo.clear(); 827 mInitialState = null; 828 mDestState = null; 829 mDeferredMessages.clear(); 830 } 831 832 /** 833 * Complete the construction of the state machine. 834 */ 835 private final void completeConstruction() { 836 if (mDbg) Log.d(TAG, "completeConstruction: E"); 837 838 /** 839 * Determine the maximum depth of the state hierarchy 840 * so we can allocate the state stacks. 841 */ 842 int maxDepth = 0; 843 for (StateInfo si : mStateInfo.values()) { 844 int depth = 0; 845 for (StateInfo i = si; i != null; depth++) { 846 i = i.parentStateInfo; 847 } 848 if (maxDepth < depth) { 849 maxDepth = depth; 850 } 851 } 852 if (mDbg) Log.d(TAG, "completeConstruction: maxDepth=" + maxDepth); 853 854 mStateStack = new StateInfo[maxDepth]; 855 mTempStateStack = new StateInfo[maxDepth]; 856 setupInitialStateStack(); 857 858 /** Sending SM_INIT_CMD message to invoke enter methods asynchronously */ 859 sendMessageAtFrontOfQueue(obtainMessage(SM_INIT_CMD, mSmHandlerObj)); 860 861 if (mDbg) Log.d(TAG, "completeConstruction: X"); 862 } 863 864 /** 865 * Process the message. If the current state doesn't handle 866 * it, call the states parent and so on. If it is never handled then 867 * call the state machines unhandledMessage method. 868 */ 869 private final void processMsg(Message msg) { 870 StateInfo curStateInfo = mStateStack[mStateStackTopIndex]; 871 if (mDbg) { 872 Log.d(TAG, "processMsg: " + curStateInfo.state.getName()); 873 } 874 while (!curStateInfo.state.processMessage(msg)) { 875 /** 876 * Not processed 877 */ 878 curStateInfo = curStateInfo.parentStateInfo; 879 if (curStateInfo == null) { 880 /** 881 * No parents left so it's not handled 882 */ 883 mSm.unhandledMessage(msg); 884 if (isQuit(msg)) { 885 transitionTo(mQuittingState); 886 } 887 break; 888 } 889 if (mDbg) { 890 Log.d(TAG, "processMsg: " + curStateInfo.state.getName()); 891 } 892 } 893 894 /** 895 * Record that we processed the message 896 */ 897 if (curStateInfo != null) { 898 State orgState = mStateStack[mStateStackTopIndex].state; 899 mProcessedMessages.add(msg, curStateInfo.state, orgState); 900 } else { 901 mProcessedMessages.add(msg, null, null); 902 } 903 } 904 905 /** 906 * Call the exit method for each state from the top of stack 907 * up to the common ancestor state. 908 */ 909 private final void invokeExitMethods(StateInfo commonStateInfo) { 910 while ((mStateStackTopIndex >= 0) && 911 (mStateStack[mStateStackTopIndex] != commonStateInfo)) { 912 State curState = mStateStack[mStateStackTopIndex].state; 913 if (mDbg) Log.d(TAG, "invokeExitMethods: " + curState.getName()); 914 curState.exit(); 915 mStateStack[mStateStackTopIndex].active = false; 916 mStateStackTopIndex -= 1; 917 } 918 } 919 920 /** 921 * Invoke the enter method starting at the entering index to top of state stack 922 */ 923 private final void invokeEnterMethods(int stateStackEnteringIndex) { 924 for (int i = stateStackEnteringIndex; i <= mStateStackTopIndex; i++) { 925 if (mDbg) Log.d(TAG, "invokeEnterMethods: " + mStateStack[i].state.getName()); 926 mStateStack[i].state.enter(); 927 mStateStack[i].active = true; 928 } 929 } 930 931 /** 932 * Move the deferred message to the front of the message queue. 933 */ 934 private final void moveDeferredMessageAtFrontOfQueue() { 935 /** 936 * The oldest messages on the deferred list must be at 937 * the front of the queue so start at the back, which 938 * as the most resent message and end with the oldest 939 * messages at the front of the queue. 940 */ 941 for (int i = mDeferredMessages.size() - 1; i >= 0; i-- ) { 942 Message curMsg = mDeferredMessages.get(i); 943 if (mDbg) Log.d(TAG, "moveDeferredMessageAtFrontOfQueue; what=" + curMsg.what); 944 sendMessageAtFrontOfQueue(curMsg); 945 } 946 mDeferredMessages.clear(); 947 } 948 949 /** 950 * Move the contents of the temporary stack to the state stack 951 * reversing the order of the items on the temporary stack as 952 * they are moved. 953 * 954 * @return index into mStateStack where entering needs to start 955 */ 956 private final int moveTempStateStackToStateStack() { 957 int startingIndex = mStateStackTopIndex + 1; 958 int i = mTempStateStackCount - 1; 959 int j = startingIndex; 960 while (i >= 0) { 961 if (mDbg) Log.d(TAG, "moveTempStackToStateStack: i=" + i + ",j=" + j); 962 mStateStack[j] = mTempStateStack[i]; 963 j += 1; 964 i -= 1; 965 } 966 967 mStateStackTopIndex = j - 1; 968 if (mDbg) { 969 Log.d(TAG, "moveTempStackToStateStack: X mStateStackTop=" 970 + mStateStackTopIndex + ",startingIndex=" + startingIndex 971 + ",Top=" + mStateStack[mStateStackTopIndex].state.getName()); 972 } 973 return startingIndex; 974 } 975 976 /** 977 * Setup the mTempStateStack with the states we are going to enter. 978 * 979 * This is found by searching up the destState's ancestors for a 980 * state that is already active i.e. StateInfo.active == true. 981 * The destStae and all of its inactive parents will be on the 982 * TempStateStack as the list of states to enter. 983 * 984 * @return StateInfo of the common ancestor for the destState and 985 * current state or null if there is no common parent. 986 */ 987 private final StateInfo setupTempStateStackWithStatesToEnter(State destState) { 988 /** 989 * Search up the parent list of the destination state for an active 990 * state. Use a do while() loop as the destState must always be entered 991 * even if it is active. This can happen if we are exiting/entering 992 * the current state. 993 */ 994 mTempStateStackCount = 0; 995 StateInfo curStateInfo = mStateInfo.get(destState); 996 do { 997 mTempStateStack[mTempStateStackCount++] = curStateInfo; 998 curStateInfo = curStateInfo.parentStateInfo; 999 } while ((curStateInfo != null) && !curStateInfo.active); 1000 1001 if (mDbg) { 1002 Log.d(TAG, "setupTempStateStackWithStatesToEnter: X mTempStateStackCount=" 1003 + mTempStateStackCount + ",curStateInfo: " + curStateInfo); 1004 } 1005 return curStateInfo; 1006 } 1007 1008 /** 1009 * Initialize StateStack to mInitialState. 1010 */ 1011 private final void setupInitialStateStack() { 1012 if (mDbg) { 1013 Log.d(TAG, "setupInitialStateStack: E mInitialState=" 1014 + mInitialState.getName()); 1015 } 1016 1017 StateInfo curStateInfo = mStateInfo.get(mInitialState); 1018 for (mTempStateStackCount = 0; curStateInfo != null; mTempStateStackCount++) { 1019 mTempStateStack[mTempStateStackCount] = curStateInfo; 1020 curStateInfo = curStateInfo.parentStateInfo; 1021 } 1022 1023 // Empty the StateStack 1024 mStateStackTopIndex = -1; 1025 1026 moveTempStateStackToStateStack(); 1027 } 1028 1029 /** 1030 * @return current message 1031 */ 1032 private final Message getCurrentMessage() { 1033 return mMsg; 1034 } 1035 1036 /** 1037 * @return current state 1038 */ 1039 private final IState getCurrentState() { 1040 return mStateStack[mStateStackTopIndex].state; 1041 } 1042 1043 /** 1044 * Add a new state to the state machine. Bottom up addition 1045 * of states is allowed but the same state may only exist 1046 * in one hierarchy. 1047 * 1048 * @param state the state to add 1049 * @param parent the parent of state 1050 * @return stateInfo for this state 1051 */ 1052 private final StateInfo addState(State state, State parent) { 1053 if (mDbg) { 1054 Log.d(TAG, "addStateInternal: E state=" + state.getName() 1055 + ",parent=" + ((parent == null) ? "" : parent.getName())); 1056 } 1057 StateInfo parentStateInfo = null; 1058 if (parent != null) { 1059 parentStateInfo = mStateInfo.get(parent); 1060 if (parentStateInfo == null) { 1061 // Recursively add our parent as it's not been added yet. 1062 parentStateInfo = addState(parent, null); 1063 } 1064 } 1065 StateInfo stateInfo = mStateInfo.get(state); 1066 if (stateInfo == null) { 1067 stateInfo = new StateInfo(); 1068 mStateInfo.put(state, stateInfo); 1069 } 1070 1071 // Validate that we aren't adding the same state in two different hierarchies. 1072 if ((stateInfo.parentStateInfo != null) && 1073 (stateInfo.parentStateInfo != parentStateInfo)) { 1074 throw new RuntimeException("state already added"); 1075 } 1076 stateInfo.state = state; 1077 stateInfo.parentStateInfo = parentStateInfo; 1078 stateInfo.active = false; 1079 if (mDbg) Log.d(TAG, "addStateInternal: X stateInfo: " + stateInfo); 1080 return stateInfo; 1081 } 1082 1083 /** 1084 * Constructor 1085 * 1086 * @param looper for dispatching messages 1087 * @param sm the hierarchical state machine 1088 */ 1089 private SmHandler(Looper looper, StateMachine sm) { 1090 super(looper); 1091 mSm = sm; 1092 1093 addState(mHaltingState, null); 1094 addState(mQuittingState, null); 1095 } 1096 1097 /** @see StateMachine#setInitialState(State) */ 1098 private final void setInitialState(State initialState) { 1099 if (mDbg) Log.d(TAG, "setInitialState: initialState=" + initialState.getName()); 1100 mInitialState = initialState; 1101 } 1102 1103 /** @see StateMachine#transitionTo(IState) */ 1104 private final void transitionTo(IState destState) { 1105 mDestState = (State) destState; 1106 if (mDbg) Log.d(TAG, "transitionTo: destState=" + mDestState.getName()); 1107 } 1108 1109 /** @see StateMachine#deferMessage(Message) */ 1110 private final void deferMessage(Message msg) { 1111 if (mDbg) Log.d(TAG, "deferMessage: msg=" + msg.what); 1112 1113 /* Copy the "msg" to "newMsg" as "msg" will be recycled */ 1114 Message newMsg = obtainMessage(); 1115 newMsg.copyFrom(msg); 1116 1117 mDeferredMessages.add(newMsg); 1118 } 1119 1120 /** @see StateMachine#deferMessage(Message) */ 1121 private final void quit() { 1122 if (mDbg) Log.d(TAG, "quit:"); 1123 sendMessage(obtainMessage(SM_QUIT_CMD, mSmHandlerObj)); 1124 } 1125 1126 /** @see StateMachine#isQuit(Message) */ 1127 private final boolean isQuit(Message msg) { 1128 return (msg.what == SM_QUIT_CMD) && (msg.obj == mSmHandlerObj); 1129 } 1130 1131 /** @see StateMachine#isDbg() */ 1132 private final boolean isDbg() { 1133 return mDbg; 1134 } 1135 1136 /** @see StateMachine#setDbg(boolean) */ 1137 private final void setDbg(boolean dbg) { 1138 mDbg = dbg; 1139 } 1140 1141 /** @see StateMachine#setProcessedMessagesSize(int) */ 1142 private final void setProcessedMessagesSize(int maxSize) { 1143 mProcessedMessages.setSize(maxSize); 1144 } 1145 1146 /** @see StateMachine#getProcessedMessagesSize() */ 1147 private final int getProcessedMessagesSize() { 1148 return mProcessedMessages.size(); 1149 } 1150 1151 /** @see StateMachine#getProcessedMessagesCount() */ 1152 private final int getProcessedMessagesCount() { 1153 return mProcessedMessages.count(); 1154 } 1155 1156 /** @see StateMachine#getProcessedMessageInfo(int) */ 1157 private final ProcessedMessageInfo getProcessedMessageInfo(int index) { 1158 return mProcessedMessages.get(index); 1159 } 1160 1161 } 1162 1163 private SmHandler mSmHandler; 1164 private HandlerThread mSmThread; 1165 1166 /** 1167 * Initialize. 1168 * 1169 * @param looper for this state machine 1170 * @param name of the state machine 1171 */ 1172 private void initStateMachine(String name, Looper looper) { 1173 mName = name; 1174 mSmHandler = new SmHandler(looper, this); 1175 } 1176 1177 /** 1178 * Constructor creates a StateMachine with its own thread. 1179 * 1180 * @param name of the state machine 1181 */ 1182 protected StateMachine(String name) { 1183 mSmThread = new HandlerThread(name); 1184 mSmThread.start(); 1185 Looper looper = mSmThread.getLooper(); 1186 1187 initStateMachine(name, looper); 1188 } 1189 1190 /** 1191 * Constructor creates a StateMachine using the looper. 1192 * 1193 * @param name of the state machine 1194 */ 1195 protected StateMachine(String name, Looper looper) { 1196 initStateMachine(name, looper); 1197 } 1198 1199 /** 1200 * Add a new state to the state machine 1201 * @param state the state to add 1202 * @param parent the parent of state 1203 */ 1204 protected final void addState(State state, State parent) { 1205 mSmHandler.addState(state, parent); 1206 } 1207 1208 /** 1209 * @return current message 1210 */ 1211 protected final Message getCurrentMessage() { 1212 return mSmHandler.getCurrentMessage(); 1213 } 1214 1215 /** 1216 * @return current state 1217 */ 1218 protected final IState getCurrentState() { 1219 return mSmHandler.getCurrentState(); 1220 } 1221 1222 /** 1223 * Add a new state to the state machine, parent will be null 1224 * @param state to add 1225 */ 1226 protected final void addState(State state) { 1227 mSmHandler.addState(state, null); 1228 } 1229 1230 /** 1231 * Set the initial state. This must be invoked before 1232 * and messages are sent to the state machine. 1233 * 1234 * @param initialState is the state which will receive the first message. 1235 */ 1236 protected final void setInitialState(State initialState) { 1237 mSmHandler.setInitialState(initialState); 1238 } 1239 1240 /** 1241 * transition to destination state. Upon returning 1242 * from processMessage the current state's exit will 1243 * be executed and upon the next message arriving 1244 * destState.enter will be invoked. 1245 * 1246 * this function can also be called inside the enter function of the 1247 * previous transition target, but the behavior is undefined when it is 1248 * called mid-way through a previous transition (for example, calling this 1249 * in the enter() routine of a intermediate node when the current transition 1250 * target is one of the nodes descendants). 1251 * 1252 * @param destState will be the state that receives the next message. 1253 */ 1254 protected final void transitionTo(IState destState) { 1255 mSmHandler.transitionTo(destState); 1256 } 1257 1258 /** 1259 * transition to halt state. Upon returning 1260 * from processMessage we will exit all current 1261 * states, execute the halting() method and then 1262 * all subsequent messages haltedProcessMesage 1263 * will be called. 1264 */ 1265 protected final void transitionToHaltingState() { 1266 mSmHandler.transitionTo(mSmHandler.mHaltingState); 1267 } 1268 1269 /** 1270 * Defer this message until next state transition. 1271 * Upon transitioning all deferred messages will be 1272 * placed on the queue and reprocessed in the original 1273 * order. (i.e. The next state the oldest messages will 1274 * be processed first) 1275 * 1276 * @param msg is deferred until the next transition. 1277 */ 1278 protected final void deferMessage(Message msg) { 1279 mSmHandler.deferMessage(msg); 1280 } 1281 1282 1283 /** 1284 * Called when message wasn't handled 1285 * 1286 * @param msg that couldn't be handled. 1287 */ 1288 protected void unhandledMessage(Message msg) { 1289 if (mSmHandler.mDbg) Log.e(TAG, mName + " - unhandledMessage: msg.what=" + msg.what); 1290 } 1291 1292 /** 1293 * Called for any message that is received after 1294 * transitionToHalting is called. 1295 */ 1296 protected void haltedProcessMessage(Message msg) { 1297 } 1298 1299 /** 1300 * This will be called once after handling a message that called 1301 * transitionToHalting. All subsequent messages will invoke 1302 * {@link StateMachine#haltedProcessMessage(Message)} 1303 */ 1304 protected void halting() { 1305 } 1306 1307 /** 1308 * This will be called once after a quit message that was NOT handled by 1309 * the derived StateMachine. The StateMachine will stop and any subsequent messages will be 1310 * ignored. In addition, if this StateMachine created the thread, the thread will 1311 * be stopped after this method returns. 1312 */ 1313 protected void quitting() { 1314 } 1315 1316 /** 1317 * @return the name 1318 */ 1319 public final String getName() { 1320 return mName; 1321 } 1322 1323 /** 1324 * Set size of messages to maintain and clears all current messages. 1325 * 1326 * @param maxSize number of messages to maintain at anyone time. 1327 */ 1328 public final void setProcessedMessagesSize(int maxSize) { 1329 mSmHandler.setProcessedMessagesSize(maxSize); 1330 } 1331 1332 /** 1333 * @return number of messages processed 1334 */ 1335 public final int getProcessedMessagesSize() { 1336 return mSmHandler.getProcessedMessagesSize(); 1337 } 1338 1339 /** 1340 * @return the total number of messages processed 1341 */ 1342 public final int getProcessedMessagesCount() { 1343 return mSmHandler.getProcessedMessagesCount(); 1344 } 1345 1346 /** 1347 * @return a processed message information 1348 */ 1349 public final ProcessedMessageInfo getProcessedMessageInfo(int index) { 1350 return mSmHandler.getProcessedMessageInfo(index); 1351 } 1352 1353 /** 1354 * @return Handler 1355 */ 1356 public final Handler getHandler() { 1357 return mSmHandler; 1358 } 1359 1360 /** 1361 * Get a message and set Message.target = this. 1362 * 1363 * @return message or null if SM has quit 1364 */ 1365 public final Message obtainMessage() 1366 { 1367 if (mSmHandler == null) return null; 1368 1369 return Message.obtain(mSmHandler); 1370 } 1371 1372 /** 1373 * Get a message and set Message.target = this and what 1374 * 1375 * @param what is the assigned to Message.what. 1376 * @return message or null if SM has quit 1377 */ 1378 public final Message obtainMessage(int what) { 1379 if (mSmHandler == null) return null; 1380 1381 return Message.obtain(mSmHandler, what); 1382 } 1383 1384 /** 1385 * Get a message and set Message.target = this, 1386 * what and obj. 1387 * 1388 * @param what is the assigned to Message.what. 1389 * @param obj is assigned to Message.obj. 1390 * @return message or null if SM has quit 1391 */ 1392 public final Message obtainMessage(int what, Object obj) 1393 { 1394 if (mSmHandler == null) return null; 1395 1396 return Message.obtain(mSmHandler, what, obj); 1397 } 1398 1399 /** 1400 * Get a message and set Message.target = this, 1401 * what, arg1 and arg2 1402 * 1403 * @param what is assigned to Message.what 1404 * @param arg1 is assigned to Message.arg1 1405 * @param arg2 is assigned to Message.arg2 1406 * @return A Message object from the global pool or null if 1407 * SM has quit 1408 */ 1409 public final Message obtainMessage(int what, int arg1, int arg2) 1410 { 1411 if (mSmHandler == null) return null; 1412 1413 return Message.obtain(mSmHandler, what, arg1, arg2); 1414 } 1415 1416 /** 1417 * Get a message and set Message.target = this, 1418 * what, arg1, arg2 and obj 1419 * 1420 * @param what is assigned to Message.what 1421 * @param arg1 is assigned to Message.arg1 1422 * @param arg2 is assigned to Message.arg2 1423 * @param obj is assigned to Message.obj 1424 * @return A Message object from the global pool or null if 1425 * SM has quit 1426 */ 1427 public final Message obtainMessage(int what, int arg1, int arg2, Object obj) 1428 { 1429 if (mSmHandler == null) return null; 1430 1431 return Message.obtain(mSmHandler, what, arg1, arg2, obj); 1432 } 1433 1434 /** 1435 * Enqueue a message to this state machine. 1436 */ 1437 public final void sendMessage(int what) { 1438 // mSmHandler can be null if the state machine has quit. 1439 if (mSmHandler == null) return; 1440 1441 mSmHandler.sendMessage(obtainMessage(what)); 1442 } 1443 1444 /** 1445 * Enqueue a message to this state machine. 1446 */ 1447 public final void sendMessage(int what, Object obj) { 1448 // mSmHandler can be null if the state machine has quit. 1449 if (mSmHandler == null) return; 1450 1451 mSmHandler.sendMessage(obtainMessage(what,obj)); 1452 } 1453 1454 /** 1455 * Enqueue a message to this state machine. 1456 */ 1457 public final void sendMessage(Message msg) { 1458 // mSmHandler can be null if the state machine has quit. 1459 if (mSmHandler == null) return; 1460 1461 mSmHandler.sendMessage(msg); 1462 } 1463 1464 /** 1465 * Enqueue a message to this state machine after a delay. 1466 */ 1467 public final void sendMessageDelayed(int what, long delayMillis) { 1468 // mSmHandler can be null if the state machine has quit. 1469 if (mSmHandler == null) return; 1470 1471 mSmHandler.sendMessageDelayed(obtainMessage(what), delayMillis); 1472 } 1473 1474 /** 1475 * Enqueue a message to this state machine after a delay. 1476 */ 1477 public final void sendMessageDelayed(int what, Object obj, long delayMillis) { 1478 // mSmHandler can be null if the state machine has quit. 1479 if (mSmHandler == null) return; 1480 1481 mSmHandler.sendMessageDelayed(obtainMessage(what, obj), delayMillis); 1482 } 1483 1484 /** 1485 * Enqueue a message to this state machine after a delay. 1486 */ 1487 public final void sendMessageDelayed(Message msg, long delayMillis) { 1488 // mSmHandler can be null if the state machine has quit. 1489 if (mSmHandler == null) return; 1490 1491 mSmHandler.sendMessageDelayed(msg, delayMillis); 1492 } 1493 1494 /** 1495 * Enqueue a message to the front of the queue for this state machine. 1496 * Protected, may only be called by instances of StateMachine. 1497 */ 1498 protected final void sendMessageAtFrontOfQueue(int what, Object obj) { 1499 mSmHandler.sendMessageAtFrontOfQueue(obtainMessage(what, obj)); 1500 } 1501 1502 /** 1503 * Enqueue a message to the front of the queue for this state machine. 1504 * Protected, may only be called by instances of StateMachine. 1505 */ 1506 protected final void sendMessageAtFrontOfQueue(int what) { 1507 mSmHandler.sendMessageAtFrontOfQueue(obtainMessage(what)); 1508 } 1509 1510 /** 1511 * Enqueue a message to the front of the queue for this state machine. 1512 * Protected, may only be called by instances of StateMachine. 1513 */ 1514 protected final void sendMessageAtFrontOfQueue(Message msg) { 1515 mSmHandler.sendMessageAtFrontOfQueue(msg); 1516 } 1517 1518 /** 1519 * Removes a message from the message queue. 1520 * Protected, may only be called by instances of StateMachine. 1521 */ 1522 protected final void removeMessages(int what) { 1523 mSmHandler.removeMessages(what); 1524 } 1525 1526 /** 1527 * Conditionally quit the looper and stop execution. 1528 * 1529 * This sends the SM_QUIT_MSG to the state machine and 1530 * if not handled by any state's processMessage then the 1531 * state machine will be stopped and no further messages 1532 * will be processed. 1533 */ 1534 public final void quit() { 1535 // mSmHandler can be null if the state machine has quit. 1536 if (mSmHandler == null) return; 1537 1538 mSmHandler.quit(); 1539 } 1540 1541 /** 1542 * @return ture if msg is quit 1543 */ 1544 protected final boolean isQuit(Message msg) { 1545 return mSmHandler.isQuit(msg); 1546 } 1547 1548 /** 1549 * @return if debugging is enabled 1550 */ 1551 public boolean isDbg() { 1552 // mSmHandler can be null if the state machine has quit. 1553 if (mSmHandler == null) return false; 1554 1555 return mSmHandler.isDbg(); 1556 } 1557 1558 /** 1559 * Set debug enable/disabled. 1560 * 1561 * @param dbg is true to enable debugging. 1562 */ 1563 public void setDbg(boolean dbg) { 1564 // mSmHandler can be null if the state machine has quit. 1565 if (mSmHandler == null) return; 1566 1567 mSmHandler.setDbg(dbg); 1568 } 1569 1570 /** 1571 * Start the state machine. 1572 */ 1573 public void start() { 1574 // mSmHandler can be null if the state machine has quit. 1575 if (mSmHandler == null) return; 1576 1577 /** Send the complete construction message */ 1578 mSmHandler.completeConstruction(); 1579 } 1580} 1581