• 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
19  *                      (Disconnected)
20  *                           |    ^
21  *                   CONNECT |    | DISCONNECTED
22  *                           V    |
23  *                         (Pending)
24  *                           |    ^
25  *                 CONNECTED |    | CONNECT
26  *                           V    |
27  *                        (Connected)
28  */
29 package com.android.bluetooth.a2dp;
30 
31 import android.bluetooth.BluetoothA2dp;
32 import android.bluetooth.BluetoothAdapter;
33 import android.bluetooth.BluetoothCodecConfig;
34 import android.bluetooth.BluetoothCodecStatus;
35 import android.bluetooth.BluetoothDevice;
36 import android.bluetooth.BluetoothProfile;
37 import android.bluetooth.BluetoothUuid;
38 import android.content.Context;
39 import android.content.Intent;
40 import android.content.res.Resources;
41 import android.content.res.Resources.NotFoundException;
42 import android.media.AudioManager;
43 import android.os.Handler;
44 import android.os.Message;
45 import android.os.ParcelUuid;
46 import android.os.PowerManager;
47 import android.os.PowerManager.WakeLock;
48 import android.util.Log;
49 
50 import com.android.bluetooth.R;
51 import com.android.bluetooth.Utils;
52 import com.android.bluetooth.btservice.AdapterService;
53 import com.android.bluetooth.btservice.ProfileService;
54 import com.android.internal.util.IState;
55 import com.android.internal.util.State;
56 import com.android.internal.util.StateMachine;
57 
58 import java.util.ArrayList;
59 import java.util.List;
60 import java.util.Set;
61 
62 final class A2dpStateMachine extends StateMachine {
63     private static final boolean DBG = false;
64     private static final String TAG = "A2dpStateMachine";
65 
66     static final int CONNECT = 1;
67     static final int DISCONNECT = 2;
68     private static final int STACK_EVENT = 101;
69     private static final int CONNECT_TIMEOUT = 201;
70 
71     private Disconnected mDisconnected;
72     private Pending mPending;
73     private Connected mConnected;
74 
75     private A2dpService mService;
76     private Context mContext;
77     private BluetoothAdapter mAdapter;
78     private final AudioManager mAudioManager;
79     private IntentBroadcastHandler mIntentBroadcastHandler;
80     private final WakeLock mWakeLock;
81     private BluetoothCodecConfig[] mCodecConfigPriorities;
82 
83     private static final int MSG_CONNECTION_STATE_CHANGED = 0;
84 
85     // mCurrentDevice is the device connected before the state changes
86     // mTargetDevice is the device to be connected
87     // mIncomingDevice is the device connecting to us, valid only in Pending state
88     //                when mIncomingDevice is not null, both mCurrentDevice
89     //                  and mTargetDevice are null
90     //                when either mCurrentDevice or mTargetDevice is not null,
91     //                  mIncomingDevice is null
92     // Stable states
93     //   No connection, Disconnected state
94     //                  both mCurrentDevice and mTargetDevice are null
95     //   Connected, Connected state
96     //              mCurrentDevice is not null, mTargetDevice is null
97     // Interim states
98     //   Connecting to a device, Pending
99     //                           mCurrentDevice is null, mTargetDevice is not null
100     //   Disconnecting device, Connecting to new device
101     //     Pending
102     //     Both mCurrentDevice and mTargetDevice are not null
103     //   Disconnecting device Pending
104     //                        mCurrentDevice is not null, mTargetDevice is null
105     //   Incoming connections Pending
106     //                        Both mCurrentDevice and mTargetDevice are null
107     private BluetoothDevice mCurrentDevice = null;
108     private BluetoothDevice mTargetDevice = null;
109     private BluetoothDevice mIncomingDevice = null;
110     private BluetoothDevice mPlayingA2dpDevice = null;
111 
112     private BluetoothCodecStatus mCodecStatus = null;
113     private int mA2dpSourceCodecPrioritySbc = BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT;
114     private int mA2dpSourceCodecPriorityAac = BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT;
115     private int mA2dpSourceCodecPriorityAptx = BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT;
116     private int mA2dpSourceCodecPriorityAptxHd = BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT;
117     private int mA2dpSourceCodecPriorityLdac = BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT;
118 
119     static {
classInitNative()120         classInitNative();
121     }
122 
A2dpStateMachine(A2dpService svc, Context context)123     private A2dpStateMachine(A2dpService svc, Context context) {
124         super("A2dpStateMachine");
125         mService = svc;
126         mContext = context;
127         mAdapter = BluetoothAdapter.getDefaultAdapter();
128         mCodecConfigPriorities = assignCodecConfigPriorities();
129 
130         initNative(mCodecConfigPriorities);
131 
132         mDisconnected = new Disconnected();
133         mPending = new Pending();
134         mConnected = new Connected();
135 
136         addState(mDisconnected);
137         addState(mPending);
138         addState(mConnected);
139 
140         setInitialState(mDisconnected);
141 
142         PowerManager pm = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
143         mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "BluetoothA2dpService");
144 
145         mIntentBroadcastHandler = new IntentBroadcastHandler();
146 
147         mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
148     }
149 
make(A2dpService svc, Context context)150     static A2dpStateMachine make(A2dpService svc, Context context) {
151         Log.d(TAG, "make");
152         A2dpStateMachine a2dpSm = new A2dpStateMachine(svc, context);
153         a2dpSm.start();
154         return a2dpSm;
155     }
156 
157     // Assign the A2DP Source codec config priorities
assignCodecConfigPriorities()158     private BluetoothCodecConfig[] assignCodecConfigPriorities() {
159         Resources resources = mContext.getResources();
160         if (resources == null) {
161             return null;
162         }
163 
164         int value;
165         try {
166             value = resources.getInteger(R.integer.a2dp_source_codec_priority_sbc);
167         } catch (NotFoundException e) {
168             value = BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT;
169         }
170         if ((value >= BluetoothCodecConfig.CODEC_PRIORITY_DISABLED)
171                 && (value < BluetoothCodecConfig.CODEC_PRIORITY_HIGHEST)) {
172             mA2dpSourceCodecPrioritySbc = value;
173         }
174 
175         try {
176             value = resources.getInteger(R.integer.a2dp_source_codec_priority_aac);
177         } catch (NotFoundException e) {
178             value = BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT;
179         }
180         if ((value >= BluetoothCodecConfig.CODEC_PRIORITY_DISABLED)
181                 && (value < BluetoothCodecConfig.CODEC_PRIORITY_HIGHEST)) {
182             mA2dpSourceCodecPriorityAac = value;
183         }
184 
185         try {
186             value = resources.getInteger(R.integer.a2dp_source_codec_priority_aptx);
187         } catch (NotFoundException e) {
188             value = BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT;
189         }
190         if ((value >= BluetoothCodecConfig.CODEC_PRIORITY_DISABLED)
191                 && (value < BluetoothCodecConfig.CODEC_PRIORITY_HIGHEST)) {
192             mA2dpSourceCodecPriorityAptx = value;
193         }
194 
195         try {
196             value = resources.getInteger(R.integer.a2dp_source_codec_priority_aptx_hd);
197         } catch (NotFoundException e) {
198             value = BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT;
199         }
200         if ((value >= BluetoothCodecConfig.CODEC_PRIORITY_DISABLED)
201                 && (value < BluetoothCodecConfig.CODEC_PRIORITY_HIGHEST)) {
202             mA2dpSourceCodecPriorityAptxHd = value;
203         }
204 
205         try {
206             value = resources.getInteger(R.integer.a2dp_source_codec_priority_ldac);
207         } catch (NotFoundException e) {
208             value = BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT;
209         }
210         if ((value >= BluetoothCodecConfig.CODEC_PRIORITY_DISABLED)
211                 && (value < BluetoothCodecConfig.CODEC_PRIORITY_HIGHEST)) {
212             mA2dpSourceCodecPriorityLdac = value;
213         }
214 
215         BluetoothCodecConfig codecConfig;
216         BluetoothCodecConfig[] codecConfigArray =
217                 new BluetoothCodecConfig[BluetoothCodecConfig.SOURCE_CODEC_TYPE_MAX];
218         codecConfig = new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
219                 mA2dpSourceCodecPrioritySbc, BluetoothCodecConfig.SAMPLE_RATE_NONE,
220                 BluetoothCodecConfig.BITS_PER_SAMPLE_NONE, BluetoothCodecConfig.CHANNEL_MODE_NONE,
221                 0 /* codecSpecific1 */, 0 /* codecSpecific2 */, 0 /* codecSpecific3 */,
222                 0 /* codecSpecific4 */);
223         codecConfigArray[0] = codecConfig;
224         codecConfig = new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC,
225                 mA2dpSourceCodecPriorityAac, BluetoothCodecConfig.SAMPLE_RATE_NONE,
226                 BluetoothCodecConfig.BITS_PER_SAMPLE_NONE, BluetoothCodecConfig.CHANNEL_MODE_NONE,
227                 0 /* codecSpecific1 */, 0 /* codecSpecific2 */, 0 /* codecSpecific3 */,
228                 0 /* codecSpecific4 */);
229         codecConfigArray[1] = codecConfig;
230         codecConfig = new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX,
231                 mA2dpSourceCodecPriorityAptx, BluetoothCodecConfig.SAMPLE_RATE_NONE,
232                 BluetoothCodecConfig.BITS_PER_SAMPLE_NONE, BluetoothCodecConfig.CHANNEL_MODE_NONE,
233                 0 /* codecSpecific1 */, 0 /* codecSpecific2 */, 0 /* codecSpecific3 */,
234                 0 /* codecSpecific4 */);
235         codecConfigArray[2] = codecConfig;
236         codecConfig = new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX_HD,
237                 mA2dpSourceCodecPriorityAptxHd, BluetoothCodecConfig.SAMPLE_RATE_NONE,
238                 BluetoothCodecConfig.BITS_PER_SAMPLE_NONE, BluetoothCodecConfig.CHANNEL_MODE_NONE,
239                 0 /* codecSpecific1 */, 0 /* codecSpecific2 */, 0 /* codecSpecific3 */,
240                 0 /* codecSpecific4 */);
241         codecConfigArray[3] = codecConfig;
242         codecConfig = new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
243                 mA2dpSourceCodecPriorityLdac, BluetoothCodecConfig.SAMPLE_RATE_NONE,
244                 BluetoothCodecConfig.BITS_PER_SAMPLE_NONE, BluetoothCodecConfig.CHANNEL_MODE_NONE,
245                 0 /* codecSpecific1 */, 0 /* codecSpecific2 */, 0 /* codecSpecific3 */,
246                 0 /* codecSpecific4 */);
247         codecConfigArray[4] = codecConfig;
248 
249         return codecConfigArray;
250     }
251 
doQuit()252     public void doQuit() {
253         if ((mTargetDevice != null) &&
254             (getConnectionState(mTargetDevice) == BluetoothProfile.STATE_CONNECTING)) {
255             log("doQuit()- Move A2DP State to DISCONNECTED");
256             broadcastConnectionState(mTargetDevice, BluetoothProfile.STATE_DISCONNECTED,
257                                      BluetoothProfile.STATE_CONNECTING);
258         }
259         quitNow();
260     }
261 
cleanup()262     public void cleanup() {
263         cleanupNative();
264     }
265 
266         private class Disconnected extends State {
267         @Override
enter()268         public void enter() {
269             log("Enter Disconnected: " + getCurrentMessage().what);
270             if (mCurrentDevice != null || mTargetDevice != null || mIncomingDevice != null) {
271                 loge("ERROR: enter() inconsistent state in Disconnected: current = "
272                         + mCurrentDevice + " target = " + mTargetDevice + " incoming = "
273                         + mIncomingDevice);
274             }
275         }
276 
277         @Override
processMessage(Message message)278         public boolean processMessage(Message message) {
279             log("Disconnected process message: " + message.what);
280             if (mCurrentDevice != null || mTargetDevice != null  || mIncomingDevice != null) {
281                 loge("ERROR: not null state in Disconnected: current = " + mCurrentDevice
282                         + " target = " + mTargetDevice + " incoming = " + mIncomingDevice);
283                 mCurrentDevice = null;
284                 mTargetDevice = null;
285                 mIncomingDevice = null;
286             }
287 
288             boolean retValue = HANDLED;
289             switch(message.what) {
290                 case CONNECT:
291                     BluetoothDevice device = (BluetoothDevice) message.obj;
292                     broadcastConnectionState(device, BluetoothProfile.STATE_CONNECTING,
293                                    BluetoothProfile.STATE_DISCONNECTED);
294 
295                     if (!connectA2dpNative(getByteAddress(device)) ) {
296                         broadcastConnectionState(device, BluetoothProfile.STATE_DISCONNECTED,
297                                        BluetoothProfile.STATE_CONNECTING);
298                         break;
299                     }
300 
301                     synchronized (A2dpStateMachine.this) {
302                         mTargetDevice = device;
303                         transitionTo(mPending);
304                     }
305                     // TODO(BT) remove CONNECT_TIMEOUT when the stack
306                     //          sends back events consistently
307                     sendMessageDelayed(CONNECT_TIMEOUT, 30000);
308                     break;
309                 case DISCONNECT:
310                     // ignore
311                     break;
312                 case STACK_EVENT:
313                     StackEvent event = (StackEvent) message.obj;
314                     switch (event.type) {
315                         case EVENT_TYPE_CONNECTION_STATE_CHANGED:
316                             processConnectionEvent(event.valueInt, event.device);
317                             break;
318                         default:
319                             loge("Unexpected stack event: " + event.type);
320                             break;
321                     }
322                     break;
323                 default:
324                     return NOT_HANDLED;
325             }
326             return retValue;
327         }
328 
329         @Override
exit()330         public void exit() {
331             log("Exit Disconnected: " + getCurrentMessage().what);
332         }
333 
334         // in Disconnected state
processConnectionEvent(int state, BluetoothDevice device)335         private void processConnectionEvent(int state, BluetoothDevice device) {
336             switch (state) {
337             case CONNECTION_STATE_DISCONNECTED:
338                 logw("Ignore HF DISCONNECTED event, device: " + device);
339                 break;
340             case CONNECTION_STATE_CONNECTING:
341                 if (okToConnect(device)){
342                     logi("Incoming A2DP accepted");
343                     broadcastConnectionState(device, BluetoothProfile.STATE_CONNECTING,
344                                              BluetoothProfile.STATE_DISCONNECTED);
345                     synchronized (A2dpStateMachine.this) {
346                         mIncomingDevice = device;
347                         transitionTo(mPending);
348                     }
349                 } else {
350                     //reject the connection and stay in Disconnected state itself
351                     logi("Incoming A2DP rejected");
352                     disconnectA2dpNative(getByteAddress(device));
353                 }
354                 break;
355             case CONNECTION_STATE_CONNECTED:
356                 logw("A2DP Connected from Disconnected state");
357                 if (okToConnect(device)){
358                     logi("Incoming A2DP accepted");
359                     broadcastConnectionState(device, BluetoothProfile.STATE_CONNECTED,
360                                              BluetoothProfile.STATE_DISCONNECTED);
361                     synchronized (A2dpStateMachine.this) {
362                         mCurrentDevice = device;
363                         transitionTo(mConnected);
364                     }
365                 } else {
366                     //reject the connection and stay in Disconnected state itself
367                     logi("Incoming A2DP rejected");
368                     disconnectA2dpNative(getByteAddress(device));
369                 }
370                 break;
371             case CONNECTION_STATE_DISCONNECTING:
372                 logw("Ignore A2dp DISCONNECTING event, device: " + device);
373                 break;
374             default:
375                 loge("Incorrect state: " + state);
376                 break;
377             }
378         }
379     }
380 
381     private class Pending extends State {
382         @Override
enter()383         public void enter() {
384             log("Enter Pending: " + getCurrentMessage().what);
385             if (mTargetDevice != null && mIncomingDevice != null) {
386                 loge("ERROR: enter() inconsistent state in Pending: current = " + mCurrentDevice
387                         + " target = " + mTargetDevice + " incoming = " + mIncomingDevice);
388             }
389         }
390 
391         @Override
processMessage(Message message)392         public boolean processMessage(Message message) {
393             log("Pending process message: " + message.what);
394 
395             boolean retValue = HANDLED;
396             switch(message.what) {
397                 case CONNECT:
398                     deferMessage(message);
399                     break;
400                 case CONNECT_TIMEOUT:
401                     onConnectionStateChanged(CONNECTION_STATE_DISCONNECTED,
402                                              getByteAddress(mTargetDevice));
403                     break;
404                 case DISCONNECT:
405                     BluetoothDevice device = (BluetoothDevice) message.obj;
406                     if (mCurrentDevice != null && mTargetDevice != null &&
407                         mTargetDevice.equals(device) ) {
408                         // cancel connection to the mTargetDevice
409                         broadcastConnectionState(device, BluetoothProfile.STATE_DISCONNECTED,
410                                        BluetoothProfile.STATE_CONNECTING);
411                         synchronized (A2dpStateMachine.this) {
412                             mTargetDevice = null;
413                         }
414                     } else {
415                         deferMessage(message);
416                     }
417                     break;
418                 case STACK_EVENT:
419                     StackEvent event = (StackEvent) message.obj;
420                     switch (event.type) {
421                         case EVENT_TYPE_CONNECTION_STATE_CHANGED:
422                             removeMessages(CONNECT_TIMEOUT);
423                             processConnectionEvent(event.valueInt, event.device);
424                             break;
425                         default:
426                             loge("Unexpected stack event: " + event.type);
427                             break;
428                     }
429                     break;
430                 default:
431                     return NOT_HANDLED;
432             }
433             return retValue;
434         }
435 
436         // in Pending state
processConnectionEvent(int state, BluetoothDevice device)437         private void processConnectionEvent(int state, BluetoothDevice device) {
438             switch (state) {
439                 case CONNECTION_STATE_DISCONNECTED:
440                     if ((mCurrentDevice != null) && mCurrentDevice.equals(device)) {
441                         broadcastConnectionState(mCurrentDevice,
442                                                  BluetoothProfile.STATE_DISCONNECTED,
443                                                  BluetoothProfile.STATE_DISCONNECTING);
444                         synchronized (A2dpStateMachine.this) {
445                             mCurrentDevice = null;
446                         }
447 
448                         if (mTargetDevice != null) {
449                             if (!connectA2dpNative(getByteAddress(mTargetDevice))) {
450                                 broadcastConnectionState(mTargetDevice,
451                                                          BluetoothProfile.STATE_DISCONNECTED,
452                                                          BluetoothProfile.STATE_CONNECTING);
453                                 synchronized (A2dpStateMachine.this) {
454                                     mTargetDevice = null;
455                                     transitionTo(mDisconnected);
456                                 }
457                             }
458                         } else {
459                             synchronized (A2dpStateMachine.this) {
460                                 mIncomingDevice = null;
461                                 transitionTo(mDisconnected);
462                             }
463                         }
464                     } else if (mTargetDevice != null && mTargetDevice.equals(device)) {
465                         // outgoing connection failed
466                         broadcastConnectionState(mTargetDevice, BluetoothProfile.STATE_DISCONNECTED,
467                                                  BluetoothProfile.STATE_CONNECTING);
468                         // check if there is some incoming connection request
469                         if (mIncomingDevice != null) {
470                             logi("disconnect for outgoing in pending state");
471                             synchronized (A2dpStateMachine.this) {
472                                 mTargetDevice = null;
473                             }
474                             break;
475                         }
476                         synchronized (A2dpStateMachine.this) {
477                             mTargetDevice = null;
478                             transitionTo(mDisconnected);
479                         }
480                     } else if (mIncomingDevice != null && mIncomingDevice.equals(device)) {
481                         broadcastConnectionState(mIncomingDevice,
482                                                  BluetoothProfile.STATE_DISCONNECTED,
483                                                  BluetoothProfile.STATE_CONNECTING);
484                         synchronized (A2dpStateMachine.this) {
485                             mIncomingDevice = null;
486                             transitionTo(mDisconnected);
487                         }
488                     } else {
489                         loge("Unknown device Disconnected: " + device);
490                     }
491                     break;
492             case CONNECTION_STATE_CONNECTED:
493                 if ((mCurrentDevice != null) && mCurrentDevice.equals(device)) {
494                     // disconnection failed
495                     broadcastConnectionState(mCurrentDevice, BluetoothProfile.STATE_CONNECTED,
496                                              BluetoothProfile.STATE_DISCONNECTING);
497                     if (mTargetDevice != null) {
498                         broadcastConnectionState(mTargetDevice, BluetoothProfile.STATE_DISCONNECTED,
499                                                  BluetoothProfile.STATE_CONNECTING);
500                     }
501                     synchronized (A2dpStateMachine.this) {
502                         mTargetDevice = null;
503                         transitionTo(mConnected);
504                     }
505                 } else if (mTargetDevice != null && mTargetDevice.equals(device)) {
506                     broadcastConnectionState(mTargetDevice, BluetoothProfile.STATE_CONNECTED,
507                                              BluetoothProfile.STATE_CONNECTING);
508                     synchronized (A2dpStateMachine.this) {
509                         mCurrentDevice = mTargetDevice;
510                         mTargetDevice = null;
511                         transitionTo(mConnected);
512                     }
513                 } else if (mIncomingDevice != null && mIncomingDevice.equals(device)) {
514                     broadcastConnectionState(mIncomingDevice, BluetoothProfile.STATE_CONNECTED,
515                                              BluetoothProfile.STATE_CONNECTING);
516                     // check for a2dp connection allowed for this device in race condition
517                     if (okToConnect(mIncomingDevice)) {
518                         logi("Ready to connect incoming Connection from pending state");
519                         synchronized (A2dpStateMachine.this) {
520                             mCurrentDevice = mIncomingDevice;
521                             mIncomingDevice = null;
522                             transitionTo(mConnected);
523                         }
524                     } else {
525                         // A2dp connection unchecked for this device
526                         loge("Incoming A2DP rejected from pending state");
527                         disconnectA2dpNative(getByteAddress(device));
528                     }
529                 } else {
530                     loge("Unknown device Connected: " + device);
531                     // something is wrong here, but sync our state with stack
532                     broadcastConnectionState(device, BluetoothProfile.STATE_CONNECTED,
533                                              BluetoothProfile.STATE_DISCONNECTED);
534                     synchronized (A2dpStateMachine.this) {
535                         mCurrentDevice = device;
536                         mTargetDevice = null;
537                         mIncomingDevice = null;
538                         transitionTo(mConnected);
539                     }
540                 }
541                 break;
542             case CONNECTION_STATE_CONNECTING:
543                 if ((mCurrentDevice != null) && mCurrentDevice.equals(device)) {
544                     log("current device tries to connect back");
545                     // TODO(BT) ignore or reject
546                 } else if (mTargetDevice != null && mTargetDevice.equals(device)) {
547                     // The stack is connecting to target device or
548                     // there is an incoming connection from the target device at the same time
549                     // we already broadcasted the intent, doing nothing here
550                     log("Stack and target device are connecting");
551                 }
552                 else if (mIncomingDevice != null && mIncomingDevice.equals(device)) {
553                     loge("Another connecting event on the incoming device");
554                 } else {
555                     // We get an incoming connecting request while Pending
556                     // TODO(BT) is stack handing this case? let's ignore it for now
557                     log("Incoming connection while pending, accept it");
558                     broadcastConnectionState(device, BluetoothProfile.STATE_CONNECTING,
559                                              BluetoothProfile.STATE_DISCONNECTED);
560                     mIncomingDevice = device;
561                 }
562                 break;
563             case CONNECTION_STATE_DISCONNECTING:
564                 if ((mCurrentDevice != null) && mCurrentDevice.equals(device)) {
565                     // we already broadcasted the intent, doing nothing here
566                     if (DBG) {
567                         log("stack is disconnecting mCurrentDevice");
568                     }
569                 } else if (mTargetDevice != null && mTargetDevice.equals(device)) {
570                     loge("TargetDevice is getting disconnected");
571                 } else if (mIncomingDevice != null && mIncomingDevice.equals(device)) {
572                     loge("IncomingDevice is getting disconnected");
573                 } else {
574                     loge("Disconnecting unknow device: " + device);
575                 }
576                 break;
577             default:
578                 loge("Incorrect state: " + state);
579                 break;
580             }
581         }
582 
583     }
584 
585     private class Connected extends State {
586         @Override
enter()587         public void enter() {
588             // Remove pending connection attempts that were deferred during the pending
589             // state. This is to prevent auto connect attempts from disconnecting
590             // devices that previously successfully connected.
591             // TODO: This needs to check for multiple A2DP connections, once supported...
592             removeDeferredMessages(CONNECT);
593 
594             log("Enter Connected: " + getCurrentMessage().what);
595             if (mTargetDevice != null || mIncomingDevice != null) {
596                 loge("ERROR: enter() inconsistent state in Connected: current = " + mCurrentDevice
597                         + " target = " + mTargetDevice + " incoming = " + mIncomingDevice);
598             }
599 
600             // Upon connected, the audio starts out as stopped
601             broadcastAudioState(mCurrentDevice, BluetoothA2dp.STATE_NOT_PLAYING,
602                                 BluetoothA2dp.STATE_PLAYING);
603         }
604 
605         @Override
processMessage(Message message)606         public boolean processMessage(Message message) {
607             log("Connected process message: " + message.what);
608             if (mCurrentDevice == null) {
609                 loge("ERROR: mCurrentDevice is null in Connected");
610                 return NOT_HANDLED;
611             }
612 
613             boolean retValue = HANDLED;
614             switch(message.what) {
615                 case CONNECT:
616                 {
617                     BluetoothDevice device = (BluetoothDevice) message.obj;
618                     if (mCurrentDevice.equals(device)) {
619                         break;
620                     }
621 
622                     broadcastConnectionState(device, BluetoothProfile.STATE_CONNECTING,
623                                    BluetoothProfile.STATE_DISCONNECTED);
624                     if (!disconnectA2dpNative(getByteAddress(mCurrentDevice))) {
625                         broadcastConnectionState(device, BluetoothProfile.STATE_DISCONNECTED,
626                                        BluetoothProfile.STATE_CONNECTING);
627                         break;
628                     }
629 
630                     synchronized (A2dpStateMachine.this) {
631                         mTargetDevice = device;
632                         transitionTo(mPending);
633                     }
634                 }
635                     break;
636                 case DISCONNECT:
637                 {
638                     BluetoothDevice device = (BluetoothDevice) message.obj;
639                     if (!mCurrentDevice.equals(device)) {
640                         break;
641                     }
642                     broadcastConnectionState(device, BluetoothProfile.STATE_DISCONNECTING,
643                                    BluetoothProfile.STATE_CONNECTED);
644                     if (!disconnectA2dpNative(getByteAddress(device))) {
645                         broadcastConnectionState(device, BluetoothProfile.STATE_CONNECTED,
646                                        BluetoothProfile.STATE_DISCONNECTED);
647                         break;
648                     }
649                     synchronized (A2dpStateMachine.this) {
650                         transitionTo(mPending);
651                     }
652                 }
653                     break;
654                 case STACK_EVENT:
655                     StackEvent event = (StackEvent) message.obj;
656                     switch (event.type) {
657                         case EVENT_TYPE_CONNECTION_STATE_CHANGED:
658                             processConnectionEvent(event.valueInt, event.device);
659                             break;
660                         case EVENT_TYPE_AUDIO_STATE_CHANGED:
661                             processAudioStateEvent(event.valueInt, event.device);
662                             break;
663                         default:
664                             loge("Unexpected stack event: " + event.type);
665                             break;
666                     }
667                     break;
668                 default:
669                     return NOT_HANDLED;
670             }
671             return retValue;
672         }
673 
674         // in Connected state
processConnectionEvent(int state, BluetoothDevice device)675         private void processConnectionEvent(int state, BluetoothDevice device) {
676             switch (state) {
677                 case CONNECTION_STATE_DISCONNECTED:
678                     if (mCurrentDevice.equals(device)) {
679                         broadcastConnectionState(mCurrentDevice, BluetoothProfile.STATE_DISCONNECTED,
680                                                  BluetoothProfile.STATE_CONNECTED);
681                         synchronized (A2dpStateMachine.this) {
682                             mCurrentDevice = null;
683                             transitionTo(mDisconnected);
684                         }
685                     } else if (mTargetDevice != null && mTargetDevice.equals(device)) {
686                         broadcastConnectionState(device, BluetoothProfile.STATE_DISCONNECTED,
687                                                  BluetoothProfile.STATE_CONNECTING);
688                         synchronized (A2dpStateMachine.this) {
689                             mTargetDevice = null;
690                         }
691                         logi("Disconnected from mTargetDevice in connected state device: " + device);
692                     } else {
693                         loge("Disconnected from unknown device: " + device);
694                     }
695                     break;
696               default:
697                   loge("Connection State Device: " + device + " bad state: " + state);
698                   break;
699             }
700         }
processAudioStateEvent(int state, BluetoothDevice device)701         private void processAudioStateEvent(int state, BluetoothDevice device) {
702             if (!mCurrentDevice.equals(device)) {
703                 loge("Audio State Device:" + device + "is different from ConnectedDevice:" +
704                                                            mCurrentDevice);
705                 return;
706             }
707             switch (state) {
708                 case AUDIO_STATE_STARTED:
709                     if (mPlayingA2dpDevice == null) {
710                         mPlayingA2dpDevice = device;
711                         mService.setAvrcpAudioState(BluetoothA2dp.STATE_PLAYING);
712                         broadcastAudioState(device, BluetoothA2dp.STATE_PLAYING,
713                                             BluetoothA2dp.STATE_NOT_PLAYING);
714                     }
715                     break;
716                 case AUDIO_STATE_REMOTE_SUSPEND:
717                 case AUDIO_STATE_STOPPED:
718                     if (mPlayingA2dpDevice != null) {
719                         mPlayingA2dpDevice = null;
720                         mService.setAvrcpAudioState(BluetoothA2dp.STATE_NOT_PLAYING);
721                         broadcastAudioState(device, BluetoothA2dp.STATE_NOT_PLAYING,
722                                             BluetoothA2dp.STATE_PLAYING);
723                     }
724                     break;
725                 default:
726                   loge("Audio State Device: " + device + " bad state: " + state);
727                   break;
728             }
729         }
730     }
731 
getConnectionState(BluetoothDevice device)732     int getConnectionState(BluetoothDevice device) {
733         if (getCurrentState() == mDisconnected) {
734             return BluetoothProfile.STATE_DISCONNECTED;
735         }
736 
737         synchronized (this) {
738             IState currentState = getCurrentState();
739             if (currentState == mPending) {
740                 if ((mTargetDevice != null) && mTargetDevice.equals(device)) {
741                     return BluetoothProfile.STATE_CONNECTING;
742                 }
743                 if ((mCurrentDevice != null) && mCurrentDevice.equals(device)) {
744                     return BluetoothProfile.STATE_DISCONNECTING;
745                 }
746                 if ((mIncomingDevice != null) && mIncomingDevice.equals(device)) {
747                     return BluetoothProfile.STATE_CONNECTING; // incoming connection
748                 }
749                 return BluetoothProfile.STATE_DISCONNECTED;
750             }
751 
752             if (currentState == mConnected) {
753                 if (mCurrentDevice.equals(device)) {
754                     return BluetoothProfile.STATE_CONNECTED;
755                 }
756                 return BluetoothProfile.STATE_DISCONNECTED;
757             } else {
758                 loge("Bad currentState: " + currentState);
759                 return BluetoothProfile.STATE_DISCONNECTED;
760             }
761         }
762     }
763 
getConnectedDevices()764     List<BluetoothDevice> getConnectedDevices() {
765         List<BluetoothDevice> devices = new ArrayList<BluetoothDevice>();
766         synchronized (this) {
767             if (getCurrentState() == mConnected) {
768                 devices.add(mCurrentDevice);
769             }
770         }
771         return devices;
772     }
773 
isPlaying(BluetoothDevice device)774     boolean isPlaying(BluetoothDevice device) {
775         synchronized (this) {
776             if (device.equals(mPlayingA2dpDevice)) {
777                 return true;
778             }
779         }
780         return false;
781     }
782 
getCodecStatus()783     BluetoothCodecStatus getCodecStatus() {
784         synchronized (this) {
785             return mCodecStatus;
786         }
787     }
788 
onCodecConfigChanged(BluetoothCodecConfig newCodecConfig, BluetoothCodecConfig[] codecsLocalCapabilities, BluetoothCodecConfig[] codecsSelectableCapabilities)789     private void onCodecConfigChanged(BluetoothCodecConfig newCodecConfig,
790             BluetoothCodecConfig[] codecsLocalCapabilities,
791             BluetoothCodecConfig[] codecsSelectableCapabilities) {
792         BluetoothCodecConfig prevCodecConfig = null;
793         synchronized (this) {
794             if (mCodecStatus != null) {
795                 prevCodecConfig = mCodecStatus.getCodecConfig();
796             }
797             mCodecStatus = new BluetoothCodecStatus(
798                     newCodecConfig, codecsLocalCapabilities, codecsSelectableCapabilities);
799         }
800 
801         Intent intent = new Intent(BluetoothA2dp.ACTION_CODEC_CONFIG_CHANGED);
802         intent.putExtra(BluetoothCodecStatus.EXTRA_CODEC_STATUS, mCodecStatus);
803         intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
804 
805         log("A2DP Codec Config: " + prevCodecConfig + "->" + newCodecConfig);
806         for (BluetoothCodecConfig codecConfig : codecsLocalCapabilities) {
807             log("A2DP Codec Local Capability: " + codecConfig);
808         }
809         for (BluetoothCodecConfig codecConfig : codecsSelectableCapabilities) {
810             log("A2DP Codec Selectable Capability: " + codecConfig);
811         }
812 
813         // Inform the Audio Service about the codec configuration change,
814         // so the Audio Service can reset accordingly the audio feeding
815         // parameters in the Audio HAL to the Bluetooth stack.
816         if (!newCodecConfig.sameAudioFeedingParameters(prevCodecConfig) && (mCurrentDevice != null)
817                 && (getCurrentState() == mConnected)) {
818             // Add the device only if it is currently connected
819             intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mCurrentDevice);
820             mAudioManager.handleBluetoothA2dpDeviceConfigChange(mCurrentDevice);
821         }
822         mContext.sendBroadcast(intent, A2dpService.BLUETOOTH_PERM);
823     }
824 
setCodecConfigPreference(BluetoothCodecConfig codecConfig)825     void setCodecConfigPreference(BluetoothCodecConfig codecConfig) {
826         BluetoothCodecConfig[] codecConfigArray = new BluetoothCodecConfig[1];
827         codecConfigArray[0] = codecConfig;
828         setCodecConfigPreferenceNative(codecConfigArray);
829     }
830 
enableOptionalCodecs()831     void enableOptionalCodecs() {
832         BluetoothCodecConfig[] codecConfigArray = assignCodecConfigPriorities();
833         if (codecConfigArray == null) {
834             return;
835         }
836 
837         // Set the mandatory codec's priority to default, and remove the rest
838         for (int i = 0; i < codecConfigArray.length; i++) {
839             BluetoothCodecConfig codecConfig = codecConfigArray[i];
840             if (!codecConfig.isMandatoryCodec()) {
841                 codecConfigArray[i] = null;
842             }
843         }
844 
845         setCodecConfigPreferenceNative(codecConfigArray);
846     }
847 
disableOptionalCodecs()848     void disableOptionalCodecs() {
849         BluetoothCodecConfig[] codecConfigArray = assignCodecConfigPriorities();
850         if (codecConfigArray == null) {
851             return;
852         }
853         // Set the mandatory codec's priority to highest, and ignore the rest
854         for (int i = 0; i < codecConfigArray.length; i++) {
855             BluetoothCodecConfig codecConfig = codecConfigArray[i];
856             if (codecConfig.isMandatoryCodec()) {
857                 codecConfig.setCodecPriority(BluetoothCodecConfig.CODEC_PRIORITY_HIGHEST);
858             } else {
859                 codecConfigArray[i] = null;
860             }
861         }
862         setCodecConfigPreferenceNative(codecConfigArray);
863     }
864 
okToConnect(BluetoothDevice device)865     boolean okToConnect(BluetoothDevice device) {
866         AdapterService adapterService = AdapterService.getAdapterService();
867         int priority = mService.getPriority(device);
868         boolean ret = false;
869         //check if this is an incoming connection in Quiet mode.
870         if((adapterService == null) ||
871            ((adapterService.isQuietModeEnabled() == true) &&
872            (mTargetDevice == null))){
873             ret = false;
874         }
875         // check priority and accept or reject the connection. if priority is undefined
876         // it is likely that our SDP has not completed and peer is initiating the
877         // connection. Allow this connection, provided the device is bonded
878         else if((BluetoothProfile.PRIORITY_OFF < priority) ||
879                 ((BluetoothProfile.PRIORITY_UNDEFINED == priority) &&
880                 (device.getBondState() != BluetoothDevice.BOND_NONE))){
881             ret= true;
882         }
883         return ret;
884     }
885 
getDevicesMatchingConnectionStates(int[] states)886     synchronized List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
887         List<BluetoothDevice> deviceList = new ArrayList<BluetoothDevice>();
888         Set<BluetoothDevice> bondedDevices = mAdapter.getBondedDevices();
889         int connectionState;
890 
891         for (BluetoothDevice device : bondedDevices) {
892             ParcelUuid[] featureUuids = device.getUuids();
893             if (!BluetoothUuid.isUuidPresent(featureUuids, BluetoothUuid.AudioSink)) {
894                 continue;
895             }
896             connectionState = getConnectionState(device);
897             for(int i = 0; i < states.length; i++) {
898                 if (connectionState == states[i]) {
899                     deviceList.add(device);
900                 }
901             }
902         }
903         return deviceList;
904     }
905 
906 
907     // This method does not check for error conditon (newState == prevState)
broadcastConnectionState(BluetoothDevice device, int newState, int prevState)908     private void broadcastConnectionState(BluetoothDevice device, int newState, int prevState) {
909         mAudioManager.setBluetoothA2dpDeviceConnectionState(
910                 device, newState, BluetoothProfile.A2DP);
911 
912         mWakeLock.acquire();
913         mIntentBroadcastHandler.sendMessage(mIntentBroadcastHandler.obtainMessage(
914                 MSG_CONNECTION_STATE_CHANGED, prevState, newState, device));
915     }
916 
broadcastAudioState(BluetoothDevice device, int state, int prevState)917     private void broadcastAudioState(BluetoothDevice device, int state, int prevState) {
918         Intent intent = new Intent(BluetoothA2dp.ACTION_PLAYING_STATE_CHANGED);
919         intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
920         intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, prevState);
921         intent.putExtra(BluetoothProfile.EXTRA_STATE, state);
922         intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
923         mContext.sendBroadcast(intent, A2dpService.BLUETOOTH_PERM);
924 
925         log("A2DP Playing state : device: " + device + " State:" + prevState + "->" + state);
926     }
927 
getByteAddress(BluetoothDevice device)928     private byte[] getByteAddress(BluetoothDevice device) {
929         return Utils.getBytesFromAddress(device.getAddress());
930     }
931 
onConnectionStateChanged(int state, byte[] address)932     private void onConnectionStateChanged(int state, byte[] address) {
933         StackEvent event = new StackEvent(EVENT_TYPE_CONNECTION_STATE_CHANGED);
934         event.valueInt = state;
935         event.device = getDevice(address);
936         sendMessage(STACK_EVENT, event);
937     }
938 
onAudioStateChanged(int state, byte[] address)939     private void onAudioStateChanged(int state, byte[] address) {
940         StackEvent event = new StackEvent(EVENT_TYPE_AUDIO_STATE_CHANGED);
941         event.valueInt = state;
942         event.device = getDevice(address);
943         sendMessage(STACK_EVENT, event);
944     }
getDevice(byte[] address)945     private BluetoothDevice getDevice(byte[] address) {
946         return mAdapter.getRemoteDevice(Utils.getAddressStringFromByte(address));
947     }
948 
949     private class StackEvent {
950         int type = EVENT_TYPE_NONE;
951         int valueInt = 0;
952         BluetoothDevice device = null;
953 
StackEvent(int type)954         private StackEvent(int type) {
955             this.type = type;
956         }
957     }
958     /** Handles A2DP connection state change intent broadcasts. */
959     private class IntentBroadcastHandler extends Handler {
960 
onConnectionStateChanged(BluetoothDevice device, int prevState, int state)961         private void onConnectionStateChanged(BluetoothDevice device, int prevState, int state) {
962             Intent intent = new Intent(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED);
963             intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, prevState);
964             intent.putExtra(BluetoothProfile.EXTRA_STATE, state);
965             intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
966             intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT
967                     | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
968             mContext.sendBroadcast(intent, ProfileService.BLUETOOTH_PERM);
969             log("Connection state " + device + ": " + prevState + "->" + state);
970         }
971 
972         @Override
handleMessage(Message msg)973         public void handleMessage(Message msg) {
974             switch (msg.what) {
975                 case MSG_CONNECTION_STATE_CHANGED:
976                     onConnectionStateChanged((BluetoothDevice) msg.obj, msg.arg1, msg.arg2);
977                     mWakeLock.release();
978                     break;
979             }
980         }
981     }
982 
dump(StringBuilder sb)983     public void dump(StringBuilder sb) {
984         ProfileService.println(sb, "mCurrentDevice: " + mCurrentDevice);
985         ProfileService.println(sb, "mTargetDevice: " + mTargetDevice);
986         ProfileService.println(sb, "mIncomingDevice: " + mIncomingDevice);
987         ProfileService.println(sb, "mPlayingA2dpDevice: " + mPlayingA2dpDevice);
988         ProfileService.println(sb, "StateMachine: " + this.toString());
989     }
990 
991     // Event types for STACK_EVENT message
992     final private static int EVENT_TYPE_NONE = 0;
993     final private static int EVENT_TYPE_CONNECTION_STATE_CHANGED = 1;
994     final private static int EVENT_TYPE_AUDIO_STATE_CHANGED = 2;
995 
996    // Do not modify without updating the HAL bt_av.h files.
997 
998     // match up with btav_connection_state_t enum of bt_av.h
999     final static int CONNECTION_STATE_DISCONNECTED = 0;
1000     final static int CONNECTION_STATE_CONNECTING = 1;
1001     final static int CONNECTION_STATE_CONNECTED = 2;
1002     final static int CONNECTION_STATE_DISCONNECTING = 3;
1003 
1004     // match up with btav_audio_state_t enum of bt_av.h
1005     final static int AUDIO_STATE_REMOTE_SUSPEND = 0;
1006     final static int AUDIO_STATE_STOPPED = 1;
1007     final static int AUDIO_STATE_STARTED = 2;
1008 
classInitNative()1009     private native static void classInitNative();
initNative(BluetoothCodecConfig[] codecConfigPriorites)1010     private native void initNative(BluetoothCodecConfig[] codecConfigPriorites);
cleanupNative()1011     private native void cleanupNative();
connectA2dpNative(byte[] address)1012     private native boolean connectA2dpNative(byte[] address);
disconnectA2dpNative(byte[] address)1013     private native boolean disconnectA2dpNative(byte[] address);
setCodecConfigPreferenceNative(BluetoothCodecConfig[] codecConfigArray)1014     private native boolean setCodecConfigPreferenceNative(BluetoothCodecConfig[] codecConfigArray);
1015 }
1016