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