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 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 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 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 final 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 final HaltingState mHaltingState = new HaltingState(); 716 717 /** State used when state machine is quitting */ 718 private final 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 static class StateInfo { 728 /** The state */ 729 final State state; 730 731 /** The parent of this state, null if there is no parent */ 732 final StateInfo parentStateInfo; 733 734 /** True when the state has been entered and on the stack */ 735 // Note that this can be initialized on a different thread than it's used as long 736 // as it's only used on one thread. The reason is that it's initialized to false, 737 // which is also the default value for a boolean, so if the member is seen uninitialized 738 // then it's seen with the default value which is also false. 739 boolean active = false; 740 StateInfo(final State state, final StateInfo parent)741 StateInfo(final State state, final StateInfo parent) { 742 this.state = state; 743 this.parentStateInfo = parent; 744 } 745 746 /** 747 * Convert StateInfo to string 748 */ 749 @Override toString()750 public String toString() { 751 return "state=" + state.getName() + ",active=" + active + ",parent=" 752 + ((parentStateInfo == null) ? "null" : parentStateInfo.state.getName()); 753 } 754 } 755 756 /** The map of all of the states in the state machine */ 757 private final HashMap<State, StateInfo> mStateInfo = new HashMap<>(); 758 759 /** The initial state that will process the first message */ 760 private State mInitialState; 761 762 /** The destination state when transitionTo has been invoked */ 763 private State mDestState; 764 765 /** 766 * Indicates if a transition is in progress 767 * 768 * This will be true for all calls of State.exit and all calls of State.enter except for the 769 * last enter call for the current destination state. 770 */ 771 private boolean mTransitionInProgress = false; 772 773 /** The list of deferred messages */ 774 private final ArrayList<Message> mDeferredMessages = new ArrayList<>(); 775 776 /** 777 * State entered when transitionToHaltingState is called. 778 */ 779 private class HaltingState extends State { 780 @Override processMessage(Message msg)781 public boolean processMessage(Message msg) { 782 mSm.haltedProcessMessage(msg); 783 return true; 784 } 785 } 786 787 /** 788 * State entered when a valid quit message is handled. 789 */ 790 private static class QuittingState extends State { 791 @Override processMessage(Message msg)792 public boolean processMessage(Message msg) { 793 return NOT_HANDLED; 794 } 795 } 796 797 /** 798 * Handle messages sent to the state machine by calling 799 * the current state's processMessage. It also handles 800 * the enter/exit calls and placing any deferred messages 801 * back onto the queue when transitioning to a new state. 802 */ 803 @Override handleMessage(Message msg)804 public final void handleMessage(Message msg) { 805 if (!mHasQuit) { 806 if (mSm != null && msg.what != SM_INIT_CMD && msg.what != SM_QUIT_CMD) { 807 mSm.onPreHandleMessage(msg); 808 } 809 810 if (mDbg) mSm.log("handleMessage: E msg.what=" + msg.what); 811 812 // Save the current message 813 /* Copy the "msg" to "mMsg" as "msg" will be recycled */ 814 mMsg = obtainMessage(); 815 mMsg.copyFrom(msg); 816 // State that processed the message 817 State msgProcessedState = null; 818 if (mIsConstructionCompleted || (msg.what == SM_QUIT_CMD)) { 819 // Normal path 820 msgProcessedState = processMsg(msg); 821 } else if (msg.what == SM_INIT_CMD && msg.obj == mSmHandlerObj) { 822 // Initial one time path. 823 mIsConstructionCompleted = true; 824 invokeEnterMethods(0); 825 } else { 826 throw new RuntimeException("StateMachine.handleMessage: " 827 + "The start method not called, received msg: " + msg); 828 } 829 performTransitions(msgProcessedState, msg); 830 831 // We need to check if mSm == null here as we could be quitting. 832 if (mDbg && mSm != null) mSm.log("handleMessage: X"); 833 834 if (mSm != null && msg.what != SM_INIT_CMD && msg.what != SM_QUIT_CMD) { 835 mSm.onPostHandleMessage(msg); 836 } 837 } 838 } 839 840 /** 841 * Do any transitions 842 * @param msgProcessedState is the state that processed the message 843 */ performTransitions(State msgProcessedState, Message msg)844 private void performTransitions(State msgProcessedState, Message msg) { 845 /* 846 * If transitionTo has been called, exit and then enter 847 * the appropriate states. We loop on this to allow 848 * enter and exit methods to use transitionTo. 849 */ 850 final State orgState = mStateStack[mStateStackTopIndex].state; 851 852 /* 853 * Record whether message needs to be logged before we transition and 854 * and we won't log special messages SM_INIT_CMD or SM_QUIT_CMD which 855 * always set msg.obj to the handler. 856 */ 857 boolean recordLogMsg = mSm.recordLogRec(msg) && (msg.obj != mSmHandlerObj); 858 859 if (mLogRecords.logOnlyTransitions()) { 860 // Record only if there is a transition 861 if (mDestState != null) { 862 mLogRecords.add(mSm, msg, mSm.getLogRecString(msg), msgProcessedState, 863 orgState, mDestState); 864 } 865 } else if (recordLogMsg) { 866 // Record message 867 mLogRecords.add(mSm, msg, mSm.getLogRecString(msg), msgProcessedState, orgState, 868 mDestState); 869 } 870 871 State destState = mDestState; 872 if (destState != null) { 873 /* 874 * Process the transitions including transitions in the enter/exit methods 875 */ 876 while (true) { 877 if (mDbg) mSm.log("handleMessage: new destination call exit/enter"); 878 879 /* 880 * Determine the states to exit and enter and return the 881 * common ancestor state of the enter/exit states. Then 882 * invoke the exit methods then the enter methods. 883 */ 884 StateInfo commonStateInfo = setupTempStateStackWithStatesToEnter(destState); 885 // flag is cleared in invokeEnterMethods before entering the target state 886 mTransitionInProgress = true; 887 invokeExitMethods(commonStateInfo); 888 int stateStackEnteringIndex = moveTempStateStackToStateStack(); 889 invokeEnterMethods(stateStackEnteringIndex); 890 891 /* 892 * Since we have transitioned to a new state we need to have 893 * any deferred messages moved to the front of the message queue 894 * so they will be processed before any other messages in the 895 * message queue. 896 */ 897 moveDeferredMessageAtFrontOfQueue(); 898 899 if (destState != mDestState) { 900 // A new mDestState so continue looping 901 destState = mDestState; 902 } else { 903 // No change in mDestState so we're done 904 break; 905 } 906 } 907 mDestState = null; 908 } 909 910 /* 911 * After processing all transitions check and 912 * see if the last transition was to quit or halt. 913 */ 914 if (destState != null) { 915 if (destState == mQuittingState) { 916 /* 917 * Call onQuitting to let subclasses cleanup. 918 */ 919 mSm.onQuitting(); 920 cleanupAfterQuitting(); 921 } else if (destState == mHaltingState) { 922 /* 923 * Call onHalting() if we've transitioned to the halting 924 * state. All subsequent messages will be processed in 925 * in the halting state which invokes haltedProcessMessage(msg); 926 */ 927 mSm.onHalting(); 928 } 929 } 930 } 931 932 /** 933 * Cleanup all the static variables and the looper after the SM has been quit. 934 */ cleanupAfterQuitting()935 private final void cleanupAfterQuitting() { 936 if (mSm.mSmThread != null) { 937 // If we made the thread then quit looper which stops the thread. 938 getLooper().quit(); 939 mSm.mSmThread = null; 940 } 941 942 mSm.mSmHandler = null; 943 mSm = null; 944 mMsg = null; 945 mLogRecords.cleanup(); 946 mStateStack = null; 947 mTempStateStack = null; 948 mStateInfo.clear(); 949 mInitialState = null; 950 mDestState = null; 951 mDeferredMessages.clear(); 952 mHasQuit = true; 953 } 954 955 /** 956 * Complete the construction of the state machine. 957 */ completeConstruction()958 private final void completeConstruction() { 959 if (mDbg) mSm.log("completeConstruction: E"); 960 961 /* 962 * Determine the maximum depth of the state hierarchy 963 * so we can allocate the state stacks. 964 */ 965 int maxDepth = 0; 966 for (StateInfo si : mStateInfo.values()) { 967 int depth = 0; 968 for (StateInfo i = si; i != null; depth++) { 969 i = i.parentStateInfo; 970 } 971 if (maxDepth < depth) { 972 maxDepth = depth; 973 } 974 } 975 if (mDbg) mSm.log("completeConstruction: maxDepth=" + maxDepth); 976 977 mStateStack = new StateInfo[maxDepth]; 978 mTempStateStack = new StateInfo[maxDepth]; 979 setupInitialStateStack(); 980 981 // Sending SM_INIT_CMD message to invoke enter methods asynchronously 982 sendMessageAtFrontOfQueue(obtainMessage(SM_INIT_CMD, mSmHandlerObj)); 983 984 if (mDbg) mSm.log("completeConstruction: X"); 985 } 986 987 /** 988 * Process the message. If the current state doesn't handle 989 * it, call the states parent and so on. If it is never handled then 990 * call the state machines unhandledMessage method. 991 * @return the state that processed the message 992 */ processMsg(Message msg)993 private final State processMsg(Message msg) { 994 StateInfo curStateInfo = mStateStack[mStateStackTopIndex]; 995 if (mDbg) { 996 mSm.log("processMsg: " + curStateInfo.state.getName()); 997 } 998 999 if (isQuit(msg)) { 1000 transitionTo(mQuittingState); 1001 } else { 1002 while (!curStateInfo.state.processMessage(msg)) { 1003 // Not processed 1004 curStateInfo = curStateInfo.parentStateInfo; 1005 if (curStateInfo == null) { 1006 // No parents left so it's not handled 1007 mSm.unhandledMessage(msg); 1008 break; 1009 } 1010 if (mDbg) { 1011 mSm.log("processMsg: " + curStateInfo.state.getName()); 1012 } 1013 } 1014 } 1015 return (curStateInfo != null) ? curStateInfo.state : null; 1016 } 1017 1018 /** 1019 * Call the exit method for each state from the top of stack 1020 * up to the common ancestor state. 1021 */ invokeExitMethods(StateInfo commonStateInfo)1022 private final void invokeExitMethods(StateInfo commonStateInfo) { 1023 while ((mStateStackTopIndex >= 0) 1024 && (mStateStack[mStateStackTopIndex] != commonStateInfo)) { 1025 State curState = mStateStack[mStateStackTopIndex].state; 1026 if (mDbg) mSm.log("invokeExitMethods: " + curState.getName()); 1027 curState.exit(); 1028 mStateStack[mStateStackTopIndex].active = false; 1029 mStateStackTopIndex -= 1; 1030 } 1031 } 1032 1033 /** 1034 * Invoke the enter method starting at the entering index to top of state stack 1035 */ invokeEnterMethods(int stateStackEnteringIndex)1036 private final void invokeEnterMethods(int stateStackEnteringIndex) { 1037 for (int i = stateStackEnteringIndex; i <= mStateStackTopIndex; i++) { 1038 if (stateStackEnteringIndex == mStateStackTopIndex) { 1039 // Last enter state for transition 1040 mTransitionInProgress = false; 1041 } 1042 if (mDbg) mSm.log("invokeEnterMethods: " + mStateStack[i].state.getName()); 1043 mStateStack[i].state.enter(); 1044 mStateStack[i].active = true; 1045 } 1046 mTransitionInProgress = false; // ensure flag set to false if no methods called 1047 } 1048 1049 /** 1050 * Move the deferred message to the front of the message queue. 1051 */ moveDeferredMessageAtFrontOfQueue()1052 private final void moveDeferredMessageAtFrontOfQueue() { 1053 /* 1054 * The oldest messages on the deferred list must be at 1055 * the front of the queue so start at the back, which 1056 * as the most resent message and end with the oldest 1057 * messages at the front of the queue. 1058 */ 1059 for (int i = mDeferredMessages.size() - 1; i >= 0; i--) { 1060 Message curMsg = mDeferredMessages.get(i); 1061 if (mDbg) mSm.log("moveDeferredMessageAtFrontOfQueue; what=" + curMsg.what); 1062 sendMessageAtFrontOfQueue(curMsg); 1063 } 1064 mDeferredMessages.clear(); 1065 } 1066 1067 /** 1068 * Move the contents of the temporary stack to the state stack 1069 * reversing the order of the items on the temporary stack as 1070 * they are moved. 1071 * 1072 * @return index into mStateStack where entering needs to start 1073 */ moveTempStateStackToStateStack()1074 private final int moveTempStateStackToStateStack() { 1075 int startingIndex = mStateStackTopIndex + 1; 1076 int i = mTempStateStackCount - 1; 1077 int j = startingIndex; 1078 while (i >= 0) { 1079 if (mDbg) mSm.log("moveTempStackToStateStack: i=" + i + ",j=" + j); 1080 mStateStack[j] = mTempStateStack[i]; 1081 j += 1; 1082 i -= 1; 1083 } 1084 1085 mStateStackTopIndex = j - 1; 1086 if (mDbg) { 1087 mSm.log("moveTempStackToStateStack: X mStateStackTop=" + mStateStackTopIndex 1088 + ",startingIndex=" + startingIndex + ",Top=" 1089 + mStateStack[mStateStackTopIndex].state.getName()); 1090 } 1091 return startingIndex; 1092 } 1093 1094 /** 1095 * Set up the mTempStateStack with the states we are going to enter. 1096 * 1097 * This is found by searching up the destState's ancestors for a 1098 * state that is already active i.e. StateInfo.active == true. 1099 * The destStae and all of its inactive parents will be on the 1100 * TempStateStack as the list of states to enter. 1101 * 1102 * @return StateInfo of the common ancestor for the destState and 1103 * current state or null if there is no common parent. 1104 */ setupTempStateStackWithStatesToEnter(State destState)1105 private final StateInfo setupTempStateStackWithStatesToEnter(State destState) { 1106 /* 1107 * Search up the parent list of the destination state for an active 1108 * state. Use a do while() loop as the destState must always be entered 1109 * even if it is active. This can happen if we are exiting/entering 1110 * the current state. 1111 */ 1112 mTempStateStackCount = 0; 1113 StateInfo curStateInfo = mStateInfo.get(destState); 1114 do { 1115 mTempStateStack[mTempStateStackCount++] = curStateInfo; 1116 curStateInfo = curStateInfo.parentStateInfo; 1117 } while ((curStateInfo != null) && !curStateInfo.active); 1118 1119 if (mDbg) { 1120 mSm.log("setupTempStateStackWithStatesToEnter: X mTempStateStackCount=" 1121 + mTempStateStackCount + ",curStateInfo: " + curStateInfo); 1122 } 1123 return curStateInfo; 1124 } 1125 1126 /** 1127 * Initialize StateStack to mInitialState. 1128 */ setupInitialStateStack()1129 private final void setupInitialStateStack() { 1130 if (mDbg) { 1131 mSm.log("setupInitialStateStack: E mInitialState=" + mInitialState.getName()); 1132 } 1133 1134 StateInfo curStateInfo = mStateInfo.get(mInitialState); 1135 for (mTempStateStackCount = 0; curStateInfo != null; mTempStateStackCount++) { 1136 mTempStateStack[mTempStateStackCount] = curStateInfo; 1137 curStateInfo = curStateInfo.parentStateInfo; 1138 } 1139 1140 // Empty the StateStack 1141 mStateStackTopIndex = -1; 1142 1143 moveTempStateStackToStateStack(); 1144 } 1145 1146 /** 1147 * @return current message 1148 */ getCurrentMessage()1149 private final Message getCurrentMessage() { 1150 return mMsg; 1151 } 1152 1153 /** 1154 * @return current state 1155 */ getCurrentState()1156 private final IState getCurrentState() { 1157 return mStateStack[mStateStackTopIndex].state; 1158 } 1159 1160 /** 1161 * Add a new state to the state machine. Bottom up addition 1162 * of states is allowed but the same state may only exist 1163 * in one hierarchy. 1164 * 1165 * @param state the state to add 1166 * @param parent the parent of state 1167 * @return stateInfo for this state 1168 */ addState(State state, State parent)1169 private final StateInfo addState(State state, State parent) { 1170 if (mDbg) { 1171 mSm.log("addStateInternal: E state=" + state.getName() + ",parent=" 1172 + ((parent == null) ? "" : parent.getName())); 1173 } 1174 StateInfo parentStateInfo = null; 1175 if (parent != null) { 1176 parentStateInfo = mStateInfo.get(parent); 1177 if (parentStateInfo == null) { 1178 // Recursively add our parent as it's not been added yet. 1179 parentStateInfo = addState(parent, null); 1180 } 1181 } 1182 StateInfo stateInfo = mStateInfo.get(state); 1183 if (stateInfo == null) { 1184 stateInfo = new StateInfo(state, parentStateInfo); 1185 mStateInfo.put(state, stateInfo); 1186 } 1187 1188 // Validate that we aren't adding the same state in two different hierarchies. 1189 if ((stateInfo.parentStateInfo != null) 1190 && (stateInfo.parentStateInfo != parentStateInfo)) { 1191 throw new RuntimeException("state already added"); 1192 } 1193 if (mDbg) mSm.log("addStateInternal: X stateInfo: " + stateInfo); 1194 return stateInfo; 1195 } 1196 1197 /** 1198 * Remove a state from the state machine. Will not remove the state if it is currently 1199 * active or if it has any children in the hierarchy. 1200 * @param state the state to remove 1201 */ removeState(State state)1202 private void removeState(State state) { 1203 StateInfo stateInfo = mStateInfo.get(state); 1204 if (stateInfo == null || stateInfo.active) { 1205 return; 1206 } 1207 boolean isParent = mStateInfo.values().stream() 1208 .anyMatch(si -> si.parentStateInfo == stateInfo); 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<>(); 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 the info message to add 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 the fd to dump to 2084 * @param pw the writer 2085 * @param args arguments passed to the dump command 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 state = "null"; 2102 try { 2103 state = mSmHandler.getCurrentState().getName().toString(); 2104 } catch (NullPointerException | ArrayIndexOutOfBoundsException e) { 2105 // Will use default(s) initialized above. 2106 } 2107 return "name=" + mName + " state=" + state; 2108 } 2109 2110 /** 2111 * Log with debug and add to the LogRecords. 2112 * 2113 * @param s is string log 2114 */ logAndAddLogRec(String s)2115 protected void logAndAddLogRec(String s) { 2116 addLogRec(s); 2117 log(s); 2118 } 2119 2120 /** 2121 * Log with debug 2122 * 2123 * @param s is string log 2124 */ log(String s)2125 protected void log(String s) { 2126 Log.d(mName, s); 2127 } 2128 2129 /** 2130 * Log with debug attribute 2131 * 2132 * @param s is string log 2133 */ logd(String s)2134 protected void logd(String s) { 2135 Log.d(mName, s); 2136 } 2137 2138 /** 2139 * Log with verbose attribute 2140 * 2141 * @param s is string log 2142 */ logv(String s)2143 protected void logv(String s) { 2144 Log.v(mName, s); 2145 } 2146 2147 /** 2148 * Log with info attribute 2149 * 2150 * @param s is string log 2151 */ logi(String s)2152 protected void logi(String s) { 2153 Log.i(mName, s); 2154 } 2155 2156 /** 2157 * Log with warning attribute 2158 * 2159 * @param s is string log 2160 */ logw(String s)2161 protected void logw(String s) { 2162 Log.w(mName, s); 2163 } 2164 2165 /** 2166 * Log with error attribute 2167 * 2168 * @param s is string log 2169 */ loge(String s)2170 protected void loge(String s) { 2171 Log.e(mName, s); 2172 } 2173 2174 /** 2175 * Log with error attribute 2176 * 2177 * @param s is string log 2178 * @param e is a Throwable which logs additional information. 2179 */ loge(String s, Throwable e)2180 protected void loge(String s, Throwable e) { 2181 Log.e(mName, s, e); 2182 } 2183 } 2184