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.BluetoothDevice;
34 import android.bluetooth.BluetoothProfile;
35 import android.bluetooth.BluetoothUuid;
36 import android.bluetooth.IBluetooth;
37 import android.content.Context;
38 import android.media.AudioManager;
39 import android.os.Handler;
40 import android.os.Message;
41 import android.os.ParcelUuid;
42 import android.os.PowerManager;
43 import android.os.PowerManager.WakeLock;
44 import android.content.Intent;
45 import android.os.Message;
46 import android.os.RemoteException;
47 import android.os.ServiceManager;
48 import android.os.ParcelUuid;
49 import android.util.Log;
50 import com.android.bluetooth.Utils;
51 import com.android.bluetooth.btservice.AdapterService;
52 import com.android.bluetooth.btservice.ProfileService;
53 import com.android.internal.util.IState;
54 import com.android.internal.util.State;
55 import com.android.internal.util.StateMachine;
56 import java.util.ArrayList;
57 import java.util.List;
58 import java.util.Set;
59 
60 final class A2dpStateMachine extends StateMachine {
61     private static final boolean DBG = false;
62 
63     static final int CONNECT = 1;
64     static final int DISCONNECT = 2;
65     private static final int STACK_EVENT = 101;
66     private static final int CONNECT_TIMEOUT = 201;
67 
68     private Disconnected mDisconnected;
69     private Pending mPending;
70     private Connected mConnected;
71 
72     private A2dpService mService;
73     private Context mContext;
74     private BluetoothAdapter mAdapter;
75     private final AudioManager mAudioManager;
76     private IntentBroadcastHandler mIntentBroadcastHandler;
77     private final WakeLock mWakeLock;
78 
79     private static final int MSG_CONNECTION_STATE_CHANGED = 0;
80 
81     // mCurrentDevice is the device connected before the state changes
82     // mTargetDevice is the device to be connected
83     // mIncomingDevice is the device connecting to us, valid only in Pending state
84     //                when mIncomingDevice is not null, both mCurrentDevice
85     //                  and mTargetDevice are null
86     //                when either mCurrentDevice or mTargetDevice is not null,
87     //                  mIncomingDevice is null
88     // Stable states
89     //   No connection, Disconnected state
90     //                  both mCurrentDevice and mTargetDevice are null
91     //   Connected, Connected state
92     //              mCurrentDevice is not null, mTargetDevice is null
93     // Interim states
94     //   Connecting to a device, Pending
95     //                           mCurrentDevice is null, mTargetDevice is not null
96     //   Disconnecting device, Connecting to new device
97     //     Pending
98     //     Both mCurrentDevice and mTargetDevice are not null
99     //   Disconnecting device Pending
100     //                        mCurrentDevice is not null, mTargetDevice is null
101     //   Incoming connections Pending
102     //                        Both mCurrentDevice and mTargetDevice are null
103     private BluetoothDevice mCurrentDevice = null;
104     private BluetoothDevice mTargetDevice = null;
105     private BluetoothDevice mIncomingDevice = null;
106     private BluetoothDevice mPlayingA2dpDevice = null;
107 
108 
109     static {
classInitNative()110         classInitNative();
111     }
112 
A2dpStateMachine(A2dpService svc, Context context)113     private A2dpStateMachine(A2dpService svc, Context context) {
114         super("A2dpStateMachine");
115         mService = svc;
116         mContext = context;
117         mAdapter = BluetoothAdapter.getDefaultAdapter();
118 
119         initNative();
120 
121         mDisconnected = new Disconnected();
122         mPending = new Pending();
123         mConnected = new Connected();
124 
125         addState(mDisconnected);
126         addState(mPending);
127         addState(mConnected);
128 
129         setInitialState(mDisconnected);
130 
131         PowerManager pm = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
132         mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "BluetoothA2dpService");
133 
134         mIntentBroadcastHandler = new IntentBroadcastHandler();
135 
136         mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
137     }
138 
make(A2dpService svc, Context context)139     static A2dpStateMachine make(A2dpService svc, Context context) {
140         Log.d("A2dpStateMachine", "make");
141         A2dpStateMachine a2dpSm = new A2dpStateMachine(svc, context);
142         a2dpSm.start();
143         return a2dpSm;
144     }
145 
doQuit()146     public void doQuit() {
147         if ((mTargetDevice != null) &&
148             (getConnectionState(mTargetDevice) == BluetoothProfile.STATE_CONNECTING)) {
149             log("doQuit()- Move A2DP State to DISCONNECTED");
150             broadcastConnectionState(mTargetDevice, BluetoothProfile.STATE_DISCONNECTED,
151                                      BluetoothProfile.STATE_CONNECTING);
152         }
153         quitNow();
154     }
155 
cleanup()156     public void cleanup() {
157         cleanupNative();
158     }
159 
160         private class Disconnected extends State {
161         @Override
enter()162         public void enter() {
163             log("Enter Disconnected: " + getCurrentMessage().what);
164         }
165 
166         @Override
processMessage(Message message)167         public boolean processMessage(Message message) {
168             log("Disconnected process message: " + message.what);
169             if (mCurrentDevice != null || mTargetDevice != null  || mIncomingDevice != null) {
170                 loge("ERROR: current, target, or mIncomingDevice not null in Disconnected");
171                 return NOT_HANDLED;
172             }
173 
174             boolean retValue = HANDLED;
175             switch(message.what) {
176                 case CONNECT:
177                     BluetoothDevice device = (BluetoothDevice) message.obj;
178                     broadcastConnectionState(device, BluetoothProfile.STATE_CONNECTING,
179                                    BluetoothProfile.STATE_DISCONNECTED);
180 
181                     if (!connectA2dpNative(getByteAddress(device)) ) {
182                         broadcastConnectionState(device, BluetoothProfile.STATE_DISCONNECTED,
183                                        BluetoothProfile.STATE_CONNECTING);
184                         break;
185                     }
186 
187                     synchronized (A2dpStateMachine.this) {
188                         mTargetDevice = device;
189                         transitionTo(mPending);
190                     }
191                     // TODO(BT) remove CONNECT_TIMEOUT when the stack
192                     //          sends back events consistently
193                     sendMessageDelayed(CONNECT_TIMEOUT, 30000);
194                     break;
195                 case DISCONNECT:
196                     // ignore
197                     break;
198                 case STACK_EVENT:
199                     StackEvent event = (StackEvent) message.obj;
200                     switch (event.type) {
201                         case EVENT_TYPE_CONNECTION_STATE_CHANGED:
202                             processConnectionEvent(event.valueInt, event.device);
203                             break;
204                         default:
205                             loge("Unexpected stack event: " + event.type);
206                             break;
207                     }
208                     break;
209                 default:
210                     return NOT_HANDLED;
211             }
212             return retValue;
213         }
214 
215         @Override
exit()216         public void exit() {
217             log("Exit Disconnected: " + getCurrentMessage().what);
218         }
219 
220         // in Disconnected state
processConnectionEvent(int state, BluetoothDevice device)221         private void processConnectionEvent(int state, BluetoothDevice device) {
222             switch (state) {
223             case CONNECTION_STATE_DISCONNECTED:
224                 logw("Ignore HF DISCONNECTED event, device: " + device);
225                 break;
226             case CONNECTION_STATE_CONNECTING:
227                 if (okToConnect(device)){
228                     logi("Incoming A2DP accepted");
229                     broadcastConnectionState(device, BluetoothProfile.STATE_CONNECTING,
230                                              BluetoothProfile.STATE_DISCONNECTED);
231                     synchronized (A2dpStateMachine.this) {
232                         mIncomingDevice = device;
233                         transitionTo(mPending);
234                     }
235                 } else {
236                     //reject the connection and stay in Disconnected state itself
237                     logi("Incoming A2DP rejected");
238                     disconnectA2dpNative(getByteAddress(device));
239                     // the other profile connection should be initiated
240                     AdapterService adapterService = AdapterService.getAdapterService();
241                     if (adapterService != null) {
242                         adapterService.connectOtherProfile(device,
243                                                            AdapterService.PROFILE_CONN_REJECTED);
244                     }
245                 }
246                 break;
247             case CONNECTION_STATE_CONNECTED:
248                 logw("A2DP Connected from Disconnected state");
249                 if (okToConnect(device)){
250                     logi("Incoming A2DP accepted");
251                     broadcastConnectionState(device, BluetoothProfile.STATE_CONNECTED,
252                                              BluetoothProfile.STATE_DISCONNECTED);
253                     synchronized (A2dpStateMachine.this) {
254                         mCurrentDevice = device;
255                         transitionTo(mConnected);
256                     }
257                 } else {
258                     //reject the connection and stay in Disconnected state itself
259                     logi("Incoming A2DP rejected");
260                     disconnectA2dpNative(getByteAddress(device));
261                     // the other profile connection should be initiated
262                     AdapterService adapterService = AdapterService.getAdapterService();
263                     if (adapterService != null) {
264                         adapterService.connectOtherProfile(device,
265                                                            AdapterService.PROFILE_CONN_REJECTED);
266                     }
267                 }
268                 break;
269             case CONNECTION_STATE_DISCONNECTING:
270                 logw("Ignore A2dp DISCONNECTING event, device: " + device);
271                 break;
272             default:
273                 loge("Incorrect state: " + state);
274                 break;
275             }
276         }
277     }
278 
279     private class Pending extends State {
280         @Override
enter()281         public void enter() {
282             log("Enter Pending: " + getCurrentMessage().what);
283         }
284 
285         @Override
processMessage(Message message)286         public boolean processMessage(Message message) {
287             log("Pending process message: " + message.what);
288 
289             boolean retValue = HANDLED;
290             switch(message.what) {
291                 case CONNECT:
292                     deferMessage(message);
293                     break;
294                 case CONNECT_TIMEOUT:
295                     onConnectionStateChanged(CONNECTION_STATE_DISCONNECTED,
296                                              getByteAddress(mTargetDevice));
297                     break;
298                 case DISCONNECT:
299                     BluetoothDevice device = (BluetoothDevice) message.obj;
300                     if (mCurrentDevice != null && mTargetDevice != null &&
301                         mTargetDevice.equals(device) ) {
302                         // cancel connection to the mTargetDevice
303                         broadcastConnectionState(device, BluetoothProfile.STATE_DISCONNECTED,
304                                        BluetoothProfile.STATE_CONNECTING);
305                         synchronized (A2dpStateMachine.this) {
306                             mTargetDevice = null;
307                         }
308                     } else {
309                         deferMessage(message);
310                     }
311                     break;
312                 case STACK_EVENT:
313                     StackEvent event = (StackEvent) message.obj;
314                     switch (event.type) {
315                         case EVENT_TYPE_CONNECTION_STATE_CHANGED:
316                             removeMessages(CONNECT_TIMEOUT);
317                             processConnectionEvent(event.valueInt, event.device);
318                             break;
319                         default:
320                             loge("Unexpected stack event: " + event.type);
321                             break;
322                     }
323                     break;
324                 default:
325                     return NOT_HANDLED;
326             }
327             return retValue;
328         }
329 
330         // in Pending state
processConnectionEvent(int state, BluetoothDevice device)331         private void processConnectionEvent(int state, BluetoothDevice device) {
332             switch (state) {
333                 case CONNECTION_STATE_DISCONNECTED:
334                     if ((mCurrentDevice != null) && mCurrentDevice.equals(device)) {
335                         broadcastConnectionState(mCurrentDevice,
336                                                  BluetoothProfile.STATE_DISCONNECTED,
337                                                  BluetoothProfile.STATE_DISCONNECTING);
338                         synchronized (A2dpStateMachine.this) {
339                             mCurrentDevice = null;
340                         }
341 
342                         if (mTargetDevice != null) {
343                             if (!connectA2dpNative(getByteAddress(mTargetDevice))) {
344                                 broadcastConnectionState(mTargetDevice,
345                                                          BluetoothProfile.STATE_DISCONNECTED,
346                                                          BluetoothProfile.STATE_CONNECTING);
347                                 synchronized (A2dpStateMachine.this) {
348                                     mTargetDevice = null;
349                                     transitionTo(mDisconnected);
350                                 }
351                             }
352                         } else {
353                             synchronized (A2dpStateMachine.this) {
354                                 mIncomingDevice = null;
355                                 transitionTo(mDisconnected);
356                             }
357                         }
358                     } else if (mTargetDevice != null && mTargetDevice.equals(device)) {
359                         // outgoing connection failed
360                         broadcastConnectionState(mTargetDevice, BluetoothProfile.STATE_DISCONNECTED,
361                                                  BluetoothProfile.STATE_CONNECTING);
362                         // check if there is some incoming connection request
363                         if (mIncomingDevice != null) {
364                             logi("disconnect for outgoing in pending state");
365                             synchronized (A2dpStateMachine.this) {
366                                 mTargetDevice = null;
367                             }
368                             break;
369                         }
370                         synchronized (A2dpStateMachine.this) {
371                             mTargetDevice = null;
372                             transitionTo(mDisconnected);
373                         }
374                     } else if (mIncomingDevice != null && mIncomingDevice.equals(device)) {
375                         broadcastConnectionState(mIncomingDevice,
376                                                  BluetoothProfile.STATE_DISCONNECTED,
377                                                  BluetoothProfile.STATE_CONNECTING);
378                         synchronized (A2dpStateMachine.this) {
379                             mIncomingDevice = null;
380                             transitionTo(mDisconnected);
381                         }
382                     } else {
383                         loge("Unknown device Disconnected: " + device);
384                     }
385                     break;
386             case CONNECTION_STATE_CONNECTED:
387                 if ((mCurrentDevice != null) && mCurrentDevice.equals(device)) {
388                     // disconnection failed
389                     broadcastConnectionState(mCurrentDevice, BluetoothProfile.STATE_CONNECTED,
390                                              BluetoothProfile.STATE_DISCONNECTING);
391                     if (mTargetDevice != null) {
392                         broadcastConnectionState(mTargetDevice, BluetoothProfile.STATE_DISCONNECTED,
393                                                  BluetoothProfile.STATE_CONNECTING);
394                     }
395                     synchronized (A2dpStateMachine.this) {
396                         mTargetDevice = null;
397                         transitionTo(mConnected);
398                     }
399                 } else if (mTargetDevice != null && mTargetDevice.equals(device)) {
400                     broadcastConnectionState(mTargetDevice, BluetoothProfile.STATE_CONNECTED,
401                                              BluetoothProfile.STATE_CONNECTING);
402                     synchronized (A2dpStateMachine.this) {
403                         mCurrentDevice = mTargetDevice;
404                         mTargetDevice = null;
405                         transitionTo(mConnected);
406                     }
407                 } else if (mIncomingDevice != null && mIncomingDevice.equals(device)) {
408                     broadcastConnectionState(mIncomingDevice, BluetoothProfile.STATE_CONNECTED,
409                                              BluetoothProfile.STATE_CONNECTING);
410                     // check for a2dp connection allowed for this device in race condition
411                     if (okToConnect(mIncomingDevice)) {
412                         logi("Ready to connect incoming Connection from pending state");
413                         synchronized (A2dpStateMachine.this) {
414                             mCurrentDevice = mIncomingDevice;
415                             mIncomingDevice = null;
416                             transitionTo(mConnected);
417                         }
418                     } else {
419                         // A2dp connection unchecked for this device
420                         loge("Incoming A2DP rejected from pending state");
421                         disconnectA2dpNative(getByteAddress(device));
422                     }
423                 } else {
424                     loge("Unknown device Connected: " + device);
425                     // something is wrong here, but sync our state with stack
426                     broadcastConnectionState(device, BluetoothProfile.STATE_CONNECTED,
427                                              BluetoothProfile.STATE_DISCONNECTED);
428                     synchronized (A2dpStateMachine.this) {
429                         mCurrentDevice = device;
430                         mTargetDevice = null;
431                         mIncomingDevice = null;
432                         transitionTo(mConnected);
433                     }
434                 }
435                 break;
436             case CONNECTION_STATE_CONNECTING:
437                 if ((mCurrentDevice != null) && mCurrentDevice.equals(device)) {
438                     log("current device tries to connect back");
439                     // TODO(BT) ignore or reject
440                 } else if (mTargetDevice != null && mTargetDevice.equals(device)) {
441                     // The stack is connecting to target device or
442                     // there is an incoming connection from the target device at the same time
443                     // we already broadcasted the intent, doing nothing here
444                     log("Stack and target device are connecting");
445                 }
446                 else if (mIncomingDevice != null && mIncomingDevice.equals(device)) {
447                     loge("Another connecting event on the incoming device");
448                 } else {
449                     // We get an incoming connecting request while Pending
450                     // TODO(BT) is stack handing this case? let's ignore it for now
451                     log("Incoming connection while pending, accept it");
452                     broadcastConnectionState(device, BluetoothProfile.STATE_CONNECTING,
453                                              BluetoothProfile.STATE_DISCONNECTED);
454                     mIncomingDevice = device;
455                 }
456                 break;
457             case CONNECTION_STATE_DISCONNECTING:
458                 if ((mCurrentDevice != null) && mCurrentDevice.equals(device)) {
459                     // we already broadcasted the intent, doing nothing here
460                     if (DBG) {
461                         log("stack is disconnecting mCurrentDevice");
462                     }
463                 } else if (mTargetDevice != null && mTargetDevice.equals(device)) {
464                     loge("TargetDevice is getting disconnected");
465                 } else if (mIncomingDevice != null && mIncomingDevice.equals(device)) {
466                     loge("IncomingDevice is getting disconnected");
467                 } else {
468                     loge("Disconnecting unknow device: " + device);
469                 }
470                 break;
471             default:
472                 loge("Incorrect state: " + state);
473                 break;
474             }
475         }
476 
477     }
478 
479     private class Connected extends State {
480         @Override
enter()481         public void enter() {
482             // Remove pending connection attempts that were deferred during the pending
483             // state. This is to prevent auto connect attempts from disconnecting
484             // devices that previously successfully connected.
485             // TODO: This needs to check for multiple A2DP connections, once supported...
486             removeDeferredMessages(CONNECT);
487 
488             log("Enter Connected: " + getCurrentMessage().what);
489             // Upon connected, the audio starts out as stopped
490             broadcastAudioState(mCurrentDevice, BluetoothA2dp.STATE_NOT_PLAYING,
491                                 BluetoothA2dp.STATE_PLAYING);
492         }
493 
494         @Override
processMessage(Message message)495         public boolean processMessage(Message message) {
496             log("Connected process message: " + message.what);
497             if (mCurrentDevice == null) {
498                 loge("ERROR: mCurrentDevice is null in Connected");
499                 return NOT_HANDLED;
500             }
501 
502             boolean retValue = HANDLED;
503             switch(message.what) {
504                 case CONNECT:
505                 {
506                     BluetoothDevice device = (BluetoothDevice) message.obj;
507                     if (mCurrentDevice.equals(device)) {
508                         break;
509                     }
510 
511                     broadcastConnectionState(device, BluetoothProfile.STATE_CONNECTING,
512                                    BluetoothProfile.STATE_DISCONNECTED);
513                     if (!disconnectA2dpNative(getByteAddress(mCurrentDevice))) {
514                         broadcastConnectionState(device, BluetoothProfile.STATE_DISCONNECTED,
515                                        BluetoothProfile.STATE_CONNECTING);
516                         break;
517                     }
518 
519                     synchronized (A2dpStateMachine.this) {
520                         mTargetDevice = device;
521                         transitionTo(mPending);
522                     }
523                 }
524                     break;
525                 case DISCONNECT:
526                 {
527                     BluetoothDevice device = (BluetoothDevice) message.obj;
528                     if (!mCurrentDevice.equals(device)) {
529                         break;
530                     }
531                     broadcastConnectionState(device, BluetoothProfile.STATE_DISCONNECTING,
532                                    BluetoothProfile.STATE_CONNECTED);
533                     if (!disconnectA2dpNative(getByteAddress(device))) {
534                         broadcastConnectionState(device, BluetoothProfile.STATE_CONNECTED,
535                                        BluetoothProfile.STATE_DISCONNECTED);
536                         break;
537                     }
538                     transitionTo(mPending);
539                 }
540                     break;
541                 case STACK_EVENT:
542                     StackEvent event = (StackEvent) message.obj;
543                     switch (event.type) {
544                         case EVENT_TYPE_CONNECTION_STATE_CHANGED:
545                             processConnectionEvent(event.valueInt, event.device);
546                             break;
547                         case EVENT_TYPE_AUDIO_STATE_CHANGED:
548                             processAudioStateEvent(event.valueInt, event.device);
549                             break;
550                         default:
551                             loge("Unexpected stack event: " + event.type);
552                             break;
553                     }
554                     break;
555                 default:
556                     return NOT_HANDLED;
557             }
558             return retValue;
559         }
560 
561         // in Connected state
processConnectionEvent(int state, BluetoothDevice device)562         private void processConnectionEvent(int state, BluetoothDevice device) {
563             switch (state) {
564                 case CONNECTION_STATE_DISCONNECTED:
565                     if (mCurrentDevice.equals(device)) {
566                         broadcastConnectionState(mCurrentDevice, BluetoothProfile.STATE_DISCONNECTED,
567                                                  BluetoothProfile.STATE_CONNECTED);
568                         synchronized (A2dpStateMachine.this) {
569                             mCurrentDevice = null;
570                             transitionTo(mDisconnected);
571                         }
572                     } else if (mTargetDevice != null && mTargetDevice.equals(device)) {
573                         broadcastConnectionState(device, BluetoothProfile.STATE_DISCONNECTED,
574                                                  BluetoothProfile.STATE_CONNECTING);
575                         synchronized (A2dpStateMachine.this) {
576                             mTargetDevice = null;
577                         }
578                         logi("Disconnected from mTargetDevice in connected state device: " + device);
579                     } else {
580                         loge("Disconnected from unknown device: " + device);
581                     }
582                     break;
583               default:
584                   loge("Connection State Device: " + device + " bad state: " + state);
585                   break;
586             }
587         }
processAudioStateEvent(int state, BluetoothDevice device)588         private void processAudioStateEvent(int state, BluetoothDevice device) {
589             if (!mCurrentDevice.equals(device)) {
590                 loge("Audio State Device:" + device + "is different from ConnectedDevice:" +
591                                                            mCurrentDevice);
592                 return;
593             }
594             switch (state) {
595                 case AUDIO_STATE_STARTED:
596                     if (mPlayingA2dpDevice == null) {
597                         mPlayingA2dpDevice = device;
598                         mService.setAvrcpAudioState(BluetoothA2dp.STATE_PLAYING);
599                         broadcastAudioState(device, BluetoothA2dp.STATE_PLAYING,
600                                             BluetoothA2dp.STATE_NOT_PLAYING);
601                     }
602                     break;
603                 case AUDIO_STATE_REMOTE_SUSPEND:
604                 case AUDIO_STATE_STOPPED:
605                     if (mPlayingA2dpDevice != null) {
606                         mPlayingA2dpDevice = null;
607                         mService.setAvrcpAudioState(BluetoothA2dp.STATE_NOT_PLAYING);
608                         broadcastAudioState(device, BluetoothA2dp.STATE_NOT_PLAYING,
609                                             BluetoothA2dp.STATE_PLAYING);
610                     }
611                     break;
612                 default:
613                   loge("Audio State Device: " + device + " bad state: " + state);
614                   break;
615             }
616         }
617     }
618 
getConnectionState(BluetoothDevice device)619     int getConnectionState(BluetoothDevice device) {
620         if (getCurrentState() == mDisconnected) {
621             return BluetoothProfile.STATE_DISCONNECTED;
622         }
623 
624         synchronized (this) {
625             IState currentState = getCurrentState();
626             if (currentState == mPending) {
627                 if ((mTargetDevice != null) && mTargetDevice.equals(device)) {
628                     return BluetoothProfile.STATE_CONNECTING;
629                 }
630                 if ((mCurrentDevice != null) && mCurrentDevice.equals(device)) {
631                     return BluetoothProfile.STATE_DISCONNECTING;
632                 }
633                 if ((mIncomingDevice != null) && mIncomingDevice.equals(device)) {
634                     return BluetoothProfile.STATE_CONNECTING; // incoming connection
635                 }
636                 return BluetoothProfile.STATE_DISCONNECTED;
637             }
638 
639             if (currentState == mConnected) {
640                 if (mCurrentDevice.equals(device)) {
641                     return BluetoothProfile.STATE_CONNECTED;
642                 }
643                 return BluetoothProfile.STATE_DISCONNECTED;
644             } else {
645                 loge("Bad currentState: " + currentState);
646                 return BluetoothProfile.STATE_DISCONNECTED;
647             }
648         }
649     }
650 
getConnectedDevices()651     List<BluetoothDevice> getConnectedDevices() {
652         List<BluetoothDevice> devices = new ArrayList<BluetoothDevice>();
653         synchronized(this) {
654             if (getCurrentState() == mConnected) {
655                 devices.add(mCurrentDevice);
656             }
657         }
658         return devices;
659     }
660 
isPlaying(BluetoothDevice device)661     boolean isPlaying(BluetoothDevice device) {
662         synchronized(this) {
663             if (device.equals(mPlayingA2dpDevice)) {
664                 return true;
665             }
666         }
667         return false;
668     }
669 
okToConnect(BluetoothDevice device)670     boolean okToConnect(BluetoothDevice device) {
671         AdapterService adapterService = AdapterService.getAdapterService();
672         int priority = mService.getPriority(device);
673         boolean ret = false;
674         //check if this is an incoming connection in Quiet mode.
675         if((adapterService == null) ||
676            ((adapterService.isQuietModeEnabled() == true) &&
677            (mTargetDevice == null))){
678             ret = false;
679         }
680         // check priority and accept or reject the connection. if priority is undefined
681         // it is likely that our SDP has not completed and peer is initiating the
682         // connection. Allow this connection, provided the device is bonded
683         else if((BluetoothProfile.PRIORITY_OFF < priority) ||
684                 ((BluetoothProfile.PRIORITY_UNDEFINED == priority) &&
685                 (device.getBondState() != BluetoothDevice.BOND_NONE))){
686             ret= true;
687         }
688         return ret;
689     }
690 
getDevicesMatchingConnectionStates(int[] states)691     synchronized List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
692         List<BluetoothDevice> deviceList = new ArrayList<BluetoothDevice>();
693         Set<BluetoothDevice> bondedDevices = mAdapter.getBondedDevices();
694         int connectionState;
695 
696         for (BluetoothDevice device : bondedDevices) {
697             ParcelUuid[] featureUuids = device.getUuids();
698             if (!BluetoothUuid.isUuidPresent(featureUuids, BluetoothUuid.AudioSink)) {
699                 continue;
700             }
701             connectionState = getConnectionState(device);
702             for(int i = 0; i < states.length; i++) {
703                 if (connectionState == states[i]) {
704                     deviceList.add(device);
705                 }
706             }
707         }
708         return deviceList;
709     }
710 
711 
712     // This method does not check for error conditon (newState == prevState)
broadcastConnectionState(BluetoothDevice device, int newState, int prevState)713     private void broadcastConnectionState(BluetoothDevice device, int newState, int prevState) {
714 
715         int delay = mAudioManager.setBluetoothA2dpDeviceConnectionState(device, newState,
716                 BluetoothProfile.A2DP);
717 
718         mWakeLock.acquire();
719         mIntentBroadcastHandler.sendMessageDelayed(mIntentBroadcastHandler.obtainMessage(
720                                                         MSG_CONNECTION_STATE_CHANGED,
721                                                         prevState,
722                                                         newState,
723                                                         device),
724                                                         delay);
725     }
726 
broadcastAudioState(BluetoothDevice device, int state, int prevState)727     private void broadcastAudioState(BluetoothDevice device, int state, int prevState) {
728         Intent intent = new Intent(BluetoothA2dp.ACTION_PLAYING_STATE_CHANGED);
729         intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
730         intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, prevState);
731         intent.putExtra(BluetoothProfile.EXTRA_STATE, state);
732         intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
733         mContext.sendBroadcast(intent, A2dpService.BLUETOOTH_PERM);
734 
735         log("A2DP Playing state : device: " + device + " State:" + prevState + "->" + state);
736     }
737 
getByteAddress(BluetoothDevice device)738     private byte[] getByteAddress(BluetoothDevice device) {
739         return Utils.getBytesFromAddress(device.getAddress());
740     }
741 
onConnectionStateChanged(int state, byte[] address)742     private void onConnectionStateChanged(int state, byte[] address) {
743         StackEvent event = new StackEvent(EVENT_TYPE_CONNECTION_STATE_CHANGED);
744         event.valueInt = state;
745         event.device = getDevice(address);
746         sendMessage(STACK_EVENT, event);
747     }
748 
onAudioStateChanged(int state, byte[] address)749     private void onAudioStateChanged(int state, byte[] address) {
750         StackEvent event = new StackEvent(EVENT_TYPE_AUDIO_STATE_CHANGED);
751         event.valueInt = state;
752         event.device = getDevice(address);
753         sendMessage(STACK_EVENT, event);
754     }
getDevice(byte[] address)755     private BluetoothDevice getDevice(byte[] address) {
756         return mAdapter.getRemoteDevice(Utils.getAddressStringFromByte(address));
757     }
758 
759     private class StackEvent {
760         int type = EVENT_TYPE_NONE;
761         int valueInt = 0;
762         BluetoothDevice device = null;
763 
StackEvent(int type)764         private StackEvent(int type) {
765             this.type = type;
766         }
767     }
768     /** Handles A2DP connection state change intent broadcasts. */
769     private class IntentBroadcastHandler extends Handler {
770 
onConnectionStateChanged(BluetoothDevice device, int prevState, int state)771         private void onConnectionStateChanged(BluetoothDevice device, int prevState, int state) {
772             Intent intent = new Intent(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED);
773             intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, prevState);
774             intent.putExtra(BluetoothProfile.EXTRA_STATE, state);
775             intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
776             intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
777             mContext.sendBroadcast(intent, ProfileService.BLUETOOTH_PERM);
778             log("Connection state " + device + ": " + prevState + "->" + state);
779             mService.notifyProfileConnectionStateChanged(device, BluetoothProfile.A2DP, state, prevState);
780         }
781 
782         @Override
handleMessage(Message msg)783         public void handleMessage(Message msg) {
784             switch (msg.what) {
785                 case MSG_CONNECTION_STATE_CHANGED:
786                     onConnectionStateChanged((BluetoothDevice) msg.obj, msg.arg1, msg.arg2);
787                     mWakeLock.release();
788                     break;
789             }
790         }
791     }
792 
dump(StringBuilder sb)793     public void dump(StringBuilder sb) {
794         ProfileService.println(sb, "mCurrentDevice: " + mCurrentDevice);
795         ProfileService.println(sb, "mTargetDevice: " + mTargetDevice);
796         ProfileService.println(sb, "mIncomingDevice: " + mIncomingDevice);
797         ProfileService.println(sb, "mPlayingA2dpDevice: " + mPlayingA2dpDevice);
798         ProfileService.println(sb, "StateMachine: " + this.toString());
799     }
800 
801     // Event types for STACK_EVENT message
802     final private static int EVENT_TYPE_NONE = 0;
803     final private static int EVENT_TYPE_CONNECTION_STATE_CHANGED = 1;
804     final private static int EVENT_TYPE_AUDIO_STATE_CHANGED = 2;
805 
806    // Do not modify without updating the HAL bt_av.h files.
807 
808     // match up with btav_connection_state_t enum of bt_av.h
809     final static int CONNECTION_STATE_DISCONNECTED = 0;
810     final static int CONNECTION_STATE_CONNECTING = 1;
811     final static int CONNECTION_STATE_CONNECTED = 2;
812     final static int CONNECTION_STATE_DISCONNECTING = 3;
813 
814     // match up with btav_audio_state_t enum of bt_av.h
815     final static int AUDIO_STATE_REMOTE_SUSPEND = 0;
816     final static int AUDIO_STATE_STOPPED = 1;
817     final static int AUDIO_STATE_STARTED = 2;
818 
classInitNative()819     private native static void classInitNative();
initNative()820     private native void initNative();
cleanupNative()821     private native void cleanupNative();
connectA2dpNative(byte[] address)822     private native boolean connectA2dpNative(byte[] address);
disconnectA2dpNative(byte[] address)823     private native boolean disconnectA2dpNative(byte[] address);
824 }
825