讲解下面的代码:
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 * Changes from Qualcomm Innovation Center are provided under the following license:
17 * Copyright (c) 2023 Qualcomm Innovation Center, Inc. All rights reserved.
18 * SPDX-License-Identifier: BSD-3-Clause-Clear
19 */
20
21 /**
22 * Bluetooth A2DP StateMachine. There is one instance per remote device.
23 * - "Disconnected" and "Connected" are steady states.
24 * - "Connecting" and "Disconnecting" are transient states until the
25 * connection / disconnection is completed.
26 *
27 *
28 * (Disconnected)
29 * | ^
30 * CONNECT | | DISCONNECTED
31 * V |
32 * (Connecting)<--->(Disconnecting)
33 * | ^
34 * CONNECTED | | DISCONNECT
35 * V |
36 * (Connected)
37 * NOTES:
38 * - If state machine is in "Connecting" state and the remote device sends
39 * DISCONNECT request, the state machine transitions to "Disconnecting" state.
40 * - Similarly, if the state machine is in "Disconnecting" state and the remote device
41 * sends CONNECT request, the state machine transitions to "Connecting" state.
42 *
43 * DISCONNECT
44 * (Connecting) ---------------> (Disconnecting)
45 * <---------------
46 * CONNECT
47 *
48 */
49
50 package com.android.bluetooth.a2dp;
51
52 import static android.Manifest.permission.BLUETOOTH_CONNECT;
53
54 import android.bluetooth.BluetoothA2dp;
55 import android.bluetooth.BluetoothCodecConfig;
56 import android.bluetooth.BluetoothCodecStatus;
57 import android.bluetooth.BluetoothDevice;
58 import android.bluetooth.BluetoothProfile;
59 import android.content.Intent;
60 import android.os.Looper;
61 import android.os.Message;
62 import android.util.Log;
63
64 import com.android.bluetooth.BluetoothStatsLog;
65 import com.android.bluetooth.Utils;
66 import com.android.bluetooth.btservice.ProfileService;
67 import com.android.bluetooth.apm.ApmConstIntf;
68 import com.android.bluetooth.apm.DeviceProfileMapIntf;
69 import com.android.bluetooth.apm.MediaAudioIntf;
70
71 import com.android.internal.annotations.VisibleForTesting;
72 import com.android.internal.util.State;
73 import com.android.internal.util.StateMachine;
74
75 import java.io.FileDescriptor;
76 import java.io.PrintWriter;
77 import java.io.StringWriter;
78 import java.util.List;
79 import java.util.Scanner;
80 import java.lang.reflect.Method;
81 import android.os.SystemProperties;
82 import com.android.bluetooth.btservice.AdapterService;
83
84 final class A2dpStateMachine extends StateMachine {
85 private static final boolean DBG = true;
86 private static final String TAG = "A2dpStateMachine";
87
88 static final int CONNECT = 1;
89 static final int DISCONNECT = 2;
90 @VisibleForTesting
91 static final int STACK_EVENT = 101;
92 private static final int CONNECT_TIMEOUT = 201;
93
94 // NOTE: the value is not "final" - it is modified in the unit tests
95 @VisibleForTesting
96 static int sConnectTimeoutMs = 30000; // 30s
97
98 private Disconnected mDisconnected;
99 private Connecting mConnecting;
100 private Disconnecting mDisconnecting;
101 private Connected mConnected;
102 private int mConnectionState = BluetoothProfile.STATE_DISCONNECTED;
103 private int mLastConnectionState = -1;
104
105 private A2dpService mA2dpService;
106 private A2dpNativeInterface mA2dpNativeInterface;
107 @VisibleForTesting
108 boolean mA2dpOffloadEnabled = false;
109 private final BluetoothDevice mDevice;
110 private boolean mIsPlaying = false;
111 private BluetoothCodecStatus mCodecStatus;
112 private boolean mCodecConfigUpdated = false;
113
114 A2dpStateMachine(BluetoothDevice device, A2dpService a2dpService,
115 A2dpNativeInterface a2dpNativeInterface, Looper looper) {
116 super(TAG, looper);
117 setDbg(DBG);
118 mDevice = device;
119 mCodecConfigUpdated = false;
120 mA2dpService = a2dpService;
121 mA2dpNativeInterface = a2dpNativeInterface;
122
123 mDisconnected = new Disconnected();
124 mConnecting = new Connecting();
125 mDisconnecting = new Disconnecting();
126 mConnected = new Connected();
127
128 addState(mDisconnected);
129 addState(mConnecting);
130 addState(mDisconnecting);
131 addState(mConnected);
132 mA2dpOffloadEnabled = mA2dpService.mA2dpOffloadEnabled;
133
134 setInitialState(mDisconnected);
135 }
136
137 static A2dpStateMachine make(BluetoothDevice device, A2dpService a2dpService,
138 A2dpNativeInterface a2dpNativeInterface, Looper looper) {
139 Log.i(TAG, "make for device " + device);
140 A2dpStateMachine a2dpSm = new A2dpStateMachine(device, a2dpService, a2dpNativeInterface,
141 looper);
142 a2dpSm.start();
143 return a2dpSm;
144 }
145
146 public void doQuit() {
147 log("doQuit for device " + mDevice);
148 if (mIsPlaying) {
149 // Stop if auido is still playing
150 log("doQuit: stopped playing " + mDevice);
151 mIsPlaying = false;
152 mA2dpService.setAvrcpAudioState(BluetoothA2dp.STATE_NOT_PLAYING, mDevice);
153 broadcastAudioState(BluetoothA2dp.STATE_NOT_PLAYING,
154 BluetoothA2dp.STATE_PLAYING);
155 }
156 quitNow();
157 }
158
159 public void cleanup() {
160 log("cleanup for device " + mDevice);
161 }
162
163 @VisibleForTesting
164 class Disconnected extends State {
165 @Override
166 public void enter() {
167 Message currentMessage = getCurrentMessage();
168 Log.i(TAG, "Enter Disconnected(" + mDevice + "): " + (currentMessage == null ? "null"
169 : messageWhatToString(currentMessage.what)));
170 mCodecConfigUpdated = false;
171 synchronized (this) {
172 mConnectionState = BluetoothProfile.STATE_DISCONNECTED;
173 }
174 removeDeferredMessages(DISCONNECT);
175
176 if (mLastConnectionState != -1) {
177 // Don't broadcast during startup
178 if (mIsPlaying) {
179 Log.i(TAG, "Disconnected: stopped playing: " + mDevice);
180 mIsPlaying = false;
181 mA2dpService.setAvrcpAudioState(BluetoothA2dp.STATE_NOT_PLAYING, mDevice);
182 broadcastAudioState(BluetoothA2dp.STATE_NOT_PLAYING,
183 BluetoothA2dp.STATE_PLAYING);
184 }
185 broadcastConnectionState(mConnectionState, mLastConnectionState);
186 AdapterService adapterService = AdapterService.getAdapterService();
187 if (adapterService.isVendorIntfEnabled() &&
188 adapterService.isTwsPlusDevice(mDevice)) {
189 mA2dpService.updateTwsChannelMode(BluetoothA2dp.STATE_NOT_PLAYING, mDevice);
190 }
191 }
192 }
193
194 @Override
195 public void exit() {
196 Message currentMessage = getCurrentMessage();
197 log("Exit Disconnected(" + mDevice + "): " + (currentMessage == null ? "null"
198 : messageWhatToString(currentMessage.what)));
199 mLastConnectionState = BluetoothProfile.STATE_DISCONNECTED;
200 }
201
202 @Override
203 public boolean processMessage(Message message) {
204 log("Disconnected process message(" + mDevice + "): "
205 + messageWhatToString(message.what));
206
207 switch (message.what) {
208 case CONNECT:
209 Log.i(TAG, "Connecting to " + mDevice);
210 if (!mA2dpNativeInterface.connectA2dp(mDevice)) {
211 Log.e(TAG, "Disconnected: error connecting to " + mDevice);
212 break;
213 }
214 if (mA2dpService.okToConnect(mDevice, true)) {
215 transitionTo(mConnecting);
216 } else {
217 // Reject the request and stay in Disconnected state
218 Log.w(TAG, "Outgoing A2DP Connecting request rejected: " + mDevice);
219 }
220 break;
221 case DISCONNECT:
222 Log.w(TAG, "Disconnected: DISCONNECT ignored: " + mDevice);
223 break;
224 case STACK_EVENT:
225 A2dpStackEvent event = (A2dpStackEvent) message.obj;
226 log("Disconnected: stack event: " + event);
227 if (!mDevice.equals(event.device)) {
228 Log.wtf(TAG, "Device(" + mDevice + "): event mismatch: " + event);
229 }
230 switch (event.type) {
231 case A2dpStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED:
232 processConnectionEvent(event.valueInt);
233 break;
234 case A2dpStackEvent.EVENT_TYPE_CODEC_CONFIG_CHANGED:
235 processCodecConfigEvent(event.codecStatus);
236 break;
237 default:
238 Log.e(TAG, "Disconnected: ignoring stack event: " + event);
239 break;
240 }
241 break;
242 default:
243 return NOT_HANDLED;
244 }
245 return HANDLED;
246 }
247
248 // in Disconnected state
249 private void processConnectionEvent(int event) {
250 switch (event) {
251 case A2dpStackEvent.CONNECTION_STATE_DISCONNECTED:
252 Log.w(TAG, "Ignore A2DP DISCONNECTED event: " + mDevice);
253 break;
254 case A2dpStackEvent.CONNECTION_STATE_CONNECTING:
255 if (mA2dpService.okToConnect(mDevice, false)) {
256 Log.i(TAG, "Incoming A2DP Connecting request accepted: " + mDevice);
257 transitionTo(mConnecting);
258 if (mA2dpService.isQtiLeAudioEnabled()) {
259 MediaAudioIntf mMediaAudio = MediaAudioIntf.get();
260 mMediaAudio.autoConnect(mDevice);
261 }
262 } else {
263 // Reject the connection and stay in Disconnected state itself
264 Log.w(TAG, "Incoming A2DP Connecting request rejected: " + mDevice);
265 mA2dpNativeInterface.disconnectA2dp(mDevice);
266 }
267 break;
268 case A2dpStackEvent.CONNECTION_STATE_CONNECTED:
269 Log.w(TAG, "A2DP Connected from Disconnected state: " + mDevice);
270 if (mA2dpService.okToConnect(mDevice, false)) {
271 Log.i(TAG, "Incoming A2DP Connected request accepted: " + mDevice);
272 transitionTo(mConnected);
273 if (mA2dpService.isQtiLeAudioEnabled()) {
274 MediaAudioIntf mMediaAudio = MediaAudioIntf.get();
275 mMediaAudio.autoConnect(mDevice);
276 }
277 } else {
278 // Reject the connection and stay in Disconnected state itself
279 Log.w(TAG, "Incoming A2DP Connected request rejected: " + mDevice);
280 mA2dpNativeInterface.disconnectA2dp(mDevice);
281 }
282 break;
283 case A2dpStackEvent.CONNECTION_STATE_DISCONNECTING:
284 Log.w(TAG, "Ignore A2DP DISCONNECTING event: " + mDevice);
285 break;
286 default:
287 Log.e(TAG, "Incorrect event: " + event + " device: " + mDevice);
288 break;
289 }
290 }
291 }
292
293 @VisibleForTesting
294 class Connecting extends State {
295 @Override
296 public void enter() {
297 Message currentMessage = getCurrentMessage();
298 Log.i(TAG, "Enter Connecting(" + mDevice + "): " + (currentMessage == null ? "null"
299 : messageWhatToString(currentMessage.what)));
300 sendMessageDelayed(CONNECT_TIMEOUT, sConnectTimeoutMs);
301 synchronized (this) {
302 mConnectionState = BluetoothProfile.STATE_CONNECTING;
303 }
304 broadcastConnectionState(mConnectionState, mLastConnectionState);
305 }
306
307 @Override
308 public void exit() {
309 Message currentMessage = getCurrentMessage();
310 log("Exit Connecting(" + mDevice + "): " + (currentMessage == null ? "null"
311 : messageWhatToString(currentMessage.what)));
312 mLastConnectionState = BluetoothProfile.STATE_CONNECTING;
313 removeMessages(CONNECT_TIMEOUT);
314 }
315
316 @Override
317 public boolean processMessage(Message message) {
318 log("Connecting process message(" + mDevice + "): "
319 + messageWhatToString(message.what));
320
321 switch (message.what) {
322 case CONNECT:
323 deferMessage(message);
324 break;
325 case CONNECT_TIMEOUT: {
326 Log.w(TAG, "Connecting connection timeout: " + mDevice);
327 mA2dpNativeInterface.disconnectA2dp(mDevice);
328 A2dpStackEvent event =
329 new A2dpStackEvent(A2dpStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
330 event.device = mDevice;
331 event.valueInt = A2dpStackEvent.CONNECTION_STATE_DISCONNECTED;
332 sendMessage(STACK_EVENT, event);
333 break;
334 }
335 case DISCONNECT:
336 // Cancel connection
337 Log.i(TAG, "Connecting: connection canceled to " + mDevice);
338 mA2dpNativeInterface.disconnectA2dp(mDevice);
339 transitionTo(mDisconnected);
340 break;
341 case STACK_EVENT:
342 A2dpStackEvent event = (A2dpStackEvent) message.obj;
343 log("Connecting: stack event: " + event);
344 if (!mDevice.equals(event.device)) {
345 Log.wtf(TAG, "Device(" + mDevice + "): event mismatch: " + event);
346 }
347 switch (event.type) {
348 case A2dpStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED:
349 processConnectionEvent(event.valueInt);
350 break;
351 case A2dpStackEvent.EVENT_TYPE_CODEC_CONFIG_CHANGED:
352 processCodecConfigEvent(event.codecStatus);
353 break;
354 case A2dpStackEvent.EVENT_TYPE_AUDIO_STATE_CHANGED:
355 break;
356 default:
357 Log.e(TAG, "Connecting: ignoring stack event: " + event);
358 break;
359 }
360 break;
361 default:
362 return NOT_HANDLED;
363 }
364 return HANDLED;
365 }
366
367 // in Connecting state
368 private void processConnectionEvent(int event) {
369 switch (event) {
370 case A2dpStackEvent.CONNECTION_STATE_DISCONNECTED:
371 Log.w(TAG, "Connecting device disconnected: " + mDevice);
372 transitionTo(mDisconnected);
373 break;
374 case A2dpStackEvent.CONNECTION_STATE_CONNECTED:
375 transitionTo(mConnected);
376 break;
377 case A2dpStackEvent.CONNECTION_STATE_CONNECTING:
378 // Ignored - probably an event that the outgoing connection was initiated
379 break;
380 case A2dpStackEvent.CONNECTION_STATE_DISCONNECTING:
381 Log.w(TAG, "Connecting interrupted: device is disconnecting: " + mDevice);
382 transitionTo(mDisconnecting);
383 break;
384 default:
385 Log.e(TAG, "Incorrect event: " + event);
386 break;
387 }
388 }
389 }
390
391 @VisibleForTesting
392 class Disconnecting extends State {
393 @Override
394 public void enter() {
395 Message currentMessage = getCurrentMessage();
396 Log.i(TAG, "Enter Disconnecting(" + mDevice + "): " + (currentMessage == null ? "null"
397 : messageWhatToString(currentMessage.what)));
398 mCodecConfigUpdated = false;
399 sendMessageDelayed(CONNECT_TIMEOUT, sConnectTimeoutMs);
400 synchronized (this) {
401 mConnectionState = BluetoothProfile.STATE_DISCONNECTING;
402 }
403 broadcastConnectionState(mConnectionState, mLastConnectionState);
404 }
405
406 @Override
407 public void exit() {
408 Message currentMessage = getCurrentMessage();
409 log("Exit Disconnecting(" + mDevice + "): " + (currentMessage == null ? "null"
410 : messageWhatToString(currentMessage.what)));
411 mLastConnectionState = BluetoothProfile.STATE_DISCONNECTING;
412 removeMessages(CONNECT_TIMEOUT);
413 }
414
415 @Override
416 public boolean processMessage(Message message) {
417 log("Disconnecting process message(" + mDevice + "): "
418 + messageWhatToString(message.what));
419
420 switch (message.what) {
421 case CONNECT:
422 deferMessage(message);
423 break;
424 case CONNECT_TIMEOUT: {
425 Log.w(TAG, "Disconnecting connection timeout: " + mDevice);
426 mA2dpNativeInterface.disconnectA2dp(mDevice);
427 A2dpStackEvent event =
428 new A2dpStackEvent(A2dpStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
429 event.device = mDevice;
430 event.valueInt = A2dpStackEvent.CONNECTION_STATE_DISCONNECTED;
431 sendMessage(STACK_EVENT, event);
432 break;
433 }
434 case DISCONNECT:
435 deferMessage(message);
436 break;
437 case STACK_EVENT:
438 A2dpStackEvent event = (A2dpStackEvent) message.obj;
439 log("Disconnecting: stack event: " + event);
440 if (!mDevice.equals(event.device)) {
441 Log.wtf(TAG, "Device(" + mDevice + "): event mismatch: " + event);
442 }
443 switch (event.type) {
444 case A2dpStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED:
445 processConnectionEvent(event.valueInt);
446 break;
447 case A2dpStackEvent.EVENT_TYPE_CODEC_CONFIG_CHANGED:
448 processCodecConfigEvent(event.codecStatus);
449 break;
450 case A2dpStackEvent.EVENT_TYPE_AUDIO_STATE_CHANGED:
451 default:
452 Log.e(TAG, "Disconnecting: ignoring stack event: " + event);
453 break;
454 }
455 break;
456 default:
457 return NOT_HANDLED;
458 }
459 return HANDLED;
460 }
461
462 // in Disconnecting state
463 private void processConnectionEvent(int event) {
464 switch (event) {
465 case A2dpStackEvent.CONNECTION_STATE_DISCONNECTED:
466 Log.i(TAG, "Disconnected: " + mDevice);
467 transitionTo(mDisconnected);
468 break;
469 case A2dpStackEvent.CONNECTION_STATE_CONNECTED:
470 if (mA2dpService.okToConnect(mDevice, false)) {
471 Log.w(TAG, "Disconnecting interrupted: device is connected: " + mDevice);
472 transitionTo(mConnected);
473 if (mA2dpService.isQtiLeAudioEnabled()) {
474 MediaAudioIntf mMediaAudio = MediaAudioIntf.get();
475 mMediaAudio.autoConnect(mDevice);
476 }
477 } else {
478 // Reject the connection and stay in Disconnecting state
479 Log.w(TAG, "Incoming A2DP Connected request rejected: " + mDevice);
480 mA2dpNativeInterface.disconnectA2dp(mDevice);
481 }
482 break;
483 case A2dpStackEvent.CONNECTION_STATE_CONNECTING:
484 if (mA2dpService.okToConnect(mDevice, false)) {
485 Log.i(TAG, "Disconnecting interrupted: try to reconnect: " + mDevice);
486 transitionTo(mConnecting);
487 if (mA2dpService.isQtiLeAudioEnabled()) {
488 MediaAudioIntf mMediaAudio = MediaAudioIntf.get();
489 mMediaAudio.autoConnect(mDevice);
490 }
491 } else {
492 // Reject the connection and stay in Disconnecting state
493 Log.w(TAG, "Incoming A2DP Connecting request rejected: " + mDevice);
494 mA2dpNativeInterface.disconnectA2dp(mDevice);
495 }
496 break;
497 case A2dpStackEvent.CONNECTION_STATE_DISCONNECTING:
498 // We are already disconnecting, do nothing
499 Log.i(TAG, " already disconnecting, do nothing ");
500 break;
501 default:
502 Log.e(TAG, "Incorrect event: " + event);
503 break;
504 }
505 }
506 }
507
508 @VisibleForTesting
509 class Connected extends State {
510 @Override
511 public void enter() {
512 Message currentMessage = getCurrentMessage();
513 Log.i(TAG, "Enter Connected(" + mDevice + "): " + (currentMessage == null ? "null"
514 : messageWhatToString(currentMessage.what)));
515 synchronized (this) {
516 mConnectionState = BluetoothProfile.STATE_CONNECTED;
517 }
518 removeDeferredMessages(CONNECT);
519 DeviceProfileMapIntf dpm = DeviceProfileMapIntf.getDeviceProfileMapInstance();
520 dpm.profileConnectionUpdate(mDevice, ApmConstIntf.AudioFeatures.MEDIA_AUDIO,
521 ApmConstIntf.AudioProfiles.A2DP, true);
522 // Each time a device connects, we want to re-check if it supports optional
523 // codecs (perhaps it's had a firmware update, etc.) and save that state if
524 // it differs from what we had saved before.
525 mA2dpService.updateOptionalCodecsSupport(mDevice);
526 broadcastConnectionState(mConnectionState, mLastConnectionState);
527 // Upon connected, the audio starts out as stopped
528 broadcastAudioState(BluetoothA2dp.STATE_NOT_PLAYING,
529 BluetoothA2dp.STATE_PLAYING);
530 }
531
532 @Override
533 public void exit() {
534 Message currentMessage = getCurrentMessage();
535 log("Exit Connected(" + mDevice + "): " + (currentMessage == null ? "null"
536 : messageWhatToString(currentMessage.what)));
537 mLastConnectionState = BluetoothProfile.STATE_CONNECTED;
538 }
539
540 @Override
541 public boolean processMessage(Message message) {
542 log("Connected process message(" + mDevice + "): " + messageWhatToString(message.what));
543
544 switch (message.what) {
545 case CONNECT:
546 Log.w(TAG, "Connected: CONNECT ignored: " + mDevice);
547 break;
548 case DISCONNECT: {
549 Log.i(TAG, "Disconnecting from " + mDevice);
550 if (!mA2dpNativeInterface.disconnectA2dp(mDevice)) {
551 // If error in the native stack, transition directly to Disconnected state.
552 Log.e(TAG, "Connected: error disconnecting from " + mDevice);
553 transitionTo(mDisconnected);
554 break;
555 }
556 transitionTo(mDisconnecting);
557 }
558 break;
559 case STACK_EVENT:
560 A2dpStackEvent event = (A2dpStackEvent) message.obj;
561 log("Connected: stack event: " + event);
562 if (!mDevice.equals(event.device)) {
563 Log.wtf(TAG, "Device(" + mDevice + "): event mismatch: " + event);
564 }
565 switch (event.type) {
566 case A2dpStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED:
567 processConnectionEvent(event.valueInt);
568 break;
569 case A2dpStackEvent.EVENT_TYPE_AUDIO_STATE_CHANGED:
570 processAudioStateEvent(event.valueInt);
571 break;
572 case A2dpStackEvent.EVENT_TYPE_CODEC_CONFIG_CHANGED:
573 processCodecConfigEvent(event.codecStatus);
574 break;
575 default:
576 Log.e(TAG, "Connected: ignoring stack event: " + event);
577 break;
578 }
579 break;
580 default:
581 return NOT_HANDLED;
582 }
583 return HANDLED;
584 }
585
586 // in Connected state
587 private void processConnectionEvent(int event) {
588 switch (event) {
589 case A2dpStackEvent.CONNECTION_STATE_DISCONNECTED:
590 Log.i(TAG, "Disconnected from " + mDevice);
591 transitionTo(mDisconnected);
592 break;
593 case A2dpStackEvent.CONNECTION_STATE_CONNECTED:
594 Log.w(TAG, "Ignore A2DP CONNECTED event: " + mDevice);
595 break;
596 case A2dpStackEvent.CONNECTION_STATE_CONNECTING:
597 Log.w(TAG, "Ignore A2DP CONNECTING event: " + mDevice);
598 break;
599 case A2dpStackEvent.CONNECTION_STATE_DISCONNECTING:
600 Log.i(TAG, "Disconnecting from " + mDevice);
601 transitionTo(mDisconnecting);
602 break;
603 default:
604 Log.e(TAG, "Connection State Device: " + mDevice + " bad event: " + event);
605 break;
606 }
607 }
608
609 // in Connected state
610 private void processAudioStateEvent(int state) {
611 Log.i(TAG, "Connected: processAudioStateEvent: state: " + state + "mIsPlaying: " + mIsPlaying);
612 switch (state) {
613 case A2dpStackEvent.AUDIO_STATE_STARTED:
614 synchronized (this) {
615 if (!mIsPlaying) {
616 Log.i(TAG, "Connected: started playing: " + mDevice);
617 mIsPlaying = true;
618 mA2dpService.setAvrcpAudioState(BluetoothA2dp.STATE_PLAYING, mDevice);
619 broadcastAudioState(BluetoothA2dp.STATE_PLAYING,
620 BluetoothA2dp.STATE_NOT_PLAYING);
621 Log.i(TAG,"state:AUDIO_STATE_STARTED");
622 }
623 AdapterService adapterService = AdapterService.getAdapterService();
624 if (adapterService.isVendorIntfEnabled() &&
625 adapterService.isTwsPlusDevice(mDevice)) {
626 mA2dpService.updateTwsChannelMode(BluetoothA2dp.STATE_PLAYING, mDevice);
627 }
628 }
629 break;
630 case A2dpStackEvent.AUDIO_STATE_REMOTE_SUSPEND:
631 case A2dpStackEvent.AUDIO_STATE_STOPPED:
632 synchronized (this) {
633 if (mIsPlaying) {
634 Log.i(TAG, "Connected: stopped playing: " + mDevice);
635 mIsPlaying = false;
636 mA2dpService.setAvrcpAudioState(BluetoothA2dp.STATE_NOT_PLAYING, mDevice);
637 broadcastAudioState(BluetoothA2dp.STATE_NOT_PLAYING,
638 BluetoothA2dp.STATE_PLAYING);
639 mA2dpService.setGamingMode(mDevice, false);
640 }
641 }
642 break;
643 default:
644 Log.e(TAG, "Audio State Device: " + mDevice + " bad state: " + state);
645 break;
646 }
647 }
648 }
649
650 int getConnectionState() {
651 return mConnectionState;
652 }
653
654 BluetoothDevice getDevice() {
655 return mDevice;
656 }
657
658 boolean isConnected() {
659 synchronized (this) {
660 return (mConnectionState == BluetoothProfile.STATE_CONNECTED);
661 }
662 }
663
664 boolean isPlaying() {
665 synchronized (this) {
666 return mIsPlaying;
667 }
668 }
669
670 BluetoothCodecStatus getCodecStatus() {
671 synchronized (this) {
672 return mCodecStatus;
673 }
674 }
675
676 // NOTE: This event is processed in any state
677 @VisibleForTesting
678 void processCodecConfigEvent(BluetoothCodecStatus newCodecStatus) {
679 BluetoothCodecConfig prevCodecConfig = null;
680 BluetoothCodecStatus prevCodecStatus = mCodecStatus;
681
682 int new_codec_type = newCodecStatus.getCodecConfig().getCodecType();
683
684 // Split A2dp will be enabled by default
685 boolean isSplitA2dpEnabled = true;
686 AdapterService adapterService = AdapterService.getAdapterService();
687 Object objStreamAudioService = null;
688
689 if (adapterService != null){
690 isSplitA2dpEnabled = adapterService.isSplitA2dpEnabled();
691 Log.v(TAG,"isSplitA2dpEnabled: " + isSplitA2dpEnabled);
692 } else {
693 Log.e(TAG,"adapterService is null");
694 }
695
696 Log.w(TAG,"processCodecConfigEvent: new_codec_type = " + new_codec_type);
697
698 if (isSplitA2dpEnabled) {
699 if (new_codec_type == BluetoothCodecConfig.SOURCE_QVA_CODEC_TYPE_MAX) {
700 if (adapterService.isVendorIntfEnabled() &&
701 adapterService.isTwsPlusDevice(mDevice)) {
702 Log.d(TAG,"TWSP device streaming,not calling reconfig");
703 mCodecStatus = newCodecStatus;
704 return;
705 }
706 mA2dpService.broadcastReconfigureA2dp();
707 Log.w(TAG,"Split A2dp enabled rcfg send to Audio for codec max");
708 return;
709 }
710 }
711 synchronized (this) {
712 if (mCodecStatus != null) {
713 prevCodecConfig = mCodecStatus.getCodecConfig();
714 }
715 mCodecStatus = newCodecStatus;
716 }
717 if (DBG) {
718 Log.d(TAG, "A2DP Codec Config: " + prevCodecConfig + "->"
719 + newCodecStatus.getCodecConfig());
720 for (BluetoothCodecConfig codecConfig :
721 newCodecStatus.getCodecsLocalCapabilities()) {
722 Log.d(TAG, "A2DP Codec Local Capability: " + codecConfig);
723 }
724 for (BluetoothCodecConfig codecConfig :
725 newCodecStatus.getCodecsSelectableCapabilities()) {
726 Log.d(TAG, "A2DP Codec Selectable Capability: " + codecConfig);
727 }
728 }
729
730 if (isConnected() && !sameSelectableCodec(prevCodecStatus, mCodecStatus)) {
731 // Remote selectable codec could be changed if codec config changed
732 // in connected state, we need to re-check optional codec status
733 // for this codec change event.
734 mA2dpService.updateOptionalCodecsSupport(mDevice);
735 }
736 Log.d(TAG, " mA2dpOffloadEnabled: " + mA2dpOffloadEnabled);
737 if (mA2dpOffloadEnabled) {
738 boolean update = false;
739 BluetoothCodecConfig newCodecConfig = mCodecStatus.getCodecConfig();
740 if ((prevCodecConfig != null)
741 && (prevCodecConfig.getCodecType() != newCodecConfig.getCodecType())) {
742 Log.d(TAG, " previous codec is different from new codec ");
743 update = true;
744 } else if (!newCodecConfig.sameAudioFeedingParameters(prevCodecConfig)) {
745 Log.d(TAG, " codec config parameters mismatched from previous config ");
746 update = true;
747 } else if ((newCodecConfig.getCodecType()
748 == BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC)
749 && (prevCodecConfig != null)
750 && (prevCodecConfig.getCodecSpecific1()
751 != newCodecConfig.getCodecSpecific1())) {
752 Log.d(TAG, "LDAC: codec config parameters mismatched from previous config ");
753 update = true;
754 } else if (prevCodecStatus != null &&
755 newCodecStatus.getCodecsSelectableCapabilities().size() !=
756 prevCodecStatus.getCodecsSelectableCapabilities().size()){
757 Log.d(TAG, " codec selectable caps mismatched from previous config ");
758 update = true;
759 } else if (!mCodecConfigUpdated) {
760 Log.d(TAG, " mCodecConfigUpdated is false, codecConfigUpdated is required");
761 update = true;
762 }
763
764 if ((newCodecConfig.getCodecType()
765 == BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX_ADAPTIVE)) {
766 Log.d(TAG, "processCodecConfigEvent, APTX ADAPTIVE: reset Low Latency mode ");
767 try {
768 Class streamAudioService = Class.forName("com.android.bluetooth.apm.StreamAudioService");
769 Method method = streamAudioService.getDeclaredMethod("getStreamAudioService");
770 objStreamAudioService = method.invoke(null);
771 if (objStreamAudioService != null) {
772 Log.d(TAG, " processCodecConfigEvent, objStreamAudioService not null:");
773 } else {
774 Log.d(TAG, " processCodecConfigEvent, objStreamAudioService is null:");
775 }
776 } catch (Exception ex) {
777 Log.w(TAG, ex);
778 }
779 try {
780 Class streamAudioService =
781 Class.forName("com.android.bluetooth.apm.StreamAudioService");
782 Method method = streamAudioService.getDeclaredMethod("resetLowLatencyMode");
783 if (objStreamAudioService != null) {
784 Log.d(TAG, " processCodecConfigEvent, invoke resetLowLatencyMode ");
785 method.invoke(objStreamAudioService);
786 }
787 } catch (Exception ex) {
788 Log.w(TAG, ex);
789 }
790 }
791 Log.d(TAG, " update: " + update);
792 if (update) {
793 mA2dpService.codecConfigUpdated(mDevice, mCodecStatus, false);
794 mCodecConfigUpdated = true;
795 }
796 return;
797 }
798
799 Log.d(TAG, " isSplitA2dpEnabled: " + isSplitA2dpEnabled);
800 if (!isSplitA2dpEnabled) {
801 boolean isUpdateRequired = false;
802 if ((prevCodecConfig != null) && (prevCodecConfig.getCodecType() != new_codec_type)) {
803 Log.d(TAG, "previous codec is differs from new codec");
804 isUpdateRequired = true;
805 } else if (!newCodecStatus.getCodecConfig().sameAudioFeedingParameters(prevCodecConfig)) {
806 Log.d(TAG, "codec config parameters mismatched with previous config: ");
807 isUpdateRequired = true;
808 } else if ((newCodecStatus.getCodecConfig().getCodecType()
809 == BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC)
810 && (prevCodecConfig != null)
811 && (prevCodecConfig.getCodecSpecific1()
812 != newCodecStatus.getCodecConfig().getCodecSpecific1())) {
813 Log.d(TAG, "LDAC: codec config parameters mismatched with previous config: ");
814 isUpdateRequired = true;
815 } else if(!mCodecConfigUpdated) {
816 Log.d(TAG, " mCodecConfigUpdated is false, codecConfigUpdated is required ");
817 isUpdateRequired = true;
818 }
819 Log.d(TAG, "isUpdateRequired: " + isUpdateRequired);
820 //update MM only when previous and current codec config has been changed
821 // OR reconnection has happened
822 if (isUpdateRequired) {
823 mA2dpService.codecConfigUpdated(mDevice, mCodecStatus, false);
824 mCodecConfigUpdated = true;
825 }
826 }
827 }
828
829 // This method does not check for error conditon (newState == prevState)
830 private void broadcastConnectionState(int newState, int prevState) {
831 log("Connection state " + mDevice + ": " + profileStateToString(prevState)
832 + "->" + profileStateToString(newState));
833
834 if(mA2dpService.isQtiLeAudioEnabled() || ApmConstIntf.getAospLeaEnabled()) {
835 mA2dpService.updateConnState(mDevice, newState);
836 }
837
838 if (!mA2dpService.isQtiLeAudioEnabled()) {
839 Intent intent = new Intent(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED);
840 intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, prevState);
841 intent.putExtra(BluetoothProfile.EXTRA_STATE, newState);
842 intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mDevice);
843 intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT
844 | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
845 mA2dpService.sendBroadcast(intent, BLUETOOTH_CONNECT,
846 Utils.getTempAllowlistBroadcastOptions());
847 }
848 }
849
850 private void broadcastAudioState(int newState, int prevState) {
851 log("A2DP Playing state : device: " + mDevice + " State:" + audioStateToString(prevState)
852 + "->" + audioStateToString(newState));
853
854 mA2dpService.updateStreamState(mDevice, newState);
855
856 if(mA2dpService.isQtiLeAudioEnabled()) {
857 return;
858 }
859
860 BluetoothStatsLog.write(BluetoothStatsLog.BLUETOOTH_A2DP_PLAYBACK_STATE_CHANGED, newState);
861 Intent intent = new Intent(BluetoothA2dp.ACTION_PLAYING_STATE_CHANGED);
862 intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mDevice);
863 intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, prevState);
864 intent.putExtra(BluetoothProfile.EXTRA_STATE, newState);
865 intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
866 mA2dpService.sendBroadcast(intent, BLUETOOTH_CONNECT,
867 Utils.getTempAllowlistBroadcastOptions());
868 }
869
870 @Override
871 protected String getLogRecString(Message msg) {
872 StringBuilder builder = new StringBuilder();
873 builder.append(messageWhatToString(msg.what));
874 builder.append(": ");
875 builder.append("arg1=")
876 .append(msg.arg1)
877 .append(", arg2=")
878 .append(msg.arg2)
879 .append(", obj=")
880 .append(msg.obj);
881 return builder.toString();
882 }
883
884 private static boolean sameSelectableCodec(BluetoothCodecStatus prevCodecStatus,
885 BluetoothCodecStatus newCodecStatus) {
886 if (prevCodecStatus == null || newCodecStatus == null) {
887 return false;
888 }
889 List<BluetoothCodecConfig> c1 = prevCodecStatus.getCodecsSelectableCapabilities();
890 List<BluetoothCodecConfig> c2 = newCodecStatus.getCodecsSelectableCapabilities();
891 if (c1 == null) {
892 return (c2 == null);
893 }
894 if (c2 == null) {
895 return false;
896 }
897 if (c1.size() != c2.size()) {
898 return false;
899 }
900 return c1.containsAll(c2);
901 }
902
903 private static String messageWhatToString(int what) {
904 switch (what) {
905 case CONNECT:
906 return "CONNECT";
907 case DISCONNECT:
908 return "DISCONNECT";
909 case STACK_EVENT:
910 return "STACK_EVENT";
911 case CONNECT_TIMEOUT:
912 return "CONNECT_TIMEOUT";
913 default:
914 break;
915 }
916 return Integer.toString(what);
917 }
918
919 private static String profileStateToString(int state) {
920 switch (state) {
921 case BluetoothProfile.STATE_DISCONNECTED:
922 return "DISCONNECTED";
923 case BluetoothProfile.STATE_CONNECTING:
924 return "CONNECTING";
925 case BluetoothProfile.STATE_CONNECTED:
926 return "CONNECTED";
927 case BluetoothProfile.STATE_DISCONNECTING:
928 return "DISCONNECTING";
929 default:
930 break;
931 }
932 return Integer.toString(state);
933 }
934
935 private static String audioStateToString(int state) {
936 switch (state) {
937 case BluetoothA2dp.STATE_PLAYING:
938 return "PLAYING";
939 case BluetoothA2dp.STATE_NOT_PLAYING:
940 return "NOT_PLAYING";
941 default:
942 break;
943 }
944 return Integer.toString(state);
945 }
946
947 public void dump(StringBuilder sb) {
948 ProfileService.println(sb, "mDevice: " + mDevice);
949 ProfileService.println(sb, " StateMachine: " + this.toString());
950 ProfileService.println(sb, " mIsPlaying: " + mIsPlaying);
951 synchronized (this) {
952 if (mCodecStatus != null) {
953 ProfileService.println(sb, " mCodecConfig: " + mCodecStatus.getCodecConfig());
954 }
955 }
956 // Dump the state machine logs
957 StringWriter stringWriter = new StringWriter();
958 PrintWriter printWriter = new PrintWriter(stringWriter);
959 super.dump(new FileDescriptor(), printWriter, new String[]{});
960 printWriter.flush();
961 stringWriter.flush();
962 ProfileService.println(sb, " StateMachineLog:");
963 Scanner scanner = new Scanner(stringWriter.toString());
964 while (scanner.hasNextLine()) {
965 String line = scanner.nextLine();
966 ProfileService.println(sb, " " + line);
967 }
968 scanner.close();
969 }
970
971 @Override
972 protected void log(String msg) {
973 if (DBG) {
974 super.log(msg);
975 }
976 }
977 }