1 /** 2 * Copyright (C) 2009 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.internal.util; 18 19 import android.compat.annotation.UnsupportedAppUsage; 20 import android.os.Build; 21 import android.os.Handler; 22 import android.os.HandlerThread; 23 import android.os.Looper; 24 import android.os.Message; 25 import android.text.TextUtils; 26 import android.util.Log; 27 28 import com.android.internal.annotations.VisibleForTesting; 29 30 import java.io.FileDescriptor; 31 import java.io.PrintWriter; 32 import java.util.ArrayList; 33 import java.util.Calendar; 34 import java.util.Collection; 35 import java.util.HashMap; 36 import java.util.Iterator; 37 import java.util.Vector; 38 39 /** 40 * {@hide} 41 * 42 * <p>The state machine defined here is a hierarchical state machine which processes messages 43 * and can have states arranged hierarchically.</p> 44 * 45 * <p>A state is a <code>State</code> object and must implement 46 * <code>processMessage</code> and optionally <code>enter/exit/getName</code>. 47 * The enter/exit methods are equivalent to the construction and destruction 48 * in Object Oriented programming and are used to perform initialization and 49 * cleanup of the state respectively. The <code>getName</code> method returns the 50 * name of the state; the default implementation returns the class name. It may be 51 * desirable to have <code>getName</code> return the the state instance name instead, 52 * in particular if a particular state class has multiple instances.</p> 53 * 54 * <p>When a state machine is created, <code>addState</code> is used to build the 55 * hierarchy and <code>setInitialState</code> is used to identify which of these 56 * is the initial state. After construction the programmer calls <code>start</code> 57 * which initializes and starts the state machine. The first action the StateMachine 58 * is to the invoke <code>enter</code> for all of the initial state's hierarchy, 59 * starting at its eldest parent. The calls to enter will be done in the context 60 * of the StateMachine's Handler, not in the context of the call to start, and they 61 * will be invoked before any messages are processed. For example, given the simple 62 * state machine below, mP1.enter will be invoked and then mS1.enter. Finally, 63 * messages sent to the state machine will be processed by the current state; 64 * in our simple state machine below that would initially be mS1.processMessage.</p> 65 <pre> 66 mP1 67 / \ 68 mS2 mS1 ----> initial state 69 </pre> 70 * <p>After the state machine is created and started, messages are sent to a state 71 * machine using <code>sendMessage</code> and the messages are created using 72 * <code>obtainMessage</code>. When the state machine receives a message the 73 * current state's <code>processMessage</code> is invoked. In the above example 74 * mS1.processMessage will be invoked first. The state may use <code>transitionTo</code> 75 * to change the current state to a new state.</p> 76 * 77 * <p>Each state in the state machine may have a zero or one parent states. If 78 * a child state is unable to handle a message it may have the message processed 79 * by its parent by returning false or NOT_HANDLED. If a message is not handled by 80 * a child state or any of its ancestors, <code>unhandledMessage</code> will be invoked 81 * to give one last chance for the state machine to process the message.</p> 82 * 83 * <p>When all processing is completed a state machine may choose to call 84 * <code>transitionToHaltingState</code>. When the current <code>processingMessage</code> 85 * returns the state machine will transfer to an internal <code>HaltingState</code> 86 * and invoke <code>halting</code>. Any message subsequently received by the state 87 * machine will cause <code>haltedProcessMessage</code> to be invoked.</p> 88 * 89 * <p>If it is desirable to completely stop the state machine call <code>quit</code> or 90 * <code>quitNow</code>. These will call <code>exit</code> of the current state and its parents, 91 * call <code>onQuitting</code> and then exit Thread/Loopers.</p> 92 * 93 * <p>In addition to <code>processMessage</code> each <code>State</code> has 94 * an <code>enter</code> method and <code>exit</code> method which may be overridden.</p> 95 * 96 * <p>Since the states are arranged in a hierarchy transitioning to a new state 97 * causes current states to be exited and new states to be entered. To determine 98 * the list of states to be entered/exited the common parent closest to 99 * the current state is found. We then exit from the current state and its 100 * parent's up to but not including the common parent state and then enter all 101 * of the new states below the common parent down to the destination state. 102 * If there is no common parent all states are exited and then the new states 103 * are entered.</p> 104 * 105 * <p>Two other methods that states can use are <code>deferMessage</code> and 106 * <code>sendMessageAtFrontOfQueue</code>. The <code>sendMessageAtFrontOfQueue</code> sends 107 * a message but places it on the front of the queue rather than the back. The 108 * <code>deferMessage</code> causes the message to be saved on a list until a 109 * transition is made to a new state. At which time all of the deferred messages 110 * will be put on the front of the state machine queue with the oldest message 111 * at the front. These will then be processed by the new current state before 112 * any other messages that are on the queue or might be added later. Both of 113 * these are protected and may only be invoked from within a state machine.</p> 114 * 115 * <p>To illustrate some of these properties we'll use state machine with an 8 116 * state hierarchy:</p> 117 <pre> 118 mP0 119 / \ 120 mP1 mS0 121 / \ 122 mS2 mS1 123 / \ \ 124 mS3 mS4 mS5 ---> initial state 125 </pre> 126 * <p>After starting mS5 the list of active states is mP0, mP1, mS1 and mS5. 127 * So the order of calling processMessage when a message is received is mS5, 128 * mS1, mP1, mP0 assuming each processMessage indicates it can't handle this 129 * message by returning false or NOT_HANDLED.</p> 130 * 131 * <p>Now assume mS5.processMessage receives a message it can handle, and during 132 * the handling determines the machine should change states. It could call 133 * transitionTo(mS4) and return true or HANDLED. Immediately after returning from 134 * processMessage the state machine runtime will find the common parent, 135 * which is mP1. It will then call mS5.exit, mS1.exit, mS2.enter and then 136 * mS4.enter. The new list of active states is mP0, mP1, mS2 and mS4. So 137 * when the next message is received mS4.processMessage will be invoked.</p> 138 * 139 * <p>Now for some concrete examples, here is the canonical HelloWorld as a state machine. 140 * It responds with "Hello World" being printed to the log for every message.</p> 141 <pre> 142 class HelloWorld extends StateMachine { 143 HelloWorld(String name) { 144 super(name); 145 addState(mState1); 146 setInitialState(mState1); 147 } 148 149 public static HelloWorld makeHelloWorld() { 150 HelloWorld hw = new HelloWorld("hw"); 151 hw.start(); 152 return hw; 153 } 154 155 class State1 extends State { 156 @Override public boolean processMessage(Message message) { 157 log("Hello World"); 158 return HANDLED; 159 } 160 } 161 State1 mState1 = new State1(); 162 } 163 164 void testHelloWorld() { 165 HelloWorld hw = makeHelloWorld(); 166 hw.sendMessage(hw.obtainMessage()); 167 } 168 </pre> 169 * <p>A more interesting state machine is one with four states 170 * with two independent parent states.</p> 171 <pre> 172 mP1 mP2 173 / \ 174 mS2 mS1 175 </pre> 176 * <p>Here is a description of this state machine using pseudo code.</p> 177 <pre> 178 state mP1 { 179 enter { log("mP1.enter"); } 180 exit { log("mP1.exit"); } 181 on msg { 182 CMD_2 { 183 send(CMD_3); 184 defer(msg); 185 transitionTo(mS2); 186 return HANDLED; 187 } 188 return NOT_HANDLED; 189 } 190 } 191 192 INITIAL 193 state mS1 parent mP1 { 194 enter { log("mS1.enter"); } 195 exit { log("mS1.exit"); } 196 on msg { 197 CMD_1 { 198 transitionTo(mS1); 199 return HANDLED; 200 } 201 return NOT_HANDLED; 202 } 203 } 204 205 state mS2 parent mP1 { 206 enter { log("mS2.enter"); } 207 exit { log("mS2.exit"); } 208 on msg { 209 CMD_2 { 210 send(CMD_4); 211 return HANDLED; 212 } 213 CMD_3 { 214 defer(msg); 215 transitionTo(mP2); 216 return HANDLED; 217 } 218 return NOT_HANDLED; 219 } 220 } 221 222 state mP2 { 223 enter { 224 log("mP2.enter"); 225 send(CMD_5); 226 } 227 exit { log("mP2.exit"); } 228 on msg { 229 CMD_3, CMD_4 { return HANDLED; } 230 CMD_5 { 231 transitionTo(HaltingState); 232 return HANDLED; 233 } 234 return NOT_HANDLED; 235 } 236 } 237 </pre> 238 * <p>The implementation is below and also in StateMachineTest:</p> 239 <pre> 240 class Hsm1 extends StateMachine { 241 public static final int CMD_1 = 1; 242 public static final int CMD_2 = 2; 243 public static final int CMD_3 = 3; 244 public static final int CMD_4 = 4; 245 public static final int CMD_5 = 5; 246 247 public static Hsm1 makeHsm1() { 248 log("makeHsm1 E"); 249 Hsm1 sm = new Hsm1("hsm1"); 250 sm.start(); 251 log("makeHsm1 X"); 252 return sm; 253 } 254 255 Hsm1(String name) { 256 super(name); 257 log("ctor E"); 258 259 // Add states, use indentation to show hierarchy 260 addState(mP1); 261 addState(mS1, mP1); 262 addState(mS2, mP1); 263 addState(mP2); 264 265 // Set the initial state 266 setInitialState(mS1); 267 log("ctor X"); 268 } 269 270 class P1 extends State { 271 @Override public void enter() { 272 log("mP1.enter"); 273 } 274 @Override public boolean processMessage(Message message) { 275 boolean retVal; 276 log("mP1.processMessage what=" + message.what); 277 switch(message.what) { 278 case CMD_2: 279 // CMD_2 will arrive in mS2 before CMD_3 280 sendMessage(obtainMessage(CMD_3)); 281 deferMessage(message); 282 transitionTo(mS2); 283 retVal = HANDLED; 284 break; 285 default: 286 // Any message we don't understand in this state invokes unhandledMessage 287 retVal = NOT_HANDLED; 288 break; 289 } 290 return retVal; 291 } 292 @Override public void exit() { 293 log("mP1.exit"); 294 } 295 } 296 297 class S1 extends State { 298 @Override public void enter() { 299 log("mS1.enter"); 300 } 301 @Override public boolean processMessage(Message message) { 302 log("S1.processMessage what=" + message.what); 303 if (message.what == CMD_1) { 304 // Transition to ourself to show that enter/exit is called 305 transitionTo(mS1); 306 return HANDLED; 307 } else { 308 // Let parent process all other messages 309 return NOT_HANDLED; 310 } 311 } 312 @Override public void exit() { 313 log("mS1.exit"); 314 } 315 } 316 317 class S2 extends State { 318 @Override public void enter() { 319 log("mS2.enter"); 320 } 321 @Override public boolean processMessage(Message message) { 322 boolean retVal; 323 log("mS2.processMessage what=" + message.what); 324 switch(message.what) { 325 case(CMD_2): 326 sendMessage(obtainMessage(CMD_4)); 327 retVal = HANDLED; 328 break; 329 case(CMD_3): 330 deferMessage(message); 331 transitionTo(mP2); 332 retVal = HANDLED; 333 break; 334 default: 335 retVal = NOT_HANDLED; 336 break; 337 } 338 return retVal; 339 } 340 @Override public void exit() { 341 log("mS2.exit"); 342 } 343 } 344 345 class P2 extends State { 346 @Override public void enter() { 347 log("mP2.enter"); 348 sendMessage(obtainMessage(CMD_5)); 349 } 350 @Override public boolean processMessage(Message message) { 351 log("P2.processMessage what=" + message.what); 352 switch(message.what) { 353 case(CMD_3): 354 break; 355 case(CMD_4): 356 break; 357 case(CMD_5): 358 transitionToHaltingState(); 359 break; 360 } 361 return HANDLED; 362 } 363 @Override public void exit() { 364 log("mP2.exit"); 365 } 366 } 367 368 @Override 369 void onHalting() { 370 log("halting"); 371 synchronized (this) { 372 this.notifyAll(); 373 } 374 } 375 376 P1 mP1 = new P1(); 377 S1 mS1 = new S1(); 378 S2 mS2 = new S2(); 379 P2 mP2 = new P2(); 380 } 381 </pre> 382 * <p>If this is executed by sending two messages CMD_1 and CMD_2 383 * (Note the synchronize is only needed because we use hsm.wait())</p> 384 <pre> 385 Hsm1 hsm = makeHsm1(); 386 synchronize(hsm) { 387 hsm.sendMessage(obtainMessage(hsm.CMD_1)); 388 hsm.sendMessage(obtainMessage(hsm.CMD_2)); 389 try { 390 // wait for the messages to be handled 391 hsm.wait(); 392 } catch (InterruptedException e) { 393 loge("exception while waiting " + e.getMessage()); 394 } 395 } 396 </pre> 397 * <p>The output is:</p> 398 <pre> 399 D/hsm1 ( 1999): makeHsm1 E 400 D/hsm1 ( 1999): ctor E 401 D/hsm1 ( 1999): ctor X 402 D/hsm1 ( 1999): mP1.enter 403 D/hsm1 ( 1999): mS1.enter 404 D/hsm1 ( 1999): makeHsm1 X 405 D/hsm1 ( 1999): mS1.processMessage what=1 406 D/hsm1 ( 1999): mS1.exit 407 D/hsm1 ( 1999): mS1.enter 408 D/hsm1 ( 1999): mS1.processMessage what=2 409 D/hsm1 ( 1999): mP1.processMessage what=2 410 D/hsm1 ( 1999): mS1.exit 411 D/hsm1 ( 1999): mS2.enter 412 D/hsm1 ( 1999): mS2.processMessage what=2 413 D/hsm1 ( 1999): mS2.processMessage what=3 414 D/hsm1 ( 1999): mS2.exit 415 D/hsm1 ( 1999): mP1.exit 416 D/hsm1 ( 1999): mP2.enter 417 D/hsm1 ( 1999): mP2.processMessage what=3 418 D/hsm1 ( 1999): mP2.processMessage what=4 419 D/hsm1 ( 1999): mP2.processMessage what=5 420 D/hsm1 ( 1999): mP2.exit 421 D/hsm1 ( 1999): halting 422 </pre> 423 */ 424 public class StateMachine { 425 // Name of the state machine and used as logging tag 426 private String mName; 427 428 /** Message.what value when quitting */ 429 private static final int SM_QUIT_CMD = -1; 430 431 /** Message.what value when initializing */ 432 private static final int SM_INIT_CMD = -2; 433 434 /** 435 * Convenience constant that maybe returned by processMessage 436 * to indicate the the message was processed and is not to be 437 * processed by parent states 438 */ 439 public static final boolean HANDLED = true; 440 441 /** 442 * Convenience constant that maybe returned by processMessage 443 * to indicate the the message was NOT processed and is to be 444 * processed by parent states 445 */ 446 public static final boolean NOT_HANDLED = false; 447 448 /** 449 * StateMachine logging record. 450 * {@hide} 451 */ 452 public static class LogRec { 453 private StateMachine mSm; 454 private long mTime; 455 private int mWhat; 456 private String mInfo; 457 private IState mState; 458 private IState mOrgState; 459 private IState mDstState; 460 461 /** 462 * Constructor 463 * 464 * @param msg 465 * @param state the state which handled the message 466 * @param orgState is the first state the received the message but 467 * did not processes the message. 468 * @param transToState is the state that was transitioned to after the message was 469 * processed. 470 */ LogRec(StateMachine sm, Message msg, String info, IState state, IState orgState, IState transToState)471 LogRec(StateMachine sm, Message msg, String info, IState state, IState orgState, 472 IState transToState) { 473 update(sm, msg, info, state, orgState, transToState); 474 } 475 476 /** 477 * Update the information in the record. 478 * @param state that handled the message 479 * @param orgState is the first state the received the message 480 * @param dstState is the state that was the transition target when logging 481 */ update(StateMachine sm, Message msg, String info, IState state, IState orgState, IState dstState)482 public void update(StateMachine sm, Message msg, String info, IState state, IState orgState, 483 IState dstState) { 484 mSm = sm; 485 mTime = System.currentTimeMillis(); 486 mWhat = (msg != null) ? msg.what : 0; 487 mInfo = info; 488 mState = state; 489 mOrgState = orgState; 490 mDstState = dstState; 491 } 492 493 /** 494 * @return time stamp 495 */ getTime()496 public long getTime() { 497 return mTime; 498 } 499 500 /** 501 * @return msg.what 502 */ getWhat()503 public long getWhat() { 504 return mWhat; 505 } 506 507 /** 508 * @return the command that was executing 509 */ getInfo()510 public String getInfo() { 511 return mInfo; 512 } 513 514 /** 515 * @return the state that handled this message 516 */ getState()517 public IState getState() { 518 return mState; 519 } 520 521 /** 522 * @return the state destination state if a transition is occurring or null if none. 523 */ getDestState()524 public IState getDestState() { 525 return mDstState; 526 } 527 528 /** 529 * @return the original state that received the message. 530 */ getOriginalState()531 public IState getOriginalState() { 532 return mOrgState; 533 } 534 535 @Override toString()536 public String toString() { 537 StringBuilder sb = new StringBuilder(); 538 sb.append("time="); 539 Calendar c = Calendar.getInstance(); 540 c.setTimeInMillis(mTime); 541 sb.append(String.format("%tm-%td %tH:%tM:%tS.%tL", c, c, c, c, c, c)); 542 sb.append(" processed="); 543 sb.append(mState == null ? "<null>" : mState.getName()); 544 sb.append(" org="); 545 sb.append(mOrgState == null ? "<null>" : mOrgState.getName()); 546 sb.append(" dest="); 547 sb.append(mDstState == null ? "<null>" : mDstState.getName()); 548 sb.append(" what="); 549 String what = mSm != null ? mSm.getWhatToString(mWhat) : ""; 550 if (TextUtils.isEmpty(what)) { 551 sb.append(mWhat); 552 sb.append("(0x"); 553 sb.append(Integer.toHexString(mWhat)); 554 sb.append(")"); 555 } else { 556 sb.append(what); 557 } 558 if (!TextUtils.isEmpty(mInfo)) { 559 sb.append(" "); 560 sb.append(mInfo); 561 } 562 return sb.toString(); 563 } 564 } 565 566 /** 567 * A list of log records including messages recently processed by the state machine. 568 * 569 * The class maintains a list of log records including messages 570 * recently processed. The list is finite and may be set in the 571 * constructor or by calling setSize. The public interface also 572 * includes size which returns the number of recent records, 573 * count which is the number of records processed since the 574 * the last setSize, get which returns a record and 575 * add which adds a record. 576 */ 577 private static class LogRecords { 578 579 private static final int DEFAULT_SIZE = 20; 580 581 private Vector<LogRec> mLogRecVector = new Vector<LogRec>(); 582 private int mMaxSize = DEFAULT_SIZE; 583 private int mOldestIndex = 0; 584 private int mCount = 0; 585 private boolean mLogOnlyTransitions = false; 586 587 /** 588 * private constructor use add 589 */ LogRecords()590 private LogRecords() { 591 } 592 593 /** 594 * Set size of messages to maintain and clears all current records. 595 * 596 * @param maxSize number of records to maintain at anyone time. 597 */ setSize(int maxSize)598 synchronized void setSize(int maxSize) { 599 // TODO: once b/28217358 is fixed, add unit tests to verify that these variables are 600 // cleared after calling this method, and that subsequent calls to get() function as 601 // expected. 602 mMaxSize = maxSize; 603 mOldestIndex = 0; 604 mCount = 0; 605 mLogRecVector.clear(); 606 } 607 setLogOnlyTransitions(boolean enable)608 synchronized void setLogOnlyTransitions(boolean enable) { 609 mLogOnlyTransitions = enable; 610 } 611 logOnlyTransitions()612 synchronized boolean logOnlyTransitions() { 613 return mLogOnlyTransitions; 614 } 615 616 /** 617 * @return the number of recent records. 618 */ size()619 synchronized int size() { 620 return mLogRecVector.size(); 621 } 622 623 /** 624 * @return the total number of records processed since size was set. 625 */ count()626 synchronized int count() { 627 return mCount; 628 } 629 630 /** 631 * Clear the list of records. 632 */ cleanup()633 synchronized void cleanup() { 634 mLogRecVector.clear(); 635 } 636 637 /** 638 * @return the information on a particular record. 0 is the oldest 639 * record and size()-1 is the newest record. If the index is to 640 * large null is returned. 641 */ get(int index)642 synchronized LogRec get(int index) { 643 int nextIndex = mOldestIndex + index; 644 if (nextIndex >= mMaxSize) { 645 nextIndex -= mMaxSize; 646 } 647 if (nextIndex >= size()) { 648 return null; 649 } else { 650 return mLogRecVector.get(nextIndex); 651 } 652 } 653 654 /** 655 * Add a processed message. 656 * 657 * @param msg 658 * @param messageInfo to be stored 659 * @param state that handled the message 660 * @param orgState is the first state the received the message but 661 * did not processes the message. 662 * @param transToState is the state that was transitioned to after the message was 663 * processed. 664 * 665 */ add(StateMachine sm, Message msg, String messageInfo, IState state, IState orgState, IState transToState)666 synchronized void add(StateMachine sm, Message msg, String messageInfo, IState state, 667 IState orgState, IState transToState) { 668 mCount += 1; 669 if (mLogRecVector.size() < mMaxSize) { 670 mLogRecVector.add(new LogRec(sm, msg, messageInfo, state, orgState, transToState)); 671 } else { 672 LogRec pmi = mLogRecVector.get(mOldestIndex); 673 mOldestIndex += 1; 674 if (mOldestIndex >= mMaxSize) { 675 mOldestIndex = 0; 676 } 677 pmi.update(sm, msg, messageInfo, state, orgState, transToState); 678 } 679 } 680 } 681 682 private static class SmHandler extends Handler { 683 684 /** true if StateMachine has quit */ 685 private boolean mHasQuit = false; 686 687 /** The debug flag */ 688 private boolean mDbg = false; 689 690 /** The SmHandler object, identifies that message is internal */ 691 private static final Object mSmHandlerObj = new Object(); 692 693 /** The current message */ 694 private Message mMsg; 695 696 /** A list of log records including messages this state machine has processed */ 697 private LogRecords mLogRecords = new LogRecords(); 698 699 /** true if construction of the state machine has not been completed */ 700 private boolean mIsConstructionCompleted; 701 702 /** Stack used to manage the current hierarchy of states */ 703 private StateInfo mStateStack[]; 704 705 /** Top of mStateStack */ 706 private int mStateStackTopIndex = -1; 707 708 /** A temporary stack used to manage the state stack */ 709 private StateInfo mTempStateStack[]; 710 711 /** The top of the mTempStateStack */ 712 private int mTempStateStackCount; 713 714 /** State used when state machine is halted */ 715 private HaltingState mHaltingState = new HaltingState(); 716 717 /** State used when state machine is quitting */ 718 private QuittingState mQuittingState = new QuittingState(); 719 720 /** Reference to the StateMachine */ 721 private StateMachine mSm; 722 723 /** 724 * Information about a state. 725 * Used to maintain the hierarchy. 726 */ 727 private class StateInfo { 728 /** The state */ 729 State state; 730 731 /** The parent of this state, null if there is no parent */ 732 StateInfo parentStateInfo; 733 734 /** True when the state has been entered and on the stack */ 735 boolean active; 736 737 /** 738 * Convert StateInfo to string 739 */ 740 @Override toString()741 public String toString() { 742 return "state=" + state.getName() + ",active=" + active + ",parent=" 743 + ((parentStateInfo == null) ? "null" : parentStateInfo.state.getName()); 744 } 745 } 746 747 /** The map of all of the states in the state machine */ 748 private HashMap<State, StateInfo> mStateInfo = new HashMap<State, StateInfo>(); 749 750 /** The initial state that will process the first message */ 751 private State mInitialState; 752 753 /** The destination state when transitionTo has been invoked */ 754 private State mDestState; 755 756 /** 757 * Indicates if a transition is in progress 758 * 759 * This will be true for all calls of State.exit and all calls of State.enter except for the 760 * last enter call for the current destination state. 761 */ 762 private boolean mTransitionInProgress = false; 763 764 /** The list of deferred messages */ 765 private ArrayList<Message> mDeferredMessages = new ArrayList<Message>(); 766 767 /** 768 * State entered when transitionToHaltingState is called. 769 */ 770 private class HaltingState extends State { 771 @Override processMessage(Message msg)772 public boolean processMessage(Message msg) { 773 mSm.haltedProcessMessage(msg); 774 return true; 775 } 776 } 777 778 /** 779 * State entered when a valid quit message is handled. 780 */ 781 private class QuittingState extends State { 782 @Override processMessage(Message msg)783 public boolean processMessage(Message msg) { 784 return NOT_HANDLED; 785 } 786 } 787 788 /** 789 * Handle messages sent to the state machine by calling 790 * the current state's processMessage. It also handles 791 * the enter/exit calls and placing any deferred messages 792 * back onto the queue when transitioning to a new state. 793 */ 794 @Override handleMessage(Message msg)795 public final void handleMessage(Message msg) { 796 if (!mHasQuit) { 797 if (mSm != null && msg.what != SM_INIT_CMD && msg.what != SM_QUIT_CMD) { 798 mSm.onPreHandleMessage(msg); 799 } 800 801 if (mDbg) mSm.log("handleMessage: E msg.what=" + msg.what); 802 803 /** Save the current message */ 804 mMsg = msg; 805 806 /** State that processed the message */ 807 State msgProcessedState = null; 808 if (mIsConstructionCompleted || (mMsg.what == SM_QUIT_CMD)) { 809 /** Normal path */ 810 msgProcessedState = processMsg(msg); 811 } else if (!mIsConstructionCompleted && (mMsg.what == SM_INIT_CMD) 812 && (mMsg.obj == mSmHandlerObj)) { 813 /** Initial one time path. */ 814 mIsConstructionCompleted = true; 815 invokeEnterMethods(0); 816 } else { 817 throw new RuntimeException("StateMachine.handleMessage: " 818 + "The start method not called, received msg: " + msg); 819 } 820 performTransitions(msgProcessedState, msg); 821 822 // We need to check if mSm == null here as we could be quitting. 823 if (mDbg && mSm != null) mSm.log("handleMessage: X"); 824 825 if (mSm != null && msg.what != SM_INIT_CMD && msg.what != SM_QUIT_CMD) { 826 mSm.onPostHandleMessage(msg); 827 } 828 } 829 } 830 831 /** 832 * Do any transitions 833 * @param msgProcessedState is the state that processed the message 834 */ performTransitions(State msgProcessedState, Message msg)835 private void performTransitions(State msgProcessedState, Message msg) { 836 /** 837 * If transitionTo has been called, exit and then enter 838 * the appropriate states. We loop on this to allow 839 * enter and exit methods to use transitionTo. 840 */ 841 State orgState = mStateStack[mStateStackTopIndex].state; 842 843 /** 844 * Record whether message needs to be logged before we transition and 845 * and we won't log special messages SM_INIT_CMD or SM_QUIT_CMD which 846 * always set msg.obj to the handler. 847 */ 848 boolean recordLogMsg = mSm.recordLogRec(mMsg) && (msg.obj != mSmHandlerObj); 849 850 if (mLogRecords.logOnlyTransitions()) { 851 /** Record only if there is a transition */ 852 if (mDestState != null) { 853 mLogRecords.add(mSm, mMsg, mSm.getLogRecString(mMsg), msgProcessedState, 854 orgState, mDestState); 855 } 856 } else if (recordLogMsg) { 857 /** Record message */ 858 mLogRecords.add(mSm, mMsg, mSm.getLogRecString(mMsg), msgProcessedState, orgState, 859 mDestState); 860 } 861 862 State destState = mDestState; 863 if (destState != null) { 864 /** 865 * Process the transitions including transitions in the enter/exit methods 866 */ 867 while (true) { 868 if (mDbg) mSm.log("handleMessage: new destination call exit/enter"); 869 870 /** 871 * Determine the states to exit and enter and return the 872 * common ancestor state of the enter/exit states. Then 873 * invoke the exit methods then the enter methods. 874 */ 875 StateInfo commonStateInfo = setupTempStateStackWithStatesToEnter(destState); 876 // flag is cleared in invokeEnterMethods before entering the target state 877 mTransitionInProgress = true; 878 invokeExitMethods(commonStateInfo); 879 int stateStackEnteringIndex = moveTempStateStackToStateStack(); 880 invokeEnterMethods(stateStackEnteringIndex); 881 882 /** 883 * Since we have transitioned to a new state we need to have 884 * any deferred messages moved to the front of the message queue 885 * so they will be processed before any other messages in the 886 * message queue. 887 */ 888 moveDeferredMessageAtFrontOfQueue(); 889 890 if (destState != mDestState) { 891 // A new mDestState so continue looping 892 destState = mDestState; 893 } else { 894 // No change in mDestState so we're done 895 break; 896 } 897 } 898 mDestState = null; 899 } 900 901 /** 902 * After processing all transitions check and 903 * see if the last transition was to quit or halt. 904 */ 905 if (destState != null) { 906 if (destState == mQuittingState) { 907 /** 908 * Call onQuitting to let subclasses cleanup. 909 */ 910 mSm.onQuitting(); 911 cleanupAfterQuitting(); 912 } else if (destState == mHaltingState) { 913 /** 914 * Call onHalting() if we've transitioned to the halting 915 * state. All subsequent messages will be processed in 916 * in the halting state which invokes haltedProcessMessage(msg); 917 */ 918 mSm.onHalting(); 919 } 920 } 921 } 922 923 /** 924 * Cleanup all the static variables and the looper after the SM has been quit. 925 */ cleanupAfterQuitting()926 private final void cleanupAfterQuitting() { 927 if (mSm.mSmThread != null) { 928 // If we made the thread then quit looper which stops the thread. 929 getLooper().quit(); 930 mSm.mSmThread = null; 931 } 932 933 mSm.mSmHandler = null; 934 mSm = null; 935 mMsg = null; 936 mLogRecords.cleanup(); 937 mStateStack = null; 938 mTempStateStack = null; 939 mStateInfo.clear(); 940 mInitialState = null; 941 mDestState = null; 942 mDeferredMessages.clear(); 943 mHasQuit = true; 944 } 945 946 /** 947 * Complete the construction of the state machine. 948 */ completeConstruction()949 private final void completeConstruction() { 950 if (mDbg) mSm.log("completeConstruction: E"); 951 952 /** 953 * Determine the maximum depth of the state hierarchy 954 * so we can allocate the state stacks. 955 */ 956 int maxDepth = 0; 957 for (StateInfo si : mStateInfo.values()) { 958 int depth = 0; 959 for (StateInfo i = si; i != null; depth++) { 960 i = i.parentStateInfo; 961 } 962 if (maxDepth < depth) { 963 maxDepth = depth; 964 } 965 } 966 if (mDbg) mSm.log("completeConstruction: maxDepth=" + maxDepth); 967 968 mStateStack = new StateInfo[maxDepth]; 969 mTempStateStack = new StateInfo[maxDepth]; 970 setupInitialStateStack(); 971 972 /** Sending SM_INIT_CMD message to invoke enter methods asynchronously */ 973 sendMessageAtFrontOfQueue(obtainMessage(SM_INIT_CMD, mSmHandlerObj)); 974 975 if (mDbg) mSm.log("completeConstruction: X"); 976 } 977 978 /** 979 * Process the message. If the current state doesn't handle 980 * it, call the states parent and so on. If it is never handled then 981 * call the state machines unhandledMessage method. 982 * @return the state that processed the message 983 */ processMsg(Message msg)984 private final State processMsg(Message msg) { 985 StateInfo curStateInfo = mStateStack[mStateStackTopIndex]; 986 if (mDbg) { 987 mSm.log("processMsg: " + curStateInfo.state.getName()); 988 } 989 990 if (isQuit(msg)) { 991 transitionTo(mQuittingState); 992 } else { 993 while (!curStateInfo.state.processMessage(msg)) { 994 /** 995 * Not processed 996 */ 997 curStateInfo = curStateInfo.parentStateInfo; 998 if (curStateInfo == null) { 999 /** 1000 * No parents left so it's not handled 1001 */ 1002 mSm.unhandledMessage(msg); 1003 break; 1004 } 1005 if (mDbg) { 1006 mSm.log("processMsg: " + curStateInfo.state.getName()); 1007 } 1008 } 1009 } 1010 return (curStateInfo != null) ? curStateInfo.state : null; 1011 } 1012 1013 /** 1014 * Call the exit method for each state from the top of stack 1015 * up to the common ancestor state. 1016 */ invokeExitMethods(StateInfo commonStateInfo)1017 private final void invokeExitMethods(StateInfo commonStateInfo) { 1018 while ((mStateStackTopIndex >= 0) 1019 && (mStateStack[mStateStackTopIndex] != commonStateInfo)) { 1020 State curState = mStateStack[mStateStackTopIndex].state; 1021 if (mDbg) mSm.log("invokeExitMethods: " + curState.getName()); 1022 curState.exit(); 1023 mStateStack[mStateStackTopIndex].active = false; 1024 mStateStackTopIndex -= 1; 1025 } 1026 } 1027 1028 /** 1029 * Invoke the enter method starting at the entering index to top of state stack 1030 */ invokeEnterMethods(int stateStackEnteringIndex)1031 private final void invokeEnterMethods(int stateStackEnteringIndex) { 1032 for (int i = stateStackEnteringIndex; i <= mStateStackTopIndex; i++) { 1033 if (stateStackEnteringIndex == mStateStackTopIndex) { 1034 // Last enter state for transition 1035 mTransitionInProgress = false; 1036 } 1037 if (mDbg) mSm.log("invokeEnterMethods: " + mStateStack[i].state.getName()); 1038 mStateStack[i].state.enter(); 1039 mStateStack[i].active = true; 1040 } 1041 mTransitionInProgress = false; // ensure flag set to false if no methods called 1042 } 1043 1044 /** 1045 * Move the deferred message to the front of the message queue. 1046 */ moveDeferredMessageAtFrontOfQueue()1047 private final void moveDeferredMessageAtFrontOfQueue() { 1048 /** 1049 * The oldest messages on the deferred list must be at 1050 * the front of the queue so start at the back, which 1051 * as the most resent message and end with the oldest 1052 * messages at the front of the queue. 1053 */ 1054 for (int i = mDeferredMessages.size() - 1; i >= 0; i--) { 1055 Message curMsg = mDeferredMessages.get(i); 1056 if (mDbg) mSm.log("moveDeferredMessageAtFrontOfQueue; what=" + curMsg.what); 1057 sendMessageAtFrontOfQueue(curMsg); 1058 } 1059 mDeferredMessages.clear(); 1060 } 1061 1062 /** 1063 * Move the contents of the temporary stack to the state stack 1064 * reversing the order of the items on the temporary stack as 1065 * they are moved. 1066 * 1067 * @return index into mStateStack where entering needs to start 1068 */ moveTempStateStackToStateStack()1069 private final int moveTempStateStackToStateStack() { 1070 int startingIndex = mStateStackTopIndex + 1; 1071 int i = mTempStateStackCount - 1; 1072 int j = startingIndex; 1073 while (i >= 0) { 1074 if (mDbg) mSm.log("moveTempStackToStateStack: i=" + i + ",j=" + j); 1075 mStateStack[j] = mTempStateStack[i]; 1076 j += 1; 1077 i -= 1; 1078 } 1079 1080 mStateStackTopIndex = j - 1; 1081 if (mDbg) { 1082 mSm.log("moveTempStackToStateStack: X mStateStackTop=" + mStateStackTopIndex 1083 + ",startingIndex=" + startingIndex + ",Top=" 1084 + mStateStack[mStateStackTopIndex].state.getName()); 1085 } 1086 return startingIndex; 1087 } 1088 1089 /** 1090 * Setup the mTempStateStack with the states we are going to enter. 1091 * 1092 * This is found by searching up the destState's ancestors for a 1093 * state that is already active i.e. StateInfo.active == true. 1094 * The destStae and all of its inactive parents will be on the 1095 * TempStateStack as the list of states to enter. 1096 * 1097 * @return StateInfo of the common ancestor for the destState and 1098 * current state or null if there is no common parent. 1099 */ setupTempStateStackWithStatesToEnter(State destState)1100 private final StateInfo setupTempStateStackWithStatesToEnter(State destState) { 1101 /** 1102 * Search up the parent list of the destination state for an active 1103 * state. Use a do while() loop as the destState must always be entered 1104 * even if it is active. This can happen if we are exiting/entering 1105 * the current state. 1106 */ 1107 mTempStateStackCount = 0; 1108 StateInfo curStateInfo = mStateInfo.get(destState); 1109 do { 1110 mTempStateStack[mTempStateStackCount++] = curStateInfo; 1111 curStateInfo = curStateInfo.parentStateInfo; 1112 } while ((curStateInfo != null) && !curStateInfo.active); 1113 1114 if (mDbg) { 1115 mSm.log("setupTempStateStackWithStatesToEnter: X mTempStateStackCount=" 1116 + mTempStateStackCount + ",curStateInfo: " + curStateInfo); 1117 } 1118 return curStateInfo; 1119 } 1120 1121 /** 1122 * Initialize StateStack to mInitialState. 1123 */ setupInitialStateStack()1124 private final void setupInitialStateStack() { 1125 if (mDbg) { 1126 mSm.log("setupInitialStateStack: E mInitialState=" + mInitialState.getName()); 1127 } 1128 1129 StateInfo curStateInfo = mStateInfo.get(mInitialState); 1130 for (mTempStateStackCount = 0; curStateInfo != null; mTempStateStackCount++) { 1131 mTempStateStack[mTempStateStackCount] = curStateInfo; 1132 curStateInfo = curStateInfo.parentStateInfo; 1133 } 1134 1135 // Empty the StateStack 1136 mStateStackTopIndex = -1; 1137 1138 moveTempStateStackToStateStack(); 1139 } 1140 1141 /** 1142 * @return current message 1143 */ getCurrentMessage()1144 private final Message getCurrentMessage() { 1145 return mMsg; 1146 } 1147 1148 /** 1149 * @return current state 1150 */ getCurrentState()1151 private final IState getCurrentState() { 1152 return mStateStack[mStateStackTopIndex].state; 1153 } 1154 1155 /** 1156 * Add a new state to the state machine. Bottom up addition 1157 * of states is allowed but the same state may only exist 1158 * in one hierarchy. 1159 * 1160 * @param state the state to add 1161 * @param parent the parent of state 1162 * @return stateInfo for this state 1163 */ addState(State state, State parent)1164 private final StateInfo addState(State state, State parent) { 1165 if (mDbg) { 1166 mSm.log("addStateInternal: E state=" + state.getName() + ",parent=" 1167 + ((parent == null) ? "" : parent.getName())); 1168 } 1169 StateInfo parentStateInfo = null; 1170 if (parent != null) { 1171 parentStateInfo = mStateInfo.get(parent); 1172 if (parentStateInfo == null) { 1173 // Recursively add our parent as it's not been added yet. 1174 parentStateInfo = addState(parent, null); 1175 } 1176 } 1177 StateInfo stateInfo = mStateInfo.get(state); 1178 if (stateInfo == null) { 1179 stateInfo = new StateInfo(); 1180 mStateInfo.put(state, stateInfo); 1181 } 1182 1183 // Validate that we aren't adding the same state in two different hierarchies. 1184 if ((stateInfo.parentStateInfo != null) 1185 && (stateInfo.parentStateInfo != parentStateInfo)) { 1186 throw new RuntimeException("state already added"); 1187 } 1188 stateInfo.state = state; 1189 stateInfo.parentStateInfo = parentStateInfo; 1190 stateInfo.active = false; 1191 if (mDbg) mSm.log("addStateInternal: X stateInfo: " + stateInfo); 1192 return stateInfo; 1193 } 1194 1195 /** 1196 * Remove a state from the state machine. Will not remove the state if it is currently 1197 * active or if it has any children in the hierarchy. 1198 * @param state the state to remove 1199 */ removeState(State state)1200 private void removeState(State state) { 1201 StateInfo stateInfo = mStateInfo.get(state); 1202 if (stateInfo == null || stateInfo.active) { 1203 return; 1204 } 1205 boolean isParent = mStateInfo.values().stream() 1206 .filter(si -> si.parentStateInfo == stateInfo) 1207 .findAny() 1208 .isPresent(); 1209 if (isParent) { 1210 return; 1211 } 1212 mStateInfo.remove(state); 1213 } 1214 1215 /** 1216 * Constructor 1217 * 1218 * @param looper for dispatching messages 1219 * @param sm the hierarchical state machine 1220 */ SmHandler(Looper looper, StateMachine sm)1221 private SmHandler(Looper looper, StateMachine sm) { 1222 super(looper); 1223 mSm = sm; 1224 1225 addState(mHaltingState, null); 1226 addState(mQuittingState, null); 1227 } 1228 1229 /** @see StateMachine#setInitialState(State) */ setInitialState(State initialState)1230 private final void setInitialState(State initialState) { 1231 if (mDbg) mSm.log("setInitialState: initialState=" + initialState.getName()); 1232 mInitialState = initialState; 1233 } 1234 1235 /** @see StateMachine#transitionTo(IState) */ transitionTo(IState destState)1236 private final void transitionTo(IState destState) { 1237 if (mTransitionInProgress) { 1238 Log.wtf(mSm.mName, "transitionTo called while transition already in progress to " + 1239 mDestState + ", new target state=" + destState); 1240 } 1241 mDestState = (State) destState; 1242 if (mDbg) mSm.log("transitionTo: destState=" + mDestState.getName()); 1243 } 1244 1245 /** @see StateMachine#deferMessage(Message) */ deferMessage(Message msg)1246 private final void deferMessage(Message msg) { 1247 if (mDbg) mSm.log("deferMessage: msg=" + msg.what); 1248 1249 /* Copy the "msg" to "newMsg" as "msg" will be recycled */ 1250 Message newMsg = obtainMessage(); 1251 newMsg.copyFrom(msg); 1252 1253 mDeferredMessages.add(newMsg); 1254 } 1255 1256 /** @see StateMachine#quit() */ quit()1257 private final void quit() { 1258 if (mDbg) mSm.log("quit:"); 1259 sendMessage(obtainMessage(SM_QUIT_CMD, mSmHandlerObj)); 1260 } 1261 1262 /** @see StateMachine#quitNow() */ quitNow()1263 private final void quitNow() { 1264 if (mDbg) mSm.log("quitNow:"); 1265 sendMessageAtFrontOfQueue(obtainMessage(SM_QUIT_CMD, mSmHandlerObj)); 1266 } 1267 1268 /** Validate that the message was sent by quit or quitNow. */ isQuit(Message msg)1269 private final boolean isQuit(Message msg) { 1270 return (msg.what == SM_QUIT_CMD) && (msg.obj == mSmHandlerObj); 1271 } 1272 1273 /** @see StateMachine#isDbg() */ isDbg()1274 private final boolean isDbg() { 1275 return mDbg; 1276 } 1277 1278 /** @see StateMachine#setDbg(boolean) */ setDbg(boolean dbg)1279 private final void setDbg(boolean dbg) { 1280 mDbg = dbg; 1281 } 1282 1283 } 1284 1285 private SmHandler mSmHandler; 1286 private HandlerThread mSmThread; 1287 1288 /** 1289 * Initialize. 1290 * 1291 * @param looper for this state machine 1292 * @param name of the state machine 1293 */ initStateMachine(String name, Looper looper)1294 private void initStateMachine(String name, Looper looper) { 1295 mName = name; 1296 mSmHandler = new SmHandler(looper, this); 1297 } 1298 1299 /** 1300 * Constructor creates a StateMachine with its own thread. 1301 * 1302 * @param name of the state machine 1303 */ 1304 @UnsupportedAppUsage StateMachine(String name)1305 protected StateMachine(String name) { 1306 mSmThread = new HandlerThread(name); 1307 mSmThread.start(); 1308 Looper looper = mSmThread.getLooper(); 1309 1310 initStateMachine(name, looper); 1311 } 1312 1313 /** 1314 * Constructor creates a StateMachine using the looper. 1315 * 1316 * @param name of the state machine 1317 */ 1318 @UnsupportedAppUsage StateMachine(String name, Looper looper)1319 protected StateMachine(String name, Looper looper) { 1320 initStateMachine(name, looper); 1321 } 1322 1323 /** 1324 * Constructor creates a StateMachine using the handler. 1325 * 1326 * @param name of the state machine 1327 */ 1328 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) StateMachine(String name, Handler handler)1329 protected StateMachine(String name, Handler handler) { 1330 initStateMachine(name, handler.getLooper()); 1331 } 1332 1333 /** 1334 * Notifies subclass that the StateMachine handler is about to process the Message msg 1335 * @param msg The message that is being handled 1336 */ onPreHandleMessage(Message msg)1337 protected void onPreHandleMessage(Message msg) { 1338 } 1339 1340 /** 1341 * Notifies subclass that the StateMachine handler has finished processing the Message msg and 1342 * has possibly transitioned to a new state. 1343 * @param msg The message that is being handled 1344 */ onPostHandleMessage(Message msg)1345 protected void onPostHandleMessage(Message msg) { 1346 } 1347 1348 /** 1349 * Add a new state to the state machine 1350 * @param state the state to add 1351 * @param parent the parent of state 1352 */ addState(State state, State parent)1353 public final void addState(State state, State parent) { 1354 mSmHandler.addState(state, parent); 1355 } 1356 1357 /** 1358 * Add a new state to the state machine, parent will be null 1359 * @param state to add 1360 */ 1361 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) addState(State state)1362 public final void addState(State state) { 1363 mSmHandler.addState(state, null); 1364 } 1365 1366 /** 1367 * Removes a state from the state machine, unless it is currently active or if it has children. 1368 * @param state state to remove 1369 */ removeState(State state)1370 public final void removeState(State state) { 1371 mSmHandler.removeState(state); 1372 } 1373 1374 /** 1375 * Set the initial state. This must be invoked before 1376 * and messages are sent to the state machine. 1377 * 1378 * @param initialState is the state which will receive the first message. 1379 */ 1380 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) setInitialState(State initialState)1381 public final void setInitialState(State initialState) { 1382 mSmHandler.setInitialState(initialState); 1383 } 1384 1385 /** 1386 * @return current message 1387 */ getCurrentMessage()1388 public final Message getCurrentMessage() { 1389 // mSmHandler can be null if the state machine has quit. 1390 SmHandler smh = mSmHandler; 1391 if (smh == null) return null; 1392 return smh.getCurrentMessage(); 1393 } 1394 1395 /** 1396 * @return current state 1397 */ getCurrentState()1398 public final IState getCurrentState() { 1399 // mSmHandler can be null if the state machine has quit. 1400 SmHandler smh = mSmHandler; 1401 if (smh == null) return null; 1402 return smh.getCurrentState(); 1403 } 1404 1405 /** 1406 * transition to destination state. Upon returning 1407 * from processMessage the current state's exit will 1408 * be executed and upon the next message arriving 1409 * destState.enter will be invoked. 1410 * 1411 * this function can also be called inside the enter function of the 1412 * previous transition target, but the behavior is undefined when it is 1413 * called mid-way through a previous transition (for example, calling this 1414 * in the enter() routine of a intermediate node when the current transition 1415 * target is one of the nodes descendants). 1416 * 1417 * @param destState will be the state that receives the next message. 1418 */ 1419 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) transitionTo(IState destState)1420 public final void transitionTo(IState destState) { 1421 mSmHandler.transitionTo(destState); 1422 } 1423 1424 /** 1425 * transition to halt state. Upon returning 1426 * from processMessage we will exit all current 1427 * states, execute the onHalting() method and then 1428 * for all subsequent messages haltedProcessMessage 1429 * will be called. 1430 */ transitionToHaltingState()1431 public final void transitionToHaltingState() { 1432 mSmHandler.transitionTo(mSmHandler.mHaltingState); 1433 } 1434 1435 /** 1436 * Defer this message until next state transition. 1437 * Upon transitioning all deferred messages will be 1438 * placed on the queue and reprocessed in the original 1439 * order. (i.e. The next state the oldest messages will 1440 * be processed first) 1441 * 1442 * @param msg is deferred until the next transition. 1443 */ deferMessage(Message msg)1444 public final void deferMessage(Message msg) { 1445 mSmHandler.deferMessage(msg); 1446 } 1447 1448 /** 1449 * Called when message wasn't handled 1450 * 1451 * @param msg that couldn't be handled. 1452 */ unhandledMessage(Message msg)1453 protected void unhandledMessage(Message msg) { 1454 if (mSmHandler.mDbg) loge(" - unhandledMessage: msg.what=" + msg.what); 1455 } 1456 1457 /** 1458 * Called for any message that is received after 1459 * transitionToHalting is called. 1460 */ haltedProcessMessage(Message msg)1461 protected void haltedProcessMessage(Message msg) { 1462 } 1463 1464 /** 1465 * This will be called once after handling a message that called 1466 * transitionToHalting. All subsequent messages will invoke 1467 * {@link StateMachine#haltedProcessMessage(Message)} 1468 */ onHalting()1469 protected void onHalting() { 1470 } 1471 1472 /** 1473 * This will be called once after a quit message that was NOT handled by 1474 * the derived StateMachine. The StateMachine will stop and any subsequent messages will be 1475 * ignored. In addition, if this StateMachine created the thread, the thread will 1476 * be stopped after this method returns. 1477 */ onQuitting()1478 protected void onQuitting() { 1479 } 1480 1481 /** 1482 * @return the name 1483 */ getName()1484 public final String getName() { 1485 return mName; 1486 } 1487 1488 /** 1489 * Set number of log records to maintain and clears all current records. 1490 * 1491 * @param maxSize number of messages to maintain at anyone time. 1492 */ setLogRecSize(int maxSize)1493 public final void setLogRecSize(int maxSize) { 1494 mSmHandler.mLogRecords.setSize(maxSize); 1495 } 1496 1497 /** 1498 * Set to log only messages that cause a state transition 1499 * 1500 * @param enable {@code true} to enable, {@code false} to disable 1501 */ setLogOnlyTransitions(boolean enable)1502 public final void setLogOnlyTransitions(boolean enable) { 1503 mSmHandler.mLogRecords.setLogOnlyTransitions(enable); 1504 } 1505 1506 /** 1507 * @return the number of log records currently readable 1508 */ getLogRecSize()1509 public final int getLogRecSize() { 1510 // mSmHandler can be null if the state machine has quit. 1511 SmHandler smh = mSmHandler; 1512 if (smh == null) return 0; 1513 return smh.mLogRecords.size(); 1514 } 1515 1516 /** 1517 * @return the number of log records we can store 1518 */ 1519 @VisibleForTesting getLogRecMaxSize()1520 public final int getLogRecMaxSize() { 1521 // mSmHandler can be null if the state machine has quit. 1522 SmHandler smh = mSmHandler; 1523 if (smh == null) return 0; 1524 return smh.mLogRecords.mMaxSize; 1525 } 1526 1527 /** 1528 * @return the total number of records processed 1529 */ getLogRecCount()1530 public final int getLogRecCount() { 1531 // mSmHandler can be null if the state machine has quit. 1532 SmHandler smh = mSmHandler; 1533 if (smh == null) return 0; 1534 return smh.mLogRecords.count(); 1535 } 1536 1537 /** 1538 * @return a log record, or null if index is out of range 1539 */ getLogRec(int index)1540 public final LogRec getLogRec(int index) { 1541 // mSmHandler can be null if the state machine has quit. 1542 SmHandler smh = mSmHandler; 1543 if (smh == null) return null; 1544 return smh.mLogRecords.get(index); 1545 } 1546 1547 /** 1548 * @return a copy of LogRecs as a collection 1549 */ copyLogRecs()1550 public final Collection<LogRec> copyLogRecs() { 1551 Vector<LogRec> vlr = new Vector<LogRec>(); 1552 SmHandler smh = mSmHandler; 1553 if (smh != null) { 1554 for (LogRec lr : smh.mLogRecords.mLogRecVector) { 1555 vlr.add(lr); 1556 } 1557 } 1558 return vlr; 1559 } 1560 1561 /** 1562 * Add the string to LogRecords. 1563 * 1564 * @param string 1565 */ addLogRec(String string)1566 public void addLogRec(String string) { 1567 // mSmHandler can be null if the state machine has quit. 1568 SmHandler smh = mSmHandler; 1569 if (smh == null) return; 1570 smh.mLogRecords.add(this, smh.getCurrentMessage(), string, smh.getCurrentState(), 1571 smh.mStateStack[smh.mStateStackTopIndex].state, smh.mDestState); 1572 } 1573 1574 /** 1575 * @return true if msg should be saved in the log, default is true. 1576 */ recordLogRec(Message msg)1577 protected boolean recordLogRec(Message msg) { 1578 return true; 1579 } 1580 1581 /** 1582 * Return a string to be logged by LogRec, default 1583 * is an empty string. Override if additional information is desired. 1584 * 1585 * @param msg that was processed 1586 * @return information to be logged as a String 1587 */ getLogRecString(Message msg)1588 protected String getLogRecString(Message msg) { 1589 return ""; 1590 } 1591 1592 /** 1593 * @return the string for msg.what 1594 */ getWhatToString(int what)1595 protected String getWhatToString(int what) { 1596 return null; 1597 } 1598 1599 /** 1600 * @return Handler, maybe null if state machine has quit. 1601 */ getHandler()1602 public final Handler getHandler() { 1603 return mSmHandler; 1604 } 1605 1606 /** 1607 * Get a message and set Message.target state machine handler. 1608 * 1609 * Note: The handler can be null if the state machine has quit, 1610 * which means target will be null and may cause a AndroidRuntimeException 1611 * in MessageQueue#enqueMessage if sent directly or if sent using 1612 * StateMachine#sendMessage the message will just be ignored. 1613 * 1614 * @return A Message object from the global pool 1615 */ obtainMessage()1616 public final Message obtainMessage() { 1617 return Message.obtain(mSmHandler); 1618 } 1619 1620 /** 1621 * Get a message and set Message.target state machine handler, what. 1622 * 1623 * Note: The handler can be null if the state machine has quit, 1624 * which means target will be null and may cause a AndroidRuntimeException 1625 * in MessageQueue#enqueMessage if sent directly or if sent using 1626 * StateMachine#sendMessage the message will just be ignored. 1627 * 1628 * @param what is the assigned to Message.what. 1629 * @return A Message object from the global pool 1630 */ obtainMessage(int what)1631 public final Message obtainMessage(int what) { 1632 return Message.obtain(mSmHandler, what); 1633 } 1634 1635 /** 1636 * Get a message and set Message.target state machine handler, 1637 * what and obj. 1638 * 1639 * Note: The handler can be null if the state machine has quit, 1640 * which means target will be null and may cause a AndroidRuntimeException 1641 * in MessageQueue#enqueMessage if sent directly or if sent using 1642 * StateMachine#sendMessage the message will just be ignored. 1643 * 1644 * @param what is the assigned to Message.what. 1645 * @param obj is assigned to Message.obj. 1646 * @return A Message object from the global pool 1647 */ obtainMessage(int what, Object obj)1648 public final Message obtainMessage(int what, Object obj) { 1649 return Message.obtain(mSmHandler, what, obj); 1650 } 1651 1652 /** 1653 * Get a message and set Message.target state machine handler, 1654 * what, arg1 and arg2 1655 * 1656 * Note: The handler can be null if the state machine has quit, 1657 * which means target will be null and may cause a AndroidRuntimeException 1658 * in MessageQueue#enqueMessage if sent directly or if sent using 1659 * StateMachine#sendMessage the message will just be ignored. 1660 * 1661 * @param what is assigned to Message.what 1662 * @param arg1 is assigned to Message.arg1 1663 * @return A Message object from the global pool 1664 */ obtainMessage(int what, int arg1)1665 public final Message obtainMessage(int what, int arg1) { 1666 // use this obtain so we don't match the obtain(h, what, Object) method 1667 return Message.obtain(mSmHandler, what, arg1, 0); 1668 } 1669 1670 /** 1671 * Get a message and set Message.target state machine handler, 1672 * what, arg1 and arg2 1673 * 1674 * Note: The handler can be null if the state machine has quit, 1675 * which means target will be null and may cause a AndroidRuntimeException 1676 * in MessageQueue#enqueMessage if sent directly or if sent using 1677 * StateMachine#sendMessage the message will just be ignored. 1678 * 1679 * @param what is assigned to Message.what 1680 * @param arg1 is assigned to Message.arg1 1681 * @param arg2 is assigned to Message.arg2 1682 * @return A Message object from the global pool 1683 */ 1684 @UnsupportedAppUsage obtainMessage(int what, int arg1, int arg2)1685 public final Message obtainMessage(int what, int arg1, int arg2) { 1686 return Message.obtain(mSmHandler, what, arg1, arg2); 1687 } 1688 1689 /** 1690 * Get a message and set Message.target state machine handler, 1691 * what, arg1, arg2 and obj 1692 * 1693 * Note: The handler can be null if the state machine has quit, 1694 * which means target will be null and may cause a AndroidRuntimeException 1695 * in MessageQueue#enqueMessage if sent directly or if sent using 1696 * StateMachine#sendMessage the message will just be ignored. 1697 * 1698 * @param what is assigned to Message.what 1699 * @param arg1 is assigned to Message.arg1 1700 * @param arg2 is assigned to Message.arg2 1701 * @param obj is assigned to Message.obj 1702 * @return A Message object from the global pool 1703 */ 1704 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) obtainMessage(int what, int arg1, int arg2, Object obj)1705 public final Message obtainMessage(int what, int arg1, int arg2, Object obj) { 1706 return Message.obtain(mSmHandler, what, arg1, arg2, obj); 1707 } 1708 1709 /** 1710 * Enqueue a message to this state machine. 1711 * 1712 * Message is ignored if state machine has quit. 1713 */ 1714 @UnsupportedAppUsage sendMessage(int what)1715 public void sendMessage(int what) { 1716 // mSmHandler can be null if the state machine has quit. 1717 SmHandler smh = mSmHandler; 1718 if (smh == null) return; 1719 1720 smh.sendMessage(obtainMessage(what)); 1721 } 1722 1723 /** 1724 * Enqueue a message to this state machine. 1725 * 1726 * Message is ignored if state machine has quit. 1727 */ 1728 @UnsupportedAppUsage sendMessage(int what, Object obj)1729 public void sendMessage(int what, Object obj) { 1730 // mSmHandler can be null if the state machine has quit. 1731 SmHandler smh = mSmHandler; 1732 if (smh == null) return; 1733 1734 smh.sendMessage(obtainMessage(what, obj)); 1735 } 1736 1737 /** 1738 * Enqueue a message to this state machine. 1739 * 1740 * Message is ignored if state machine has quit. 1741 */ 1742 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) sendMessage(int what, int arg1)1743 public void sendMessage(int what, int arg1) { 1744 // mSmHandler can be null if the state machine has quit. 1745 SmHandler smh = mSmHandler; 1746 if (smh == null) return; 1747 1748 smh.sendMessage(obtainMessage(what, arg1)); 1749 } 1750 1751 /** 1752 * Enqueue a message to this state machine. 1753 * 1754 * Message is ignored if state machine has quit. 1755 */ sendMessage(int what, int arg1, int arg2)1756 public void sendMessage(int what, int arg1, int arg2) { 1757 // mSmHandler can be null if the state machine has quit. 1758 SmHandler smh = mSmHandler; 1759 if (smh == null) return; 1760 1761 smh.sendMessage(obtainMessage(what, arg1, arg2)); 1762 } 1763 1764 /** 1765 * Enqueue a message to this state machine. 1766 * 1767 * Message is ignored if state machine has quit. 1768 */ 1769 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) sendMessage(int what, int arg1, int arg2, Object obj)1770 public void sendMessage(int what, int arg1, int arg2, Object obj) { 1771 // mSmHandler can be null if the state machine has quit. 1772 SmHandler smh = mSmHandler; 1773 if (smh == null) return; 1774 1775 smh.sendMessage(obtainMessage(what, arg1, arg2, obj)); 1776 } 1777 1778 /** 1779 * Enqueue a message to this state machine. 1780 * 1781 * Message is ignored if state machine has quit. 1782 */ 1783 @UnsupportedAppUsage sendMessage(Message msg)1784 public void sendMessage(Message msg) { 1785 // mSmHandler can be null if the state machine has quit. 1786 SmHandler smh = mSmHandler; 1787 if (smh == null) return; 1788 1789 smh.sendMessage(msg); 1790 } 1791 1792 /** 1793 * Enqueue a message to this state machine after a delay. 1794 * 1795 * Message is ignored if state machine has quit. 1796 */ sendMessageDelayed(int what, long delayMillis)1797 public void sendMessageDelayed(int what, long delayMillis) { 1798 // mSmHandler can be null if the state machine has quit. 1799 SmHandler smh = mSmHandler; 1800 if (smh == null) return; 1801 1802 smh.sendMessageDelayed(obtainMessage(what), delayMillis); 1803 } 1804 1805 /** 1806 * Enqueue a message to this state machine after a delay. 1807 * 1808 * Message is ignored if state machine has quit. 1809 */ sendMessageDelayed(int what, Object obj, long delayMillis)1810 public void sendMessageDelayed(int what, Object obj, long delayMillis) { 1811 // mSmHandler can be null if the state machine has quit. 1812 SmHandler smh = mSmHandler; 1813 if (smh == null) return; 1814 1815 smh.sendMessageDelayed(obtainMessage(what, obj), delayMillis); 1816 } 1817 1818 /** 1819 * Enqueue a message to this state machine after a delay. 1820 * 1821 * Message is ignored if state machine has quit. 1822 */ sendMessageDelayed(int what, int arg1, long delayMillis)1823 public void sendMessageDelayed(int what, int arg1, long delayMillis) { 1824 // mSmHandler can be null if the state machine has quit. 1825 SmHandler smh = mSmHandler; 1826 if (smh == null) return; 1827 1828 smh.sendMessageDelayed(obtainMessage(what, arg1), delayMillis); 1829 } 1830 1831 /** 1832 * Enqueue a message to this state machine after a delay. 1833 * 1834 * Message is ignored if state machine has quit. 1835 */ sendMessageDelayed(int what, int arg1, int arg2, long delayMillis)1836 public void sendMessageDelayed(int what, int arg1, int arg2, long delayMillis) { 1837 // mSmHandler can be null if the state machine has quit. 1838 SmHandler smh = mSmHandler; 1839 if (smh == null) return; 1840 1841 smh.sendMessageDelayed(obtainMessage(what, arg1, arg2), delayMillis); 1842 } 1843 1844 /** 1845 * Enqueue a message to this state machine after a delay. 1846 * 1847 * Message is ignored if state machine has quit. 1848 */ sendMessageDelayed(int what, int arg1, int arg2, Object obj, long delayMillis)1849 public void sendMessageDelayed(int what, int arg1, int arg2, Object obj, 1850 long delayMillis) { 1851 // mSmHandler can be null if the state machine has quit. 1852 SmHandler smh = mSmHandler; 1853 if (smh == null) return; 1854 1855 smh.sendMessageDelayed(obtainMessage(what, arg1, arg2, obj), delayMillis); 1856 } 1857 1858 /** 1859 * Enqueue a message to this state machine after a delay. 1860 * 1861 * Message is ignored if state machine has quit. 1862 */ sendMessageDelayed(Message msg, long delayMillis)1863 public void sendMessageDelayed(Message msg, long delayMillis) { 1864 // mSmHandler can be null if the state machine has quit. 1865 SmHandler smh = mSmHandler; 1866 if (smh == null) return; 1867 1868 smh.sendMessageDelayed(msg, delayMillis); 1869 } 1870 1871 /** 1872 * Enqueue a message to the front of the queue for this state machine. 1873 * Protected, may only be called by instances of StateMachine. 1874 * 1875 * Message is ignored if state machine has quit. 1876 */ sendMessageAtFrontOfQueue(int what)1877 protected final void sendMessageAtFrontOfQueue(int what) { 1878 // mSmHandler can be null if the state machine has quit. 1879 SmHandler smh = mSmHandler; 1880 if (smh == null) return; 1881 1882 smh.sendMessageAtFrontOfQueue(obtainMessage(what)); 1883 } 1884 1885 /** 1886 * Enqueue a message to the front of the queue for this state machine. 1887 * Protected, may only be called by instances of StateMachine. 1888 * 1889 * Message is ignored if state machine has quit. 1890 */ sendMessageAtFrontOfQueue(int what, Object obj)1891 protected final void sendMessageAtFrontOfQueue(int what, Object obj) { 1892 // mSmHandler can be null if the state machine has quit. 1893 SmHandler smh = mSmHandler; 1894 if (smh == null) return; 1895 1896 smh.sendMessageAtFrontOfQueue(obtainMessage(what, obj)); 1897 } 1898 1899 /** 1900 * Enqueue a message to the front of the queue for this state machine. 1901 * Protected, may only be called by instances of StateMachine. 1902 * 1903 * Message is ignored if state machine has quit. 1904 */ sendMessageAtFrontOfQueue(int what, int arg1)1905 protected final void sendMessageAtFrontOfQueue(int what, int arg1) { 1906 // mSmHandler can be null if the state machine has quit. 1907 SmHandler smh = mSmHandler; 1908 if (smh == null) return; 1909 1910 smh.sendMessageAtFrontOfQueue(obtainMessage(what, arg1)); 1911 } 1912 1913 1914 /** 1915 * Enqueue a message to the front of the queue for this state machine. 1916 * Protected, may only be called by instances of StateMachine. 1917 * 1918 * Message is ignored if state machine has quit. 1919 */ sendMessageAtFrontOfQueue(int what, int arg1, int arg2)1920 protected final void sendMessageAtFrontOfQueue(int what, int arg1, int arg2) { 1921 // mSmHandler can be null if the state machine has quit. 1922 SmHandler smh = mSmHandler; 1923 if (smh == null) return; 1924 1925 smh.sendMessageAtFrontOfQueue(obtainMessage(what, arg1, arg2)); 1926 } 1927 1928 /** 1929 * Enqueue a message to the front of the queue for this state machine. 1930 * Protected, may only be called by instances of StateMachine. 1931 * 1932 * Message is ignored if state machine has quit. 1933 */ sendMessageAtFrontOfQueue(int what, int arg1, int arg2, Object obj)1934 protected final void sendMessageAtFrontOfQueue(int what, int arg1, int arg2, Object obj) { 1935 // mSmHandler can be null if the state machine has quit. 1936 SmHandler smh = mSmHandler; 1937 if (smh == null) return; 1938 1939 smh.sendMessageAtFrontOfQueue(obtainMessage(what, arg1, arg2, obj)); 1940 } 1941 1942 /** 1943 * Enqueue a message to the front of the queue for this state machine. 1944 * Protected, may only be called by instances of StateMachine. 1945 * 1946 * Message is ignored if state machine has quit. 1947 */ sendMessageAtFrontOfQueue(Message msg)1948 protected final void sendMessageAtFrontOfQueue(Message msg) { 1949 // mSmHandler can be null if the state machine has quit. 1950 SmHandler smh = mSmHandler; 1951 if (smh == null) return; 1952 1953 smh.sendMessageAtFrontOfQueue(msg); 1954 } 1955 1956 /** 1957 * Removes a message from the message queue. 1958 * Protected, may only be called by instances of StateMachine. 1959 */ removeMessages(int what)1960 protected final void removeMessages(int what) { 1961 // mSmHandler can be null if the state machine has quit. 1962 SmHandler smh = mSmHandler; 1963 if (smh == null) return; 1964 1965 smh.removeMessages(what); 1966 } 1967 1968 /** 1969 * Removes a message from the deferred messages queue. 1970 */ removeDeferredMessages(int what)1971 protected final void removeDeferredMessages(int what) { 1972 SmHandler smh = mSmHandler; 1973 if (smh == null) return; 1974 1975 Iterator<Message> iterator = smh.mDeferredMessages.iterator(); 1976 while (iterator.hasNext()) { 1977 Message msg = iterator.next(); 1978 if (msg.what == what) iterator.remove(); 1979 } 1980 } 1981 1982 /** 1983 * Check if there are any pending messages with code 'what' in deferred messages queue. 1984 */ hasDeferredMessages(int what)1985 protected final boolean hasDeferredMessages(int what) { 1986 SmHandler smh = mSmHandler; 1987 if (smh == null) return false; 1988 1989 Iterator<Message> iterator = smh.mDeferredMessages.iterator(); 1990 while (iterator.hasNext()) { 1991 Message msg = iterator.next(); 1992 if (msg.what == what) return true; 1993 } 1994 1995 return false; 1996 } 1997 1998 /** 1999 * Check if there are any pending posts of messages with code 'what' in 2000 * the message queue. This does NOT check messages in deferred message queue. 2001 */ hasMessages(int what)2002 protected final boolean hasMessages(int what) { 2003 SmHandler smh = mSmHandler; 2004 if (smh == null) return false; 2005 2006 return smh.hasMessages(what); 2007 } 2008 2009 /** 2010 * Validate that the message was sent by 2011 * {@link StateMachine#quit} or {@link StateMachine#quitNow}. 2012 * */ isQuit(Message msg)2013 protected final boolean isQuit(Message msg) { 2014 // mSmHandler can be null if the state machine has quit. 2015 SmHandler smh = mSmHandler; 2016 if (smh == null) return msg.what == SM_QUIT_CMD; 2017 2018 return smh.isQuit(msg); 2019 } 2020 2021 /** 2022 * Quit the state machine after all currently queued up messages are processed. 2023 */ quit()2024 public final void quit() { 2025 // mSmHandler can be null if the state machine is already stopped. 2026 SmHandler smh = mSmHandler; 2027 if (smh == null) return; 2028 2029 smh.quit(); 2030 } 2031 2032 /** 2033 * Quit the state machine immediately all currently queued messages will be discarded. 2034 */ quitNow()2035 public final void quitNow() { 2036 // mSmHandler can be null if the state machine is already stopped. 2037 SmHandler smh = mSmHandler; 2038 if (smh == null) return; 2039 2040 smh.quitNow(); 2041 } 2042 2043 /** 2044 * @return if debugging is enabled 2045 */ isDbg()2046 public boolean isDbg() { 2047 // mSmHandler can be null if the state machine has quit. 2048 SmHandler smh = mSmHandler; 2049 if (smh == null) return false; 2050 2051 return smh.isDbg(); 2052 } 2053 2054 /** 2055 * Set debug enable/disabled. 2056 * 2057 * @param dbg is true to enable debugging. 2058 */ setDbg(boolean dbg)2059 public void setDbg(boolean dbg) { 2060 // mSmHandler can be null if the state machine has quit. 2061 SmHandler smh = mSmHandler; 2062 if (smh == null) return; 2063 2064 smh.setDbg(dbg); 2065 } 2066 2067 /** 2068 * Start the state machine. 2069 */ 2070 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) start()2071 public void start() { 2072 // mSmHandler can be null if the state machine has quit. 2073 SmHandler smh = mSmHandler; 2074 if (smh == null) return; 2075 2076 /** Send the complete construction message */ 2077 smh.completeConstruction(); 2078 } 2079 2080 /** 2081 * Dump the current state. 2082 * 2083 * @param fd 2084 * @param pw 2085 * @param args 2086 */ 2087 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) dump(FileDescriptor fd, PrintWriter pw, String[] args)2088 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 2089 pw.println(getName() + ":"); 2090 pw.println(" total records=" + getLogRecCount()); 2091 for (int i = 0; i < getLogRecSize(); i++) { 2092 pw.println(" rec[" + i + "]: " + getLogRec(i)); 2093 pw.flush(); 2094 } 2095 final IState curState = getCurrentState(); 2096 pw.println("curState=" + (curState == null ? "<QUIT>" : curState.getName())); 2097 } 2098 2099 @Override toString()2100 public String toString() { 2101 String name = "(null)"; 2102 String state = "(null)"; 2103 try { 2104 name = mName.toString(); 2105 state = mSmHandler.getCurrentState().getName().toString(); 2106 } catch (NullPointerException | ArrayIndexOutOfBoundsException e) { 2107 // Will use default(s) initialized above. 2108 } 2109 return "name=" + name + " state=" + state; 2110 } 2111 2112 /** 2113 * Log with debug and add to the LogRecords. 2114 * 2115 * @param s is string log 2116 */ logAndAddLogRec(String s)2117 protected void logAndAddLogRec(String s) { 2118 addLogRec(s); 2119 log(s); 2120 } 2121 2122 /** 2123 * Log with debug 2124 * 2125 * @param s is string log 2126 */ log(String s)2127 protected void log(String s) { 2128 Log.d(mName, s); 2129 } 2130 2131 /** 2132 * Log with debug attribute 2133 * 2134 * @param s is string log 2135 */ logd(String s)2136 protected void logd(String s) { 2137 Log.d(mName, s); 2138 } 2139 2140 /** 2141 * Log with verbose attribute 2142 * 2143 * @param s is string log 2144 */ logv(String s)2145 protected void logv(String s) { 2146 Log.v(mName, s); 2147 } 2148 2149 /** 2150 * Log with info attribute 2151 * 2152 * @param s is string log 2153 */ logi(String s)2154 protected void logi(String s) { 2155 Log.i(mName, s); 2156 } 2157 2158 /** 2159 * Log with warning attribute 2160 * 2161 * @param s is string log 2162 */ logw(String s)2163 protected void logw(String s) { 2164 Log.w(mName, s); 2165 } 2166 2167 /** 2168 * Log with error attribute 2169 * 2170 * @param s is string log 2171 */ loge(String s)2172 protected void loge(String s) { 2173 Log.e(mName, s); 2174 } 2175 2176 /** 2177 * Log with error attribute 2178 * 2179 * @param s is string log 2180 * @param e is a Throwable which logs additional information. 2181 */ loge(String s, Throwable e)2182 protected void loge(String s, Throwable e) { 2183 Log.e(mName, s, e); 2184 } 2185 } 2186