Home | History | Annotate | Download | only in a2dp
      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 
     17 /**
     18  * Bluetooth A2DP StateMachine. There is one instance per remote device.
     19  *  - "Disconnected" and "Connected" are steady states.
     20  *  - "Connecting" and "Disconnecting" are transient states until the
     21  *     connection / disconnection is completed.
     22  *
     23  *
     24  *                        (Disconnected)
     25  *                           |       ^
     26  *                   CONNECT |       | DISCONNECTED
     27  *                           V       |
     28  *                 (Connecting)<--->(Disconnecting)
     29  *                           |       ^
     30  *                 CONNECTED |       | DISCONNECT
     31  *                           V       |
     32  *                          (Connected)
     33  * NOTES:
     34  *  - If state machine is in "Connecting" state and the remote device sends
     35  *    DISCONNECT request, the state machine transitions to "Disconnecting" state.
     36  *  - Similarly, if the state machine is in "Disconnecting" state and the remote device
     37  *    sends CONNECT request, the state machine transitions to "Connecting" state.
     38  *
     39  *                    DISCONNECT
     40  *    (Connecting) ---------------> (Disconnecting)
     41  *                 <---------------
     42  *                      CONNECT
     43  *
     44  */
     45 
     46 package com.android.bluetooth.a2dp;
     47 
     48 import android.bluetooth.BluetoothA2dp;
     49 import android.bluetooth.BluetoothCodecConfig;
     50 import android.bluetooth.BluetoothCodecStatus;
     51 import android.bluetooth.BluetoothDevice;
     52 import android.bluetooth.BluetoothProfile;
     53 import android.content.Intent;
     54 import android.os.Looper;
     55 import android.os.Message;
     56 import android.support.annotation.VisibleForTesting;
     57 import android.util.Log;
     58 
     59 import com.android.bluetooth.btservice.ProfileService;
     60 import com.android.internal.util.State;
     61 import com.android.internal.util.StateMachine;
     62 
     63 import java.io.FileDescriptor;
     64 import java.io.PrintWriter;
     65 import java.io.StringWriter;
     66 import java.util.Scanner;
     67 
     68 final class A2dpStateMachine extends StateMachine {
     69     private static final boolean DBG = true;
     70     private static final String TAG = "A2dpStateMachine";
     71 
     72     static final int CONNECT = 1;
     73     static final int DISCONNECT = 2;
     74     @VisibleForTesting
     75     static final int STACK_EVENT = 101;
     76     private static final int CONNECT_TIMEOUT = 201;
     77 
     78     // NOTE: the value is not "final" - it is modified in the unit tests
     79     @VisibleForTesting
     80     static int sConnectTimeoutMs = 30000;        // 30s
     81 
     82     private Disconnected mDisconnected;
     83     private Connecting mConnecting;
     84     private Disconnecting mDisconnecting;
     85     private Connected mConnected;
     86     private int mConnectionState = BluetoothProfile.STATE_DISCONNECTED;
     87     private int mLastConnectionState = -1;
     88 
     89     private A2dpService mA2dpService;
     90     private A2dpNativeInterface mA2dpNativeInterface;
     91     private boolean mA2dpOffloadEnabled = false;
     92     private final BluetoothDevice mDevice;
     93     private boolean mIsPlaying = false;
     94     private BluetoothCodecStatus mCodecStatus;
     95 
     96     A2dpStateMachine(BluetoothDevice device, A2dpService a2dpService,
     97                      A2dpNativeInterface a2dpNativeInterface, Looper looper) {
     98         super(TAG, looper);
     99         setDbg(DBG);
    100         mDevice = device;
    101         mA2dpService = a2dpService;
    102         mA2dpNativeInterface = a2dpNativeInterface;
    103 
    104         mDisconnected = new Disconnected();
    105         mConnecting = new Connecting();
    106         mDisconnecting = new Disconnecting();
    107         mConnected = new Connected();
    108 
    109         addState(mDisconnected);
    110         addState(mConnecting);
    111         addState(mDisconnecting);
    112         addState(mConnected);
    113         mA2dpOffloadEnabled = mA2dpService.mA2dpOffloadEnabled;
    114 
    115         setInitialState(mDisconnected);
    116     }
    117 
    118     static A2dpStateMachine make(BluetoothDevice device, A2dpService a2dpService,
    119                                  A2dpNativeInterface a2dpNativeInterface, Looper looper) {
    120         Log.i(TAG, "make for device " + device);
    121         A2dpStateMachine a2dpSm = new A2dpStateMachine(device, a2dpService, a2dpNativeInterface,
    122                                                        looper);
    123         a2dpSm.start();
    124         return a2dpSm;
    125     }
    126 
    127     public void doQuit() {
    128         log("doQuit for device " + mDevice);
    129         if (mIsPlaying) {
    130             // Stop if auido is still playing
    131             log("doQuit: stopped playing " + mDevice);
    132             mIsPlaying = false;
    133             mA2dpService.setAvrcpAudioState(BluetoothA2dp.STATE_NOT_PLAYING);
    134             broadcastAudioState(BluetoothA2dp.STATE_NOT_PLAYING,
    135                                 BluetoothA2dp.STATE_PLAYING);
    136         }
    137         quitNow();
    138     }
    139 
    140     public void cleanup() {
    141         log("cleanup for device " + mDevice);
    142     }
    143 
    144     @VisibleForTesting
    145     class Disconnected extends State {
    146         @Override
    147         public void enter() {
    148             Message currentMessage = getCurrentMessage();
    149             Log.i(TAG, "Enter Disconnected(" + mDevice + "): " + (currentMessage == null ? "null"
    150                     : messageWhatToString(currentMessage.what)));
    151             mConnectionState = BluetoothProfile.STATE_DISCONNECTED;
    152 
    153             removeDeferredMessages(DISCONNECT);
    154 
    155             if (mLastConnectionState != -1) {
    156                 // Don't broadcast during startup
    157                 broadcastConnectionState(mConnectionState, mLastConnectionState);
    158                 if (mIsPlaying) {
    159                     Log.i(TAG, "Disconnected: stopped playing: " + mDevice);
    160                     mIsPlaying = false;
    161                     mA2dpService.setAvrcpAudioState(BluetoothA2dp.STATE_NOT_PLAYING);
    162                     broadcastAudioState(BluetoothA2dp.STATE_NOT_PLAYING,
    163                                         BluetoothA2dp.STATE_PLAYING);
    164                 }
    165             }
    166         }
    167 
    168         @Override
    169         public void exit() {
    170             Message currentMessage = getCurrentMessage();
    171             log("Exit Disconnected(" + mDevice + "): " + (currentMessage == null ? "null"
    172                     : messageWhatToString(currentMessage.what)));
    173             mLastConnectionState = BluetoothProfile.STATE_DISCONNECTED;
    174         }
    175 
    176         @Override
    177         public boolean processMessage(Message message) {
    178             log("Disconnected process message(" + mDevice + "): "
    179                     + messageWhatToString(message.what));
    180 
    181             switch (message.what) {
    182                 case CONNECT:
    183                     Log.i(TAG, "Connecting to " + mDevice);
    184                     if (!mA2dpNativeInterface.connectA2dp(mDevice)) {
    185                         Log.e(TAG, "Disconnected: error connecting to " + mDevice);
    186                         break;
    187                     }
    188                     if (mA2dpService.okToConnect(mDevice, true)) {
    189                         transitionTo(mConnecting);
    190                     } else {
    191                         // Reject the request and stay in Disconnected state
    192                         Log.w(TAG, "Outgoing A2DP Connecting request rejected: " + mDevice);
    193                     }
    194                     break;
    195                 case DISCONNECT:
    196                     Log.w(TAG, "Disconnected: DISCONNECT ignored: " + mDevice);
    197                     break;
    198                 case STACK_EVENT:
    199                     A2dpStackEvent event = (A2dpStackEvent) message.obj;
    200                     log("Disconnected: stack event: " + event);
    201                     if (!mDevice.equals(event.device)) {
    202                         Log.wtfStack(TAG, "Device(" + mDevice + "): event mismatch: " + event);
    203                     }
    204                     switch (event.type) {
    205                         case A2dpStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED:
    206                             processConnectionEvent(event.valueInt);
    207                             break;
    208                         case A2dpStackEvent.EVENT_TYPE_CODEC_CONFIG_CHANGED:
    209                             processCodecConfigEvent(event.codecStatus);
    210                             break;
    211                         default:
    212                             Log.e(TAG, "Disconnected: ignoring stack event: " + event);
    213                             break;
    214                     }
    215                     break;
    216                 default:
    217                     return NOT_HANDLED;
    218             }
    219             return HANDLED;
    220         }
    221 
    222         // in Disconnected state
    223         private void processConnectionEvent(int event) {
    224             switch (event) {
    225                 case A2dpStackEvent.CONNECTION_STATE_DISCONNECTED:
    226                     Log.w(TAG, "Ignore A2DP DISCONNECTED event: " + mDevice);
    227                     break;
    228                 case A2dpStackEvent.CONNECTION_STATE_CONNECTING:
    229                     if (mA2dpService.okToConnect(mDevice, false)) {
    230                         Log.i(TAG, "Incoming A2DP Connecting request accepted: " + mDevice);
    231                         transitionTo(mConnecting);
    232                     } else {
    233                         // Reject the connection and stay in Disconnected state itself
    234                         Log.w(TAG, "Incoming A2DP Connecting request rejected: " + mDevice);
    235                         mA2dpNativeInterface.disconnectA2dp(mDevice);
    236                     }
    237                     break;
    238                 case A2dpStackEvent.CONNECTION_STATE_CONNECTED:
    239                     Log.w(TAG, "A2DP Connected from Disconnected state: " + mDevice);
    240                     if (mA2dpService.okToConnect(mDevice, false)) {
    241                         Log.i(TAG, "Incoming A2DP Connected request accepted: " + mDevice);
    242                         transitionTo(mConnected);
    243                     } else {
    244                         // Reject the connection and stay in Disconnected state itself
    245                         Log.w(TAG, "Incoming A2DP Connected request rejected: " + mDevice);
    246                         mA2dpNativeInterface.disconnectA2dp(mDevice);
    247                     }
    248                     break;
    249                 case A2dpStackEvent.CONNECTION_STATE_DISCONNECTING:
    250                     Log.w(TAG, "Ignore A2DP DISCONNECTING event: " + mDevice);
    251                     break;
    252                 default:
    253                     Log.e(TAG, "Incorrect event: " + event + " device: " + mDevice);
    254                     break;
    255             }
    256         }
    257     }
    258 
    259     @VisibleForTesting
    260     class Connecting extends State {
    261         @Override
    262         public void enter() {
    263             Message currentMessage = getCurrentMessage();
    264             Log.i(TAG, "Enter Connecting(" + mDevice + "): " + (currentMessage == null ? "null"
    265                     : messageWhatToString(currentMessage.what)));
    266             sendMessageDelayed(CONNECT_TIMEOUT, sConnectTimeoutMs);
    267             mConnectionState = BluetoothProfile.STATE_CONNECTING;
    268             broadcastConnectionState(mConnectionState, mLastConnectionState);
    269         }
    270 
    271         @Override
    272         public void exit() {
    273             Message currentMessage = getCurrentMessage();
    274             log("Exit Connecting(" + mDevice + "): " + (currentMessage == null ? "null"
    275                     : messageWhatToString(currentMessage.what)));
    276             mLastConnectionState = BluetoothProfile.STATE_CONNECTING;
    277             removeMessages(CONNECT_TIMEOUT);
    278         }
    279 
    280         @Override
    281         public boolean processMessage(Message message) {
    282             log("Connecting process message(" + mDevice + "): "
    283                     + messageWhatToString(message.what));
    284 
    285             switch (message.what) {
    286                 case CONNECT:
    287                     deferMessage(message);
    288                     break;
    289                 case CONNECT_TIMEOUT: {
    290                     Log.w(TAG, "Connecting connection timeout: " + mDevice);
    291                     mA2dpNativeInterface.disconnectA2dp(mDevice);
    292                     A2dpStackEvent event =
    293                             new A2dpStackEvent(A2dpStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
    294                     event.device = mDevice;
    295                     event.valueInt = A2dpStackEvent.CONNECTION_STATE_DISCONNECTED;
    296                     sendMessage(STACK_EVENT, event);
    297                     break;
    298                 }
    299                 case DISCONNECT:
    300                     // Cancel connection
    301                     Log.i(TAG, "Connecting: connection canceled to " + mDevice);
    302                     mA2dpNativeInterface.disconnectA2dp(mDevice);
    303                     transitionTo(mDisconnected);
    304                     break;
    305                 case STACK_EVENT:
    306                     A2dpStackEvent event = (A2dpStackEvent) message.obj;
    307                     log("Connecting: stack event: " + event);
    308                     if (!mDevice.equals(event.device)) {
    309                         Log.wtfStack(TAG, "Device(" + mDevice + "): event mismatch: " + event);
    310                     }
    311                     switch (event.type) {
    312                         case A2dpStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED:
    313                             processConnectionEvent(event.valueInt);
    314                             break;
    315                         case A2dpStackEvent.EVENT_TYPE_CODEC_CONFIG_CHANGED:
    316                             processCodecConfigEvent(event.codecStatus);
    317                             break;
    318                         case A2dpStackEvent.EVENT_TYPE_AUDIO_STATE_CHANGED:
    319                             break;
    320                         default:
    321                             Log.e(TAG, "Connecting: ignoring stack event: " + event);
    322                             break;
    323                     }
    324                     break;
    325                 default:
    326                     return NOT_HANDLED;
    327             }
    328             return HANDLED;
    329         }
    330 
    331         // in Connecting state
    332         private void processConnectionEvent(int event) {
    333             switch (event) {
    334                 case A2dpStackEvent.CONNECTION_STATE_DISCONNECTED:
    335                     Log.w(TAG, "Connecting device disconnected: " + mDevice);
    336                     transitionTo(mDisconnected);
    337                     break;
    338                 case A2dpStackEvent.CONNECTION_STATE_CONNECTED:
    339                     transitionTo(mConnected);
    340                     break;
    341                 case A2dpStackEvent.CONNECTION_STATE_CONNECTING:
    342                     // Ignored - probably an event that the outgoing connection was initiated
    343                     break;
    344                 case A2dpStackEvent.CONNECTION_STATE_DISCONNECTING:
    345                     Log.w(TAG, "Connecting interrupted: device is disconnecting: " + mDevice);
    346                     transitionTo(mDisconnecting);
    347                     break;
    348                 default:
    349                     Log.e(TAG, "Incorrect event: " + event);
    350                     break;
    351             }
    352         }
    353     }
    354 
    355     @VisibleForTesting
    356     class Disconnecting extends State {
    357         @Override
    358         public void enter() {
    359             Message currentMessage = getCurrentMessage();
    360             Log.i(TAG, "Enter Disconnecting(" + mDevice + "): " + (currentMessage == null ? "null"
    361                     : messageWhatToString(currentMessage.what)));
    362             sendMessageDelayed(CONNECT_TIMEOUT, sConnectTimeoutMs);
    363             mConnectionState = BluetoothProfile.STATE_DISCONNECTING;
    364             broadcastConnectionState(mConnectionState, mLastConnectionState);
    365         }
    366 
    367         @Override
    368         public void exit() {
    369             Message currentMessage = getCurrentMessage();
    370             log("Exit Disconnecting(" + mDevice + "): " + (currentMessage == null ? "null"
    371                     : messageWhatToString(currentMessage.what)));
    372             mLastConnectionState = BluetoothProfile.STATE_DISCONNECTING;
    373             removeMessages(CONNECT_TIMEOUT);
    374         }
    375 
    376         @Override
    377         public boolean processMessage(Message message) {
    378             log("Disconnecting process message(" + mDevice + "): "
    379                     + messageWhatToString(message.what));
    380 
    381             switch (message.what) {
    382                 case CONNECT:
    383                     deferMessage(message);
    384                     break;
    385                 case CONNECT_TIMEOUT: {
    386                     Log.w(TAG, "Disconnecting connection timeout: " + mDevice);
    387                     mA2dpNativeInterface.disconnectA2dp(mDevice);
    388                     A2dpStackEvent event =
    389                             new A2dpStackEvent(A2dpStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
    390                     event.device = mDevice;
    391                     event.valueInt = A2dpStackEvent.CONNECTION_STATE_DISCONNECTED;
    392                     sendMessage(STACK_EVENT, event);
    393                     break;
    394                 }
    395                 case DISCONNECT:
    396                     deferMessage(message);
    397                     break;
    398                 case STACK_EVENT:
    399                     A2dpStackEvent event = (A2dpStackEvent) message.obj;
    400                     log("Disconnecting: stack event: " + event);
    401                     if (!mDevice.equals(event.device)) {
    402                         Log.wtfStack(TAG, "Device(" + mDevice + "): event mismatch: " + event);
    403                     }
    404                     switch (event.type) {
    405                         case A2dpStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED:
    406                             processConnectionEvent(event.valueInt);
    407                             break;
    408                         case A2dpStackEvent.EVENT_TYPE_CODEC_CONFIG_CHANGED:
    409                             processCodecConfigEvent(event.codecStatus);
    410                             break;
    411                         case A2dpStackEvent.EVENT_TYPE_AUDIO_STATE_CHANGED:
    412                         default:
    413                             Log.e(TAG, "Disconnecting: ignoring stack event: " + event);
    414                             break;
    415                     }
    416                     break;
    417                 default:
    418                     return NOT_HANDLED;
    419             }
    420             return HANDLED;
    421         }
    422 
    423         // in Disconnecting state
    424         private void processConnectionEvent(int event) {
    425             switch (event) {
    426                 case A2dpStackEvent.CONNECTION_STATE_DISCONNECTED:
    427                     Log.i(TAG, "Disconnected: " + mDevice);
    428                     transitionTo(mDisconnected);
    429                     break;
    430                 case A2dpStackEvent.CONNECTION_STATE_CONNECTED:
    431                     if (mA2dpService.okToConnect(mDevice, false)) {
    432                         Log.w(TAG, "Disconnecting interrupted: device is connected: " + mDevice);
    433                         transitionTo(mConnected);
    434                     } else {
    435                         // Reject the connection and stay in Disconnecting state
    436                         Log.w(TAG, "Incoming A2DP Connected request rejected: " + mDevice);
    437                         mA2dpNativeInterface.disconnectA2dp(mDevice);
    438                     }
    439                     break;
    440                 case A2dpStackEvent.CONNECTION_STATE_CONNECTING:
    441                     if (mA2dpService.okToConnect(mDevice, false)) {
    442                         Log.i(TAG, "Disconnecting interrupted: try to reconnect: " + mDevice);
    443                         transitionTo(mConnecting);
    444                     } else {
    445                         // Reject the connection and stay in Disconnecting state
    446                         Log.w(TAG, "Incoming A2DP Connecting request rejected: " + mDevice);
    447                         mA2dpNativeInterface.disconnectA2dp(mDevice);
    448                     }
    449                     break;
    450                 case A2dpStackEvent.CONNECTION_STATE_DISCONNECTING:
    451                     // We are already disconnecting, do nothing
    452                     break;
    453                 default:
    454                     Log.e(TAG, "Incorrect event: " + event);
    455                     break;
    456             }
    457         }
    458     }
    459 
    460     @VisibleForTesting
    461     class Connected extends State {
    462         @Override
    463         public void enter() {
    464             Message currentMessage = getCurrentMessage();
    465             Log.i(TAG, "Enter Connected(" + mDevice + "): " + (currentMessage == null ? "null"
    466                     : messageWhatToString(currentMessage.what)));
    467             mConnectionState = BluetoothProfile.STATE_CONNECTED;
    468 
    469             removeDeferredMessages(CONNECT);
    470 
    471             broadcastConnectionState(mConnectionState, mLastConnectionState);
    472             // Upon connected, the audio starts out as stopped
    473             broadcastAudioState(BluetoothA2dp.STATE_NOT_PLAYING,
    474                                 BluetoothA2dp.STATE_PLAYING);
    475         }
    476 
    477         @Override
    478         public void exit() {
    479             Message currentMessage = getCurrentMessage();
    480             log("Exit Connected(" + mDevice + "): " + (currentMessage == null ? "null"
    481                     : messageWhatToString(currentMessage.what)));
    482             mLastConnectionState = BluetoothProfile.STATE_CONNECTED;
    483         }
    484 
    485         @Override
    486         public boolean processMessage(Message message) {
    487             log("Connected process message(" + mDevice + "): " + messageWhatToString(message.what));
    488 
    489             switch (message.what) {
    490                 case CONNECT:
    491                     Log.w(TAG, "Connected: CONNECT ignored: " + mDevice);
    492                     break;
    493                 case DISCONNECT: {
    494                     Log.i(TAG, "Disconnecting from " + mDevice);
    495                     if (!mA2dpNativeInterface.disconnectA2dp(mDevice)) {
    496                         // If error in the native stack, transition directly to Disconnected state.
    497                         Log.e(TAG, "Connected: error disconnecting from " + mDevice);
    498                         transitionTo(mDisconnected);
    499                         break;
    500                     }
    501                     transitionTo(mDisconnecting);
    502                 }
    503                 break;
    504                 case STACK_EVENT:
    505                     A2dpStackEvent event = (A2dpStackEvent) message.obj;
    506                     log("Connected: stack event: " + event);
    507                     if (!mDevice.equals(event.device)) {
    508                         Log.wtfStack(TAG, "Device(" + mDevice + "): event mismatch: " + event);
    509                     }
    510                     switch (event.type) {
    511                         case A2dpStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED:
    512                             processConnectionEvent(event.valueInt);
    513                             break;
    514                         case A2dpStackEvent.EVENT_TYPE_AUDIO_STATE_CHANGED:
    515                             processAudioStateEvent(event.valueInt);
    516                             break;
    517                         case A2dpStackEvent.EVENT_TYPE_CODEC_CONFIG_CHANGED:
    518                             processCodecConfigEvent(event.codecStatus);
    519                             break;
    520                         default:
    521                             Log.e(TAG, "Connected: ignoring stack event: " + event);
    522                             break;
    523                     }
    524                     break;
    525                 default:
    526                     return NOT_HANDLED;
    527             }
    528             return HANDLED;
    529         }
    530 
    531         // in Connected state
    532         private void processConnectionEvent(int event) {
    533             switch (event) {
    534                 case A2dpStackEvent.CONNECTION_STATE_DISCONNECTED:
    535                     Log.i(TAG, "Disconnected from " + mDevice);
    536                     transitionTo(mDisconnected);
    537                     break;
    538                 case A2dpStackEvent.CONNECTION_STATE_CONNECTED:
    539                     Log.w(TAG, "Ignore A2DP CONNECTED event: " + mDevice);
    540                     break;
    541                 case A2dpStackEvent.CONNECTION_STATE_CONNECTING:
    542                     Log.w(TAG, "Ignore A2DP CONNECTING event: " + mDevice);
    543                     break;
    544                 case A2dpStackEvent.CONNECTION_STATE_DISCONNECTING:
    545                     Log.i(TAG, "Disconnecting from " + mDevice);
    546                     transitionTo(mDisconnecting);
    547                     break;
    548                 default:
    549                     Log.e(TAG, "Connection State Device: " + mDevice + " bad event: " + event);
    550                     break;
    551             }
    552         }
    553 
    554         // in Connected state
    555         private void processAudioStateEvent(int state) {
    556             switch (state) {
    557                 case A2dpStackEvent.AUDIO_STATE_STARTED:
    558                     synchronized (this) {
    559                         if (!mIsPlaying) {
    560                             Log.i(TAG, "Connected: started playing: " + mDevice);
    561                             mIsPlaying = true;
    562                             mA2dpService.setAvrcpAudioState(BluetoothA2dp.STATE_PLAYING);
    563                             broadcastAudioState(BluetoothA2dp.STATE_PLAYING,
    564                                                 BluetoothA2dp.STATE_NOT_PLAYING);
    565                         }
    566                     }
    567                     break;
    568                 case A2dpStackEvent.AUDIO_STATE_REMOTE_SUSPEND:
    569                 case A2dpStackEvent.AUDIO_STATE_STOPPED:
    570                     synchronized (this) {
    571                         if (mIsPlaying) {
    572                             Log.i(TAG, "Connected: stopped playing: " + mDevice);
    573                             mIsPlaying = false;
    574                             mA2dpService.setAvrcpAudioState(BluetoothA2dp.STATE_NOT_PLAYING);
    575                             broadcastAudioState(BluetoothA2dp.STATE_NOT_PLAYING,
    576                                                 BluetoothA2dp.STATE_PLAYING);
    577                         }
    578                     }
    579                     break;
    580                 default:
    581                     Log.e(TAG, "Audio State Device: " + mDevice + " bad state: " + state);
    582                     break;
    583             }
    584         }
    585     }
    586 
    587     int getConnectionState() {
    588         return mConnectionState;
    589     }
    590 
    591     BluetoothDevice getDevice() {
    592         return mDevice;
    593     }
    594 
    595     boolean isConnected() {
    596         synchronized (this) {
    597             return (getCurrentState() == mConnected);
    598         }
    599     }
    600 
    601     boolean isPlaying() {
    602         synchronized (this) {
    603             return mIsPlaying;
    604         }
    605     }
    606 
    607     BluetoothCodecStatus getCodecStatus() {
    608         synchronized (this) {
    609             return mCodecStatus;
    610         }
    611     }
    612 
    613     // NOTE: This event is processed in any state
    614     private void processCodecConfigEvent(BluetoothCodecStatus newCodecStatus) {
    615         BluetoothCodecConfig prevCodecConfig = null;
    616         synchronized (this) {
    617             if (mCodecStatus != null) {
    618                 prevCodecConfig = mCodecStatus.getCodecConfig();
    619             }
    620             mCodecStatus = newCodecStatus;
    621         }
    622 
    623         if (DBG) {
    624             Log.d(TAG, "A2DP Codec Config: " + prevCodecConfig + "->"
    625                     + newCodecStatus.getCodecConfig());
    626             for (BluetoothCodecConfig codecConfig :
    627                      newCodecStatus.getCodecsLocalCapabilities()) {
    628                 Log.d(TAG, "A2DP Codec Local Capability: " + codecConfig);
    629             }
    630             for (BluetoothCodecConfig codecConfig :
    631                      newCodecStatus.getCodecsSelectableCapabilities()) {
    632                 Log.d(TAG, "A2DP Codec Selectable Capability: " + codecConfig);
    633             }
    634         }
    635 
    636         if (mA2dpOffloadEnabled) {
    637             boolean update = false;
    638             BluetoothCodecConfig newCodecConfig = mCodecStatus.getCodecConfig();
    639             if ((prevCodecConfig != null)
    640                     && (prevCodecConfig.getCodecType() != newCodecConfig.getCodecType())) {
    641                 update = true;
    642             } else if (!newCodecConfig.sameAudioFeedingParameters(prevCodecConfig)) {
    643                 update = true;
    644             } else if ((newCodecConfig.getCodecType()
    645                         == BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC)
    646                     && (prevCodecConfig != null)
    647                     && (prevCodecConfig.getCodecSpecific1()
    648                         != newCodecConfig.getCodecSpecific1())) {
    649                 update = true;
    650             }
    651             if (update) {
    652                 mA2dpService.codecConfigUpdated(mDevice, mCodecStatus, false);
    653             }
    654             return;
    655         }
    656 
    657         boolean sameAudioFeedingParameters =
    658                 newCodecStatus.getCodecConfig().sameAudioFeedingParameters(prevCodecConfig);
    659         mA2dpService.codecConfigUpdated(mDevice, mCodecStatus, sameAudioFeedingParameters);
    660     }
    661 
    662     // This method does not check for error conditon (newState == prevState)
    663     private void broadcastConnectionState(int newState, int prevState) {
    664         log("Connection state " + mDevice + ": " + profileStateToString(prevState)
    665                     + "->" + profileStateToString(newState));
    666 
    667         Intent intent = new Intent(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED);
    668         intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, prevState);
    669         intent.putExtra(BluetoothProfile.EXTRA_STATE, newState);
    670         intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mDevice);
    671         intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT
    672                         | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
    673         mA2dpService.sendBroadcast(intent, ProfileService.BLUETOOTH_PERM);
    674     }
    675 
    676     private void broadcastAudioState(int newState, int prevState) {
    677         log("A2DP Playing state : device: " + mDevice + " State:" + audioStateToString(prevState)
    678                 + "->" + audioStateToString(newState));
    679 
    680         Intent intent = new Intent(BluetoothA2dp.ACTION_PLAYING_STATE_CHANGED);
    681         intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mDevice);
    682         intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, prevState);
    683         intent.putExtra(BluetoothProfile.EXTRA_STATE, newState);
    684         intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
    685         mA2dpService.sendBroadcast(intent, A2dpService.BLUETOOTH_PERM);
    686     }
    687 
    688     @Override
    689     protected String getLogRecString(Message msg) {
    690         StringBuilder builder = new StringBuilder();
    691         builder.append(messageWhatToString(msg.what));
    692         builder.append(": ");
    693         builder.append("arg1=")
    694                 .append(msg.arg1)
    695                 .append(", arg2=")
    696                 .append(msg.arg2)
    697                 .append(", obj=")
    698                 .append(msg.obj);
    699         return builder.toString();
    700     }
    701 
    702     private static String messageWhatToString(int what) {
    703         switch (what) {
    704             case CONNECT:
    705                 return "CONNECT";
    706             case DISCONNECT:
    707                 return "DISCONNECT";
    708             case STACK_EVENT:
    709                 return "STACK_EVENT";
    710             case CONNECT_TIMEOUT:
    711                 return "CONNECT_TIMEOUT";
    712             default:
    713                 break;
    714         }
    715         return Integer.toString(what);
    716     }
    717 
    718     private static String profileStateToString(int state) {
    719         switch (state) {
    720             case BluetoothProfile.STATE_DISCONNECTED:
    721                 return "DISCONNECTED";
    722             case BluetoothProfile.STATE_CONNECTING:
    723                 return "CONNECTING";
    724             case BluetoothProfile.STATE_CONNECTED:
    725                 return "CONNECTED";
    726             case BluetoothProfile.STATE_DISCONNECTING:
    727                 return "DISCONNECTING";
    728             default:
    729                 break;
    730         }
    731         return Integer.toString(state);
    732     }
    733 
    734     private static String audioStateToString(int state) {
    735         switch (state) {
    736             case BluetoothA2dp.STATE_PLAYING:
    737                 return "PLAYING";
    738             case BluetoothA2dp.STATE_NOT_PLAYING:
    739                 return "NOT_PLAYING";
    740             default:
    741                 break;
    742         }
    743         return Integer.toString(state);
    744     }
    745 
    746     public void dump(StringBuilder sb) {
    747         ProfileService.println(sb, "mDevice: " + mDevice);
    748         ProfileService.println(sb, "  StateMachine: " + this.toString());
    749         ProfileService.println(sb, "  mIsPlaying: " + mIsPlaying);
    750         synchronized (this) {
    751             if (mCodecStatus != null) {
    752                 ProfileService.println(sb, "  mCodecConfig: " + mCodecStatus.getCodecConfig());
    753             }
    754         }
    755         ProfileService.println(sb, "  StateMachine: " + this);
    756         // Dump the state machine logs
    757         StringWriter stringWriter = new StringWriter();
    758         PrintWriter printWriter = new PrintWriter(stringWriter);
    759         super.dump(new FileDescriptor(), printWriter, new String[]{});
    760         printWriter.flush();
    761         stringWriter.flush();
    762         ProfileService.println(sb, "  StateMachineLog:");
    763         Scanner scanner = new Scanner(stringWriter.toString());
    764         while (scanner.hasNextLine()) {
    765             String line = scanner.nextLine();
    766             ProfileService.println(sb, "    " + line);
    767         }
    768         scanner.close();
    769     }
    770 
    771     @Override
    772     protected void log(String msg) {
    773         if (DBG) {
    774             super.log(msg);
    775         }
    776     }
    777 }
    778