HeadsetService.java revision 970d01527efa5606701c5b1f4942a56c47814eab
1/* 2 * Copyright (C) 2012 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package com.android.bluetooth.hfp; 18 19import android.bluetooth.BluetoothDevice; 20import android.bluetooth.BluetoothHeadset; 21import android.bluetooth.BluetoothProfile; 22import android.bluetooth.IBluetoothHeadset; 23import android.content.BroadcastReceiver; 24import android.content.Context; 25import android.content.Intent; 26import android.content.IntentFilter; 27import android.media.AudioManager; 28import android.os.BatteryManager; 29import android.os.HandlerThread; 30import android.os.Looper; 31import android.os.Message; 32import android.provider.Settings; 33import android.util.Log; 34 35import com.android.bluetooth.Utils; 36import com.android.bluetooth.btservice.ProfileService; 37 38import java.util.ArrayList; 39import java.util.List; 40 41/** 42 * Provides Bluetooth Headset and Handsfree profile, as a service in the Bluetooth application. 43 */ 44public class HeadsetService extends ProfileService { 45 private static final String TAG = "HeadsetService"; 46 private static final boolean DBG = false; 47 private static final String MODIFY_PHONE_STATE = android.Manifest.permission.MODIFY_PHONE_STATE; 48 private static final int MAX_HEADSET_CONNECTIONS = 1; 49 50 private HandlerThread mStateMachinesThread; 51 private HeadsetStateMachine mStateMachine; 52 private HeadsetNativeInterface mNativeInterface; 53 private HeadsetSystemInterface mSystemInterface; 54 private boolean mStarted; 55 private boolean mCreated; 56 private static HeadsetService sHeadsetService; 57 58 @Override 59 protected String getName() { 60 return TAG; 61 } 62 63 @Override 64 public synchronized IProfileServiceBinder initBinder() { 65 return new BluetoothHeadsetBinder(this); 66 } 67 68 @Override 69 protected synchronized void create() { 70 mCreated = true; 71 } 72 73 @Override 74 protected synchronized boolean start() { 75 Log.i(TAG, "start()"); 76 // Step 1: Start handler thread for state machines 77 mStateMachinesThread = new HandlerThread("HeadsetService.StateMachines"); 78 mStateMachinesThread.start(); 79 // Step 2: Initialize system interface 80 mSystemInterface = new HeadsetSystemInterface(this); 81 mSystemInterface.init(); 82 // Step 3: Initialize native interface 83 mNativeInterface = HeadsetNativeInterface.getInstance(); 84 mNativeInterface.init(MAX_HEADSET_CONNECTIONS, 85 BluetoothHeadset.isInbandRingingSupported(this)); 86 // Step 4: Initialize state machine 87 mStateMachine = 88 HeadsetStateMachine.make(mStateMachinesThread.getLooper(), this, mNativeInterface, 89 mSystemInterface); 90 // Step 5: Setup broadcast receivers 91 IntentFilter filter = new IntentFilter(); 92 filter.addAction(Intent.ACTION_BATTERY_CHANGED); 93 filter.addAction(AudioManager.VOLUME_CHANGED_ACTION); 94 filter.addAction(BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY); 95 registerReceiver(mHeadsetReceiver, filter); 96 // Step 6: Mark service as started 97 setHeadsetService(this); 98 mStarted = true; 99 return true; 100 } 101 102 @Override 103 protected synchronized boolean stop() { 104 Log.i(TAG, "stop()"); 105 if (!mStarted) { 106 Log.w(TAG, "stop() called before start()"); 107 // Still return true because it is considered "stopped" 108 return true; 109 } 110 // Step 6: Mark service as stopped 111 mStarted = false; 112 // Step 5: Tear down broadcast receivers 113 unregisterReceiver(mHeadsetReceiver); 114 // Step 4: Destroy state machine 115 HeadsetStateMachine.destroy(mStateMachine); 116 mStateMachine = null; 117 // Step 3: Destroy native interface 118 mNativeInterface.cleanup(); 119 // Step 2: Destroy system interface 120 mSystemInterface.stop(); 121 // Step 1: Stop handler thread 122 mStateMachinesThread.quitSafely(); 123 mStateMachinesThread = null; 124 setHeadsetService(null); 125 return true; 126 } 127 128 @Override 129 protected synchronized void cleanup() { 130 Log.i(TAG, "cleanup"); 131 if (!mCreated) { 132 Log.w(TAG, "cleanup() called before create()"); 133 } 134 mCreated = false; 135 } 136 137 /** 138 * Checks if this service object is able to accept binder calls 139 * @return True if the object can accept binder calls, False otherwise 140 */ 141 public synchronized boolean isAlive() { 142 return isAvailable() && mCreated && mStarted; 143 } 144 145 synchronized Looper getStateMachinesThreadLooper() { 146 return mStateMachinesThread.getLooper(); 147 } 148 149 synchronized void onDeviceStateChanged(HeadsetDeviceState deviceState) { 150 mStateMachine.sendMessage(HeadsetStateMachine.DEVICE_STATE_CHANGED, deviceState); 151 } 152 153 /** 154 * Handle messages from native (JNI) to Java. This needs to be synchronized to avoid posting 155 * messages to state machine before start() is done 156 * 157 * @param stackEvent event from native stack 158 */ 159 synchronized void messageFromNative(HeadsetStackEvent stackEvent) { 160 mStateMachine.sendMessage(HeadsetStateMachine.STACK_EVENT, stackEvent); 161 } 162 163 private final BroadcastReceiver mHeadsetReceiver = new BroadcastReceiver() { 164 @Override 165 public void onReceive(Context context, Intent intent) { 166 String action = intent.getAction(); 167 if (action == null) { 168 Log.w(TAG, "mHeadsetReceiver, action is null"); 169 return; 170 } 171 switch (action) { 172 case Intent.ACTION_BATTERY_CHANGED: { 173 int batteryLevel = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, -1); 174 int scale = intent.getIntExtra(BatteryManager.EXTRA_SCALE, -1); 175 if (batteryLevel < 0 || scale <= 0) { 176 Log.e(TAG, "Bad Battery Changed intent: batteryLevel=" + batteryLevel 177 + ", scale=" + scale); 178 return; 179 } 180 batteryLevel = batteryLevel * 5 / scale; 181 mSystemInterface.getHeadsetPhoneState().setCindBatteryCharge(batteryLevel); 182 return; 183 } 184 case AudioManager.VOLUME_CHANGED_ACTION: { 185 int streamType = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, -1); 186 if (streamType == AudioManager.STREAM_BLUETOOTH_SCO) { 187 mStateMachine.sendMessage(HeadsetStateMachine.INTENT_SCO_VOLUME_CHANGED, 188 intent); 189 } 190 return; 191 } 192 case BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY: { 193 int requestType = intent.getIntExtra(BluetoothDevice.EXTRA_ACCESS_REQUEST_TYPE, 194 BluetoothDevice.REQUEST_TYPE_PHONEBOOK_ACCESS); 195 if (requestType == BluetoothDevice.REQUEST_TYPE_PHONEBOOK_ACCESS) { 196 if (DBG) { 197 Log.d(TAG, "Received BluetoothDevice.REQUEST_TYPE_PHONEBOOK_ACCESS"); 198 } 199 mStateMachine.sendMessage( 200 HeadsetStateMachine.INTENT_CONNECTION_ACCESS_REPLY, intent); 201 } 202 return; 203 } 204 default: 205 Log.w(TAG, "Unknown action " + action); 206 } 207 } 208 }; 209 210 /** 211 * Handlers for incoming service calls 212 */ 213 private static class BluetoothHeadsetBinder extends IBluetoothHeadset.Stub 214 implements IProfileServiceBinder { 215 private volatile HeadsetService mService; 216 217 BluetoothHeadsetBinder(HeadsetService svc) { 218 mService = svc; 219 } 220 221 @Override 222 public void cleanup() { 223 mService = null; 224 } 225 226 private HeadsetService getService() { 227 final HeadsetService service = mService; 228 if (!Utils.checkCallerAllowManagedProfiles(service)) { 229 Log.w(TAG, "Headset call not allowed for non-active user"); 230 return null; 231 } 232 if (service == null) { 233 Log.w(TAG, "Service is null"); 234 return null; 235 } 236 if (!service.isAlive()) { 237 Log.w(TAG, "Service is not alive"); 238 return null; 239 } 240 return service; 241 } 242 243 @Override 244 public boolean connect(BluetoothDevice device) { 245 HeadsetService service = getService(); 246 if (service == null) { 247 return false; 248 } 249 return service.connect(device); 250 } 251 252 @Override 253 public boolean disconnect(BluetoothDevice device) { 254 HeadsetService service = getService(); 255 if (service == null) { 256 return false; 257 } 258 if (DBG) { 259 Log.d(TAG, "disconnect in HeadsetService"); 260 } 261 return service.disconnect(device); 262 } 263 264 @Override 265 public List<BluetoothDevice> getConnectedDevices() { 266 HeadsetService service = getService(); 267 if (service == null) { 268 return new ArrayList<BluetoothDevice>(0); 269 } 270 return service.getConnectedDevices(); 271 } 272 273 @Override 274 public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { 275 HeadsetService service = getService(); 276 if (service == null) { 277 return new ArrayList<BluetoothDevice>(0); 278 } 279 return service.getDevicesMatchingConnectionStates(states); 280 } 281 282 @Override 283 public int getConnectionState(BluetoothDevice device) { 284 HeadsetService service = getService(); 285 if (service == null) { 286 return BluetoothProfile.STATE_DISCONNECTED; 287 } 288 return service.getConnectionState(device); 289 } 290 291 @Override 292 public boolean setPriority(BluetoothDevice device, int priority) { 293 HeadsetService service = getService(); 294 if (service == null) { 295 return false; 296 } 297 return service.setPriority(device, priority); 298 } 299 300 @Override 301 public int getPriority(BluetoothDevice device) { 302 HeadsetService service = getService(); 303 if (service == null) { 304 return BluetoothProfile.PRIORITY_UNDEFINED; 305 } 306 return service.getPriority(device); 307 } 308 309 @Override 310 public boolean startVoiceRecognition(BluetoothDevice device) { 311 HeadsetService service = getService(); 312 if (service == null) { 313 return false; 314 } 315 return service.startVoiceRecognition(device); 316 } 317 318 @Override 319 public boolean stopVoiceRecognition(BluetoothDevice device) { 320 HeadsetService service = getService(); 321 if (service == null) { 322 return false; 323 } 324 return service.stopVoiceRecognition(device); 325 } 326 327 @Override 328 public boolean isAudioOn() { 329 HeadsetService service = getService(); 330 if (service == null) { 331 return false; 332 } 333 return service.isAudioOn(); 334 } 335 336 @Override 337 public boolean isAudioConnected(BluetoothDevice device) { 338 HeadsetService service = getService(); 339 if (service == null) { 340 return false; 341 } 342 return service.isAudioConnected(device); 343 } 344 345 @Override 346 public int getAudioState(BluetoothDevice device) { 347 HeadsetService service = getService(); 348 if (service == null) { 349 return BluetoothHeadset.STATE_AUDIO_DISCONNECTED; 350 } 351 return service.getAudioState(device); 352 } 353 354 @Override 355 public boolean connectAudio() { 356 HeadsetService service = getService(); 357 if (service == null) { 358 return false; 359 } 360 return service.connectAudio(); 361 } 362 363 @Override 364 public boolean disconnectAudio() { 365 HeadsetService service = getService(); 366 if (service == null) { 367 return false; 368 } 369 return service.disconnectAudio(); 370 } 371 372 @Override 373 public void setAudioRouteAllowed(boolean allowed) { 374 HeadsetService service = getService(); 375 if (service == null) { 376 return; 377 } 378 service.setAudioRouteAllowed(allowed); 379 } 380 381 @Override 382 public boolean getAudioRouteAllowed() { 383 HeadsetService service = getService(); 384 if (service != null) { 385 return service.getAudioRouteAllowed(); 386 } 387 return false; 388 } 389 390 @Override 391 public void setForceScoAudio(boolean forced) { 392 HeadsetService service = getService(); 393 if (service == null) { 394 return; 395 } 396 service.setForceScoAudio(forced); 397 } 398 399 @Override 400 public boolean startScoUsingVirtualVoiceCall(BluetoothDevice device) { 401 HeadsetService service = getService(); 402 if (service == null) { 403 return false; 404 } 405 return service.startScoUsingVirtualVoiceCall(device); 406 } 407 408 @Override 409 public boolean stopScoUsingVirtualVoiceCall(BluetoothDevice device) { 410 HeadsetService service = getService(); 411 if (service == null) { 412 return false; 413 } 414 return service.stopScoUsingVirtualVoiceCall(device); 415 } 416 417 @Override 418 public void phoneStateChanged(int numActive, int numHeld, int callState, String number, 419 int type) { 420 HeadsetService service = getService(); 421 if (service == null) { 422 return; 423 } 424 service.phoneStateChanged(numActive, numHeld, callState, number, type); 425 } 426 427 @Override 428 public void clccResponse(int index, int direction, int status, int mode, boolean mpty, 429 String number, int type) { 430 HeadsetService service = getService(); 431 if (service == null) { 432 return; 433 } 434 service.clccResponse(index, direction, status, mode, mpty, number, type); 435 } 436 437 @Override 438 public boolean sendVendorSpecificResultCode(BluetoothDevice device, String command, 439 String arg) { 440 HeadsetService service = getService(); 441 if (service == null) { 442 return false; 443 } 444 return service.sendVendorSpecificResultCode(device, command, arg); 445 } 446 } 447 448 // API methods 449 public static synchronized HeadsetService getHeadsetService() { 450 if (sHeadsetService != null && sHeadsetService.isAvailable()) { 451 if (DBG) { 452 Log.d(TAG, "getHeadsetService(): returning " + sHeadsetService); 453 } 454 return sHeadsetService; 455 } 456 if (DBG) { 457 if (sHeadsetService == null) { 458 Log.d(TAG, "getHeadsetService(): service is NULL"); 459 } else if (!(sHeadsetService.isAvailable())) { 460 Log.d(TAG, "getHeadsetService(): service is not available"); 461 } 462 } 463 return null; 464 } 465 466 private static synchronized void setHeadsetService(HeadsetService instance) { 467 if (DBG) { 468 Log.d(TAG, "setHeadsetService(): set to: " + instance); 469 } 470 sHeadsetService = instance; 471 } 472 473 public boolean connect(BluetoothDevice device) { 474 enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH ADMIN permission"); 475 Log.d(TAG, "connect: device=" + device); 476 if (getPriority(device) == BluetoothProfile.PRIORITY_OFF) { 477 Log.w(TAG, "connect: PRIORITY_OFF, device=" + device); 478 return false; 479 } 480 int connectionState = mStateMachine.getConnectionState(device); 481 if (connectionState == BluetoothProfile.STATE_CONNECTED 482 || connectionState == BluetoothProfile.STATE_CONNECTING) { 483 Log.w(TAG, "connect: already connected/connecting, connectionState=" + connectionState 484 + ", device=" + device); 485 return false; 486 } 487 mStateMachine.sendMessage(HeadsetStateMachine.CONNECT, device); 488 return true; 489 } 490 491 boolean disconnect(BluetoothDevice device) { 492 enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH ADMIN permission"); 493 Log.d(TAG, "disconnect: device=" + device); 494 int connectionState = mStateMachine.getConnectionState(device); 495 if (connectionState != BluetoothProfile.STATE_CONNECTED 496 && connectionState != BluetoothProfile.STATE_CONNECTING) { 497 Log.w(TAG, "disconnect: not connected/connecting, connectionState=" + connectionState 498 + ", device=" + device); 499 return false; 500 } 501 mStateMachine.sendMessage(HeadsetStateMachine.DISCONNECT, device); 502 return true; 503 } 504 505 public List<BluetoothDevice> getConnectedDevices() { 506 enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); 507 return mStateMachine.getConnectedDevices(); 508 } 509 510 private List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { 511 enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); 512 return mStateMachine.getDevicesMatchingConnectionStates(states); 513 } 514 515 public int getConnectionState(BluetoothDevice device) { 516 enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); 517 return mStateMachine.getConnectionState(device); 518 } 519 520 public boolean setPriority(BluetoothDevice device, int priority) { 521 enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission"); 522 Settings.Global.putInt(getContentResolver(), 523 Settings.Global.getBluetoothHeadsetPriorityKey(device.getAddress()), priority); 524 if (DBG) { 525 Log.d(TAG, "Saved priority " + device + " = " + priority); 526 } 527 return true; 528 } 529 530 public int getPriority(BluetoothDevice device) { 531 enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission"); 532 return Settings.Global.getInt(getContentResolver(), 533 Settings.Global.getBluetoothHeadsetPriorityKey(device.getAddress()), 534 BluetoothProfile.PRIORITY_UNDEFINED); 535 } 536 537 boolean startVoiceRecognition(BluetoothDevice device) { 538 enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); 539 int connectionState = mStateMachine.getConnectionState(device); 540 if (connectionState != BluetoothProfile.STATE_CONNECTED 541 && connectionState != BluetoothProfile.STATE_CONNECTING) { 542 return false; 543 } 544 mStateMachine.sendMessage(HeadsetStateMachine.VOICE_RECOGNITION_START, device); 545 return true; 546 } 547 548 boolean stopVoiceRecognition(BluetoothDevice device) { 549 enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); 550 // It seem that we really need to check the AudioOn state. 551 // But since we allow startVoiceRecognition in STATE_CONNECTED and 552 // STATE_CONNECTING state, we do these 2 in this method 553 int connectionState = mStateMachine.getConnectionState(device); 554 if (connectionState != BluetoothProfile.STATE_CONNECTED 555 && connectionState != BluetoothProfile.STATE_CONNECTING) { 556 return false; 557 } 558 mStateMachine.sendMessage(HeadsetStateMachine.VOICE_RECOGNITION_STOP, device); 559 return true; 560 } 561 562 boolean isAudioOn() { 563 enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); 564 return mStateMachine.getAudioState() != BluetoothHeadset.STATE_AUDIO_DISCONNECTED; 565 } 566 567 boolean isAudioConnected(BluetoothDevice device) { 568 enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); 569 return mStateMachine.getAudioState() == BluetoothHeadset.STATE_AUDIO_CONNECTED; 570 } 571 572 int getAudioState(BluetoothDevice device) { 573 return mStateMachine.getAudioState(); 574 } 575 576 public void setAudioRouteAllowed(boolean allowed) { 577 mStateMachine.setAudioRouteAllowed(allowed); 578 } 579 580 public boolean getAudioRouteAllowed() { 581 return mStateMachine.getAudioRouteAllowed(); 582 } 583 584 public void setForceScoAudio(boolean forced) { 585 enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission"); 586 mStateMachine.setForceScoAudio(forced); 587 } 588 589 boolean connectAudio() { 590 // TODO(BT) BLUETOOTH or BLUETOOTH_ADMIN permission 591 enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); 592 if (mStateMachine.getConnectionState(mStateMachine.getCurrentDevice()) 593 != BluetoothProfile.STATE_CONNECTED) { 594 Log.w(TAG, "connectAudio: profile not connected"); 595 return false; 596 } 597 if (isAudioOn()) { 598 Log.w(TAG, "connectAudio: audio is not idle, current state " 599 + mStateMachine.getAudioState()); 600 return false; 601 } 602 mStateMachine.sendMessage(HeadsetStateMachine.CONNECT_AUDIO, 603 mStateMachine.getCurrentDevice()); 604 return true; 605 } 606 607 boolean disconnectAudio() { 608 // TODO(BT) BLUETOOTH or BLUETOOTH_ADMIN permission 609 enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); 610 if (mStateMachine.getAudioState() != BluetoothHeadset.STATE_AUDIO_CONNECTED) { 611 Log.w(TAG, "disconnectAudio, audio is not connected"); 612 return false; 613 } 614 mStateMachine.sendMessage(HeadsetStateMachine.DISCONNECT_AUDIO, 615 mStateMachine.getCurrentDevice()); 616 return true; 617 } 618 619 boolean startScoUsingVirtualVoiceCall(BluetoothDevice device) { 620 /* Do not ignore request if HSM state is still Disconnected or 621 Pending, it will be processed when transitioned to Connected */ 622 mStateMachine.sendMessage(HeadsetStateMachine.VIRTUAL_CALL_START, device); 623 return true; 624 } 625 626 boolean stopScoUsingVirtualVoiceCall(BluetoothDevice device) { 627 int connectionState = mStateMachine.getConnectionState(device); 628 if (connectionState != BluetoothProfile.STATE_CONNECTED 629 && connectionState != BluetoothProfile.STATE_CONNECTING) { 630 return false; 631 } 632 mStateMachine.sendMessage(HeadsetStateMachine.VIRTUAL_CALL_STOP, device); 633 return true; 634 } 635 636 private void phoneStateChanged(int numActive, int numHeld, int callState, String number, 637 int type) { 638 enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, null); 639 Message msg = mStateMachine.obtainMessage(HeadsetStateMachine.CALL_STATE_CHANGED); 640 msg.obj = new HeadsetCallState(numActive, numHeld, callState, number, type); 641 msg.arg1 = 0; // false 642 mStateMachine.sendMessage(msg); 643 } 644 645 private void clccResponse(int index, int direction, int status, int mode, boolean mpty, 646 String number, int type) { 647 enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, null); 648 mStateMachine.sendMessage(HeadsetStateMachine.SEND_CCLC_RESPONSE, 649 new HeadsetClccResponse(index, direction, status, mode, mpty, number, type)); 650 } 651 652 private boolean sendVendorSpecificResultCode(BluetoothDevice device, String command, 653 String arg) { 654 enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); 655 int connectionState = mStateMachine.getConnectionState(device); 656 if (connectionState != BluetoothProfile.STATE_CONNECTED) { 657 return false; 658 } 659 // Currently we support only "+ANDROID". 660 if (!command.equals(BluetoothHeadset.VENDOR_RESULT_CODE_COMMAND_ANDROID)) { 661 Log.w(TAG, "Disallowed unsolicited result code command: " + command); 662 return false; 663 } 664 mStateMachine.sendMessage(HeadsetStateMachine.SEND_VENDOR_SPECIFIC_RESULT_CODE, 665 new HeadsetVendorSpecificResultCode(device, command, arg)); 666 return true; 667 } 668 669 @Override 670 public void dump(StringBuilder sb) { 671 super.dump(sb); 672 if (mStateMachine != null) { 673 mStateMachine.dump(sb); 674 } 675 } 676} 677