blob: 4cc04e667f403b5ca6568ccb216a2c36907159b2 [file] [log] [blame]
/*
* Copyright (C) 2012 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Bluetooth A2DP StateMachine. There is one instance per remote device.
* - "Disconnected" and "Connected" are steady states.
* - "Connecting" and "Disconnecting" are transient states until the
* connection / disconnection is completed.
*
*
* (Disconnected)
* | ^
* CONNECT | | DISCONNECTED
* V |
* (Connecting)<--->(Disconnecting)
* | ^
* CONNECTED | | DISCONNECT
* V |
* (Connected)
* NOTES:
* - If state machine is in "Connecting" state and the remote device sends
* DISCONNECT request, the state machine transitions to "Disconnecting" state.
* - Similarly, if the state machine is in "Disconnecting" state and the remote device
* sends CONNECT request, the state machine transitions to "Connecting" state.
*
* DISCONNECT
* (Connecting) ---------------> (Disconnecting)
* <---------------
* CONNECT
*
*/
package com.android.bluetooth.a2dp;
import android.bluetooth.BluetoothA2dp;
import android.bluetooth.BluetoothCodecConfig;
import android.bluetooth.BluetoothCodecStatus;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothProfile;
import android.content.Intent;
import android.os.Looper;
import android.os.Message;
import android.util.Log;
import com.android.bluetooth.btservice.ProfileService;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.State;
import com.android.internal.util.StateMachine;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.Scanner;
final class A2dpStateMachine extends StateMachine {
private static final boolean DBG = true;
private static final String TAG = "A2dpStateMachine";
static final int CONNECT = 1;
static final int DISCONNECT = 2;
@VisibleForTesting
static final int STACK_EVENT = 101;
private static final int CONNECT_TIMEOUT = 201;
// NOTE: the value is not "final" - it is modified in the unit tests
@VisibleForTesting
static int sConnectTimeoutMs = 30000; // 30s
private Disconnected mDisconnected;
private Connecting mConnecting;
private Disconnecting mDisconnecting;
private Connected mConnected;
private int mConnectionState = BluetoothProfile.STATE_DISCONNECTED;
private int mLastConnectionState = -1;
private A2dpService mA2dpService;
private A2dpNativeInterface mA2dpNativeInterface;
private boolean mA2dpOffloadEnabled = false;
private final BluetoothDevice mDevice;
private boolean mIsPlaying = false;
private BluetoothCodecStatus mCodecStatus;
A2dpStateMachine(BluetoothDevice device, A2dpService a2dpService,
A2dpNativeInterface a2dpNativeInterface, Looper looper) {
super(TAG, looper);
setDbg(DBG);
mDevice = device;
mA2dpService = a2dpService;
mA2dpNativeInterface = a2dpNativeInterface;
mDisconnected = new Disconnected();
mConnecting = new Connecting();
mDisconnecting = new Disconnecting();
mConnected = new Connected();
addState(mDisconnected);
addState(mConnecting);
addState(mDisconnecting);
addState(mConnected);
mA2dpOffloadEnabled = mA2dpService.mA2dpOffloadEnabled;
setInitialState(mDisconnected);
}
static A2dpStateMachine make(BluetoothDevice device, A2dpService a2dpService,
A2dpNativeInterface a2dpNativeInterface, Looper looper) {
Log.i(TAG, "make for device " + device);
A2dpStateMachine a2dpSm = new A2dpStateMachine(device, a2dpService, a2dpNativeInterface,
looper);
a2dpSm.start();
return a2dpSm;
}
public void doQuit() {
log("doQuit for device " + mDevice);
if (mIsPlaying) {
// Stop if auido is still playing
log("doQuit: stopped playing " + mDevice);
mIsPlaying = false;
broadcastAudioState(BluetoothA2dp.STATE_NOT_PLAYING,
BluetoothA2dp.STATE_PLAYING);
}
quitNow();
}
public void cleanup() {
log("cleanup for device " + mDevice);
}
@VisibleForTesting
class Disconnected extends State {
@Override
public void enter() {
Message currentMessage = getCurrentMessage();
Log.i(TAG, "Enter Disconnected(" + mDevice + "): " + (currentMessage == null ? "null"
: messageWhatToString(currentMessage.what)));
mConnectionState = BluetoothProfile.STATE_DISCONNECTED;
removeDeferredMessages(DISCONNECT);
if (mLastConnectionState != -1) {
// Don't broadcast during startup
broadcastConnectionState(mConnectionState, mLastConnectionState);
if (mIsPlaying) {
Log.i(TAG, "Disconnected: stopped playing: " + mDevice);
mIsPlaying = false;
broadcastAudioState(BluetoothA2dp.STATE_NOT_PLAYING,
BluetoothA2dp.STATE_PLAYING);
}
}
}
@Override
public void exit() {
Message currentMessage = getCurrentMessage();
log("Exit Disconnected(" + mDevice + "): " + (currentMessage == null ? "null"
: messageWhatToString(currentMessage.what)));
mLastConnectionState = BluetoothProfile.STATE_DISCONNECTED;
}
@Override
public boolean processMessage(Message message) {
log("Disconnected process message(" + mDevice + "): "
+ messageWhatToString(message.what));
switch (message.what) {
case CONNECT:
Log.i(TAG, "Connecting to " + mDevice);
if (!mA2dpNativeInterface.connectA2dp(mDevice)) {
Log.e(TAG, "Disconnected: error connecting to " + mDevice);
break;
}
if (mA2dpService.okToConnect(mDevice, true)) {
transitionTo(mConnecting);
} else {
// Reject the request and stay in Disconnected state
Log.w(TAG, "Outgoing A2DP Connecting request rejected: " + mDevice);
}
break;
case DISCONNECT:
Log.w(TAG, "Disconnected: DISCONNECT ignored: " + mDevice);
break;
case STACK_EVENT:
A2dpStackEvent event = (A2dpStackEvent) message.obj;
log("Disconnected: stack event: " + event);
if (!mDevice.equals(event.device)) {
Log.wtfStack(TAG, "Device(" + mDevice + "): event mismatch: " + event);
}
switch (event.type) {
case A2dpStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED:
processConnectionEvent(event.valueInt);
break;
case A2dpStackEvent.EVENT_TYPE_CODEC_CONFIG_CHANGED:
processCodecConfigEvent(event.codecStatus);
break;
default:
Log.e(TAG, "Disconnected: ignoring stack event: " + event);
break;
}
break;
default:
return NOT_HANDLED;
}
return HANDLED;
}
// in Disconnected state
private void processConnectionEvent(int event) {
switch (event) {
case A2dpStackEvent.CONNECTION_STATE_DISCONNECTED:
Log.w(TAG, "Ignore A2DP DISCONNECTED event: " + mDevice);
break;
case A2dpStackEvent.CONNECTION_STATE_CONNECTING:
if (mA2dpService.okToConnect(mDevice, false)) {
Log.i(TAG, "Incoming A2DP Connecting request accepted: " + mDevice);
transitionTo(mConnecting);
} else {
// Reject the connection and stay in Disconnected state itself
Log.w(TAG, "Incoming A2DP Connecting request rejected: " + mDevice);
mA2dpNativeInterface.disconnectA2dp(mDevice);
}
break;
case A2dpStackEvent.CONNECTION_STATE_CONNECTED:
Log.w(TAG, "A2DP Connected from Disconnected state: " + mDevice);
if (mA2dpService.okToConnect(mDevice, false)) {
Log.i(TAG, "Incoming A2DP Connected request accepted: " + mDevice);
transitionTo(mConnected);
} else {
// Reject the connection and stay in Disconnected state itself
Log.w(TAG, "Incoming A2DP Connected request rejected: " + mDevice);
mA2dpNativeInterface.disconnectA2dp(mDevice);
}
break;
case A2dpStackEvent.CONNECTION_STATE_DISCONNECTING:
Log.w(TAG, "Ignore A2DP DISCONNECTING event: " + mDevice);
break;
default:
Log.e(TAG, "Incorrect event: " + event + " device: " + mDevice);
break;
}
}
}
@VisibleForTesting
class Connecting extends State {
@Override
public void enter() {
Message currentMessage = getCurrentMessage();
Log.i(TAG, "Enter Connecting(" + mDevice + "): " + (currentMessage == null ? "null"
: messageWhatToString(currentMessage.what)));
sendMessageDelayed(CONNECT_TIMEOUT, sConnectTimeoutMs);
mConnectionState = BluetoothProfile.STATE_CONNECTING;
broadcastConnectionState(mConnectionState, mLastConnectionState);
}
@Override
public void exit() {
Message currentMessage = getCurrentMessage();
log("Exit Connecting(" + mDevice + "): " + (currentMessage == null ? "null"
: messageWhatToString(currentMessage.what)));
mLastConnectionState = BluetoothProfile.STATE_CONNECTING;
removeMessages(CONNECT_TIMEOUT);
}
@Override
public boolean processMessage(Message message) {
log("Connecting process message(" + mDevice + "): "
+ messageWhatToString(message.what));
switch (message.what) {
case CONNECT:
deferMessage(message);
break;
case CONNECT_TIMEOUT: {
Log.w(TAG, "Connecting connection timeout: " + mDevice);
mA2dpNativeInterface.disconnectA2dp(mDevice);
A2dpStackEvent event =
new A2dpStackEvent(A2dpStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
event.device = mDevice;
event.valueInt = A2dpStackEvent.CONNECTION_STATE_DISCONNECTED;
sendMessage(STACK_EVENT, event);
break;
}
case DISCONNECT:
// Cancel connection
Log.i(TAG, "Connecting: connection canceled to " + mDevice);
mA2dpNativeInterface.disconnectA2dp(mDevice);
transitionTo(mDisconnected);
break;
case STACK_EVENT:
A2dpStackEvent event = (A2dpStackEvent) message.obj;
log("Connecting: stack event: " + event);
if (!mDevice.equals(event.device)) {
Log.wtfStack(TAG, "Device(" + mDevice + "): event mismatch: " + event);
}
switch (event.type) {
case A2dpStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED:
processConnectionEvent(event.valueInt);
break;
case A2dpStackEvent.EVENT_TYPE_CODEC_CONFIG_CHANGED:
processCodecConfigEvent(event.codecStatus);
break;
case A2dpStackEvent.EVENT_TYPE_AUDIO_STATE_CHANGED:
break;
default:
Log.e(TAG, "Connecting: ignoring stack event: " + event);
break;
}
break;
default:
return NOT_HANDLED;
}
return HANDLED;
}
// in Connecting state
private void processConnectionEvent(int event) {
switch (event) {
case A2dpStackEvent.CONNECTION_STATE_DISCONNECTED:
Log.w(TAG, "Connecting device disconnected: " + mDevice);
transitionTo(mDisconnected);
break;
case A2dpStackEvent.CONNECTION_STATE_CONNECTED:
transitionTo(mConnected);
break;
case A2dpStackEvent.CONNECTION_STATE_CONNECTING:
// Ignored - probably an event that the outgoing connection was initiated
break;
case A2dpStackEvent.CONNECTION_STATE_DISCONNECTING:
Log.w(TAG, "Connecting interrupted: device is disconnecting: " + mDevice);
transitionTo(mDisconnecting);
break;
default:
Log.e(TAG, "Incorrect event: " + event);
break;
}
}
}
@VisibleForTesting
class Disconnecting extends State {
@Override
public void enter() {
Message currentMessage = getCurrentMessage();
Log.i(TAG, "Enter Disconnecting(" + mDevice + "): " + (currentMessage == null ? "null"
: messageWhatToString(currentMessage.what)));
sendMessageDelayed(CONNECT_TIMEOUT, sConnectTimeoutMs);
mConnectionState = BluetoothProfile.STATE_DISCONNECTING;
broadcastConnectionState(mConnectionState, mLastConnectionState);
}
@Override
public void exit() {
Message currentMessage = getCurrentMessage();
log("Exit Disconnecting(" + mDevice + "): " + (currentMessage == null ? "null"
: messageWhatToString(currentMessage.what)));
mLastConnectionState = BluetoothProfile.STATE_DISCONNECTING;
removeMessages(CONNECT_TIMEOUT);
}
@Override
public boolean processMessage(Message message) {
log("Disconnecting process message(" + mDevice + "): "
+ messageWhatToString(message.what));
switch (message.what) {
case CONNECT:
deferMessage(message);
break;
case CONNECT_TIMEOUT: {
Log.w(TAG, "Disconnecting connection timeout: " + mDevice);
mA2dpNativeInterface.disconnectA2dp(mDevice);
A2dpStackEvent event =
new A2dpStackEvent(A2dpStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
event.device = mDevice;
event.valueInt = A2dpStackEvent.CONNECTION_STATE_DISCONNECTED;
sendMessage(STACK_EVENT, event);
break;
}
case DISCONNECT:
deferMessage(message);
break;
case STACK_EVENT:
A2dpStackEvent event = (A2dpStackEvent) message.obj;
log("Disconnecting: stack event: " + event);
if (!mDevice.equals(event.device)) {
Log.wtfStack(TAG, "Device(" + mDevice + "): event mismatch: " + event);
}
switch (event.type) {
case A2dpStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED:
processConnectionEvent(event.valueInt);
break;
case A2dpStackEvent.EVENT_TYPE_CODEC_CONFIG_CHANGED:
processCodecConfigEvent(event.codecStatus);
break;
case A2dpStackEvent.EVENT_TYPE_AUDIO_STATE_CHANGED:
default:
Log.e(TAG, "Disconnecting: ignoring stack event: " + event);
break;
}
break;
default:
return NOT_HANDLED;
}
return HANDLED;
}
// in Disconnecting state
private void processConnectionEvent(int event) {
switch (event) {
case A2dpStackEvent.CONNECTION_STATE_DISCONNECTED:
Log.i(TAG, "Disconnected: " + mDevice);
transitionTo(mDisconnected);
break;
case A2dpStackEvent.CONNECTION_STATE_CONNECTED:
if (mA2dpService.okToConnect(mDevice, false)) {
Log.w(TAG, "Disconnecting interrupted: device is connected: " + mDevice);
transitionTo(mConnected);
} else {
// Reject the connection and stay in Disconnecting state
Log.w(TAG, "Incoming A2DP Connected request rejected: " + mDevice);
mA2dpNativeInterface.disconnectA2dp(mDevice);
}
break;
case A2dpStackEvent.CONNECTION_STATE_CONNECTING:
if (mA2dpService.okToConnect(mDevice, false)) {
Log.i(TAG, "Disconnecting interrupted: try to reconnect: " + mDevice);
transitionTo(mConnecting);
} else {
// Reject the connection and stay in Disconnecting state
Log.w(TAG, "Incoming A2DP Connecting request rejected: " + mDevice);
mA2dpNativeInterface.disconnectA2dp(mDevice);
}
break;
case A2dpStackEvent.CONNECTION_STATE_DISCONNECTING:
// We are already disconnecting, do nothing
break;
default:
Log.e(TAG, "Incorrect event: " + event);
break;
}
}
}
@VisibleForTesting
class Connected extends State {
@Override
public void enter() {
Message currentMessage = getCurrentMessage();
Log.i(TAG, "Enter Connected(" + mDevice + "): " + (currentMessage == null ? "null"
: messageWhatToString(currentMessage.what)));
mConnectionState = BluetoothProfile.STATE_CONNECTED;
removeDeferredMessages(CONNECT);
broadcastConnectionState(mConnectionState, mLastConnectionState);
// Upon connected, the audio starts out as stopped
broadcastAudioState(BluetoothA2dp.STATE_NOT_PLAYING,
BluetoothA2dp.STATE_PLAYING);
}
@Override
public void exit() {
Message currentMessage = getCurrentMessage();
log("Exit Connected(" + mDevice + "): " + (currentMessage == null ? "null"
: messageWhatToString(currentMessage.what)));
mLastConnectionState = BluetoothProfile.STATE_CONNECTED;
}
@Override
public boolean processMessage(Message message) {
log("Connected process message(" + mDevice + "): " + messageWhatToString(message.what));
switch (message.what) {
case CONNECT:
Log.w(TAG, "Connected: CONNECT ignored: " + mDevice);
break;
case DISCONNECT: {
Log.i(TAG, "Disconnecting from " + mDevice);
if (!mA2dpNativeInterface.disconnectA2dp(mDevice)) {
// If error in the native stack, transition directly to Disconnected state.
Log.e(TAG, "Connected: error disconnecting from " + mDevice);
transitionTo(mDisconnected);
break;
}
transitionTo(mDisconnecting);
}
break;
case STACK_EVENT:
A2dpStackEvent event = (A2dpStackEvent) message.obj;
log("Connected: stack event: " + event);
if (!mDevice.equals(event.device)) {
Log.wtfStack(TAG, "Device(" + mDevice + "): event mismatch: " + event);
}
switch (event.type) {
case A2dpStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED:
processConnectionEvent(event.valueInt);
break;
case A2dpStackEvent.EVENT_TYPE_AUDIO_STATE_CHANGED:
processAudioStateEvent(event.valueInt);
break;
case A2dpStackEvent.EVENT_TYPE_CODEC_CONFIG_CHANGED:
processCodecConfigEvent(event.codecStatus);
break;
default:
Log.e(TAG, "Connected: ignoring stack event: " + event);
break;
}
break;
default:
return NOT_HANDLED;
}
return HANDLED;
}
// in Connected state
private void processConnectionEvent(int event) {
switch (event) {
case A2dpStackEvent.CONNECTION_STATE_DISCONNECTED:
Log.i(TAG, "Disconnected from " + mDevice);
transitionTo(mDisconnected);
break;
case A2dpStackEvent.CONNECTION_STATE_CONNECTED:
Log.w(TAG, "Ignore A2DP CONNECTED event: " + mDevice);
break;
case A2dpStackEvent.CONNECTION_STATE_CONNECTING:
Log.w(TAG, "Ignore A2DP CONNECTING event: " + mDevice);
break;
case A2dpStackEvent.CONNECTION_STATE_DISCONNECTING:
Log.i(TAG, "Disconnecting from " + mDevice);
transitionTo(mDisconnecting);
break;
default:
Log.e(TAG, "Connection State Device: " + mDevice + " bad event: " + event);
break;
}
}
// in Connected state
private void processAudioStateEvent(int state) {
switch (state) {
case A2dpStackEvent.AUDIO_STATE_STARTED:
synchronized (this) {
if (!mIsPlaying) {
Log.i(TAG, "Connected: started playing: " + mDevice);
mIsPlaying = true;
broadcastAudioState(BluetoothA2dp.STATE_PLAYING,
BluetoothA2dp.STATE_NOT_PLAYING);
}
}
break;
case A2dpStackEvent.AUDIO_STATE_REMOTE_SUSPEND:
case A2dpStackEvent.AUDIO_STATE_STOPPED:
synchronized (this) {
if (mIsPlaying) {
Log.i(TAG, "Connected: stopped playing: " + mDevice);
mIsPlaying = false;
broadcastAudioState(BluetoothA2dp.STATE_NOT_PLAYING,
BluetoothA2dp.STATE_PLAYING);
}
}
break;
default:
Log.e(TAG, "Audio State Device: " + mDevice + " bad state: " + state);
break;
}
}
}
int getConnectionState() {
return mConnectionState;
}
BluetoothDevice getDevice() {
return mDevice;
}
boolean isConnected() {
synchronized (this) {
return (getCurrentState() == mConnected);
}
}
boolean isPlaying() {
synchronized (this) {
return mIsPlaying;
}
}
BluetoothCodecStatus getCodecStatus() {
synchronized (this) {
return mCodecStatus;
}
}
// NOTE: This event is processed in any state
private void processCodecConfigEvent(BluetoothCodecStatus newCodecStatus) {
BluetoothCodecConfig prevCodecConfig = null;
synchronized (this) {
if (mCodecStatus != null) {
prevCodecConfig = mCodecStatus.getCodecConfig();
}
mCodecStatus = newCodecStatus;
}
if (DBG) {
Log.d(TAG, "A2DP Codec Config: " + prevCodecConfig + "->"
+ newCodecStatus.getCodecConfig());
for (BluetoothCodecConfig codecConfig :
newCodecStatus.getCodecsLocalCapabilities()) {
Log.d(TAG, "A2DP Codec Local Capability: " + codecConfig);
}
for (BluetoothCodecConfig codecConfig :
newCodecStatus.getCodecsSelectableCapabilities()) {
Log.d(TAG, "A2DP Codec Selectable Capability: " + codecConfig);
}
}
if (mA2dpOffloadEnabled) {
boolean update = false;
BluetoothCodecConfig newCodecConfig = mCodecStatus.getCodecConfig();
if ((prevCodecConfig != null)
&& (prevCodecConfig.getCodecType() != newCodecConfig.getCodecType())) {
update = true;
} else if (!newCodecConfig.sameAudioFeedingParameters(prevCodecConfig)) {
update = true;
} else if ((newCodecConfig.getCodecType()
== BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC)
&& (prevCodecConfig != null)
&& (prevCodecConfig.getCodecSpecific1()
!= newCodecConfig.getCodecSpecific1())) {
update = true;
}
if (update) {
mA2dpService.codecConfigUpdated(mDevice, mCodecStatus, false);
}
return;
}
boolean sameAudioFeedingParameters =
newCodecStatus.getCodecConfig().sameAudioFeedingParameters(prevCodecConfig);
mA2dpService.codecConfigUpdated(mDevice, mCodecStatus, sameAudioFeedingParameters);
}
// This method does not check for error conditon (newState == prevState)
private void broadcastConnectionState(int newState, int prevState) {
log("Connection state " + mDevice + ": " + profileStateToString(prevState)
+ "->" + profileStateToString(newState));
Intent intent = new Intent(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED);
intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, prevState);
intent.putExtra(BluetoothProfile.EXTRA_STATE, newState);
intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mDevice);
intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT
| Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
mA2dpService.sendBroadcast(intent, ProfileService.BLUETOOTH_PERM);
}
private void broadcastAudioState(int newState, int prevState) {
log("A2DP Playing state : device: " + mDevice + " State:" + audioStateToString(prevState)
+ "->" + audioStateToString(newState));
Intent intent = new Intent(BluetoothA2dp.ACTION_PLAYING_STATE_CHANGED);
intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mDevice);
intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, prevState);
intent.putExtra(BluetoothProfile.EXTRA_STATE, newState);
intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
mA2dpService.sendBroadcast(intent, A2dpService.BLUETOOTH_PERM);
}
@Override
protected String getLogRecString(Message msg) {
StringBuilder builder = new StringBuilder();
builder.append(messageWhatToString(msg.what));
builder.append(": ");
builder.append("arg1=")
.append(msg.arg1)
.append(", arg2=")
.append(msg.arg2)
.append(", obj=")
.append(msg.obj);
return builder.toString();
}
private static String messageWhatToString(int what) {
switch (what) {
case CONNECT:
return "CONNECT";
case DISCONNECT:
return "DISCONNECT";
case STACK_EVENT:
return "STACK_EVENT";
case CONNECT_TIMEOUT:
return "CONNECT_TIMEOUT";
default:
break;
}
return Integer.toString(what);
}
private static String profileStateToString(int state) {
switch (state) {
case BluetoothProfile.STATE_DISCONNECTED:
return "DISCONNECTED";
case BluetoothProfile.STATE_CONNECTING:
return "CONNECTING";
case BluetoothProfile.STATE_CONNECTED:
return "CONNECTED";
case BluetoothProfile.STATE_DISCONNECTING:
return "DISCONNECTING";
default:
break;
}
return Integer.toString(state);
}
private static String audioStateToString(int state) {
switch (state) {
case BluetoothA2dp.STATE_PLAYING:
return "PLAYING";
case BluetoothA2dp.STATE_NOT_PLAYING:
return "NOT_PLAYING";
default:
break;
}
return Integer.toString(state);
}
public void dump(StringBuilder sb) {
ProfileService.println(sb, "mDevice: " + mDevice);
ProfileService.println(sb, " StateMachine: " + this.toString());
ProfileService.println(sb, " mIsPlaying: " + mIsPlaying);
synchronized (this) {
if (mCodecStatus != null) {
ProfileService.println(sb, " mCodecConfig: " + mCodecStatus.getCodecConfig());
}
}
// Dump the state machine logs
StringWriter stringWriter = new StringWriter();
PrintWriter printWriter = new PrintWriter(stringWriter);
super.dump(new FileDescriptor(), printWriter, new String[]{});
printWriter.flush();
stringWriter.flush();
ProfileService.println(sb, " StateMachineLog:");
Scanner scanner = new Scanner(stringWriter.toString());
while (scanner.hasNextLine()) {
String line = scanner.nextLine();
ProfileService.println(sb, " " + line);
}
scanner.close();
}
@Override
protected void log(String msg) {
if (DBG) {
super.log(msg);
}
}
}