• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
A2dpStateMachine(BluetoothDevice device, A2dpService a2dpService, A2dpNativeInterface a2dpNativeInterface, Looper looper)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 
make(BluetoothDevice device, A2dpService a2dpService, A2dpNativeInterface a2dpNativeInterface, Looper looper)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 
doQuit()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 
cleanup()140     public void cleanup() {
141         log("cleanup for device " + mDevice);
142     }
143 
144     @VisibleForTesting
145     class Disconnected extends State {
146         @Override
enter()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
exit()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
processMessage(Message message)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
processConnectionEvent(int event)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
enter()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
exit()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
processMessage(Message message)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
processConnectionEvent(int event)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
enter()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
exit()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
processMessage(Message message)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
processConnectionEvent(int event)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
enter()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
exit()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
processMessage(Message message)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
processConnectionEvent(int event)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
processAudioStateEvent(int 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 
getConnectionState()587     int getConnectionState() {
588         return mConnectionState;
589     }
590 
getDevice()591     BluetoothDevice getDevice() {
592         return mDevice;
593     }
594 
isConnected()595     boolean isConnected() {
596         synchronized (this) {
597             return (getCurrentState() == mConnected);
598         }
599     }
600 
isPlaying()601     boolean isPlaying() {
602         synchronized (this) {
603             return mIsPlaying;
604         }
605     }
606 
getCodecStatus()607     BluetoothCodecStatus getCodecStatus() {
608         synchronized (this) {
609             return mCodecStatus;
610         }
611     }
612 
613     // NOTE: This event is processed in any state
processCodecConfigEvent(BluetoothCodecStatus newCodecStatus)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)
broadcastConnectionState(int newState, int 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 
broadcastAudioState(int newState, int prevState)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
getLogRecString(Message msg)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 
messageWhatToString(int what)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 
profileStateToString(int state)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 
audioStateToString(int state)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 
dump(StringBuilder sb)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
log(String msg)772     protected void log(String msg) {
773         if (DBG) {
774             super.log(msg);
775         }
776     }
777 }
778