blob: ca2212cda581afb7a60ed4d3cd1520e8066fc9e9 [file] [log] [blame]
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001/*
2 * Copyright (C) 2008 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 * TODO: Move this to services.jar
Jake Hamby9a62c9c2010-12-09 14:47:57 -080019 * and make the constructor package private again.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080020 * @hide
21 */
22
23package android.server;
24
25import android.bluetooth.BluetoothA2dp;
Nick Pellybd022f42009-08-14 18:33:38 -070026import android.bluetooth.BluetoothAdapter;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080027import android.bluetooth.BluetoothDevice;
Jaikumar Ganesh96a79832010-09-27 17:02:01 -070028import android.bluetooth.BluetoothProfile;
Jaikumar Ganeshd5ac1ae2009-05-05 22:26:12 -070029import android.bluetooth.BluetoothUuid;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080030import android.bluetooth.IBluetoothA2dp;
31import android.content.BroadcastReceiver;
32import android.content.Context;
33import android.content.Intent;
34import android.content.IntentFilter;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080035import android.media.AudioManager;
Jaikumar Ganesh9b637e52010-06-02 14:36:14 -070036import android.os.ParcelUuid;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080037import android.provider.Settings;
38import android.util.Log;
39
40import java.io.FileDescriptor;
41import java.io.PrintWriter;
Jaikumar Ganesh96a79832010-09-27 17:02:01 -070042import java.util.ArrayList;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080043import java.util.HashMap;
Jaikumar Ganesh03cd78c2010-10-18 16:41:53 -070044import java.util.List;
Jaikumar Ganesh96a79832010-09-27 17:02:01 -070045
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080046
47public class BluetoothA2dpService extends IBluetoothA2dp.Stub {
48 private static final String TAG = "BluetoothA2dpService";
49 private static final boolean DBG = true;
50
51 public static final String BLUETOOTH_A2DP_SERVICE = "bluetooth_a2dp";
52
53 private static final String BLUETOOTH_ADMIN_PERM = android.Manifest.permission.BLUETOOTH_ADMIN;
54 private static final String BLUETOOTH_PERM = android.Manifest.permission.BLUETOOTH;
55
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080056 private static final String BLUETOOTH_ENABLED = "bluetooth_enabled";
57
Jaikumar Ganeshd5ac1ae2009-05-05 22:26:12 -070058 private static final String PROPERTY_STATE = "State";
59
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080060 private final Context mContext;
61 private final IntentFilter mIntentFilter;
Nick Pellybd022f42009-08-14 18:33:38 -070062 private HashMap<BluetoothDevice, Integer> mAudioDevices;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080063 private final AudioManager mAudioManager;
Nick Pellybd022f42009-08-14 18:33:38 -070064 private final BluetoothService mBluetoothService;
65 private final BluetoothAdapter mAdapter;
Eric Laurenta47d1532009-10-16 09:16:26 -070066 private int mTargetA2dpState;
Jaikumar Ganesh96a79832010-09-27 17:02:01 -070067 private BluetoothDevice mPlayingA2dpDevice;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080068
69 private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
70 @Override
71 public void onReceive(Context context, Intent intent) {
72 String action = intent.getAction();
Nick Pellybd022f42009-08-14 18:33:38 -070073 BluetoothDevice device =
Nick Pelly005b2282009-09-10 10:21:56 -070074 intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
Nick Pellyde893f52009-09-08 13:15:33 -070075 if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) {
76 int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE,
Nick Pellyb24e11b2009-09-08 17:40:43 -070077 BluetoothAdapter.ERROR);
The Android Open Source Project10592532009-03-18 17:39:46 -070078 switch (state) {
Nick Pellyde893f52009-09-08 13:15:33 -070079 case BluetoothAdapter.STATE_ON:
The Android Open Source Project10592532009-03-18 17:39:46 -070080 onBluetoothEnable();
81 break;
Nick Pellyde893f52009-09-08 13:15:33 -070082 case BluetoothAdapter.STATE_TURNING_OFF:
The Android Open Source Project10592532009-03-18 17:39:46 -070083 onBluetoothDisable();
84 break;
85 }
Jaikumar Ganesh4c9a29e2009-09-30 15:59:35 -070086 } else if (action.equals(BluetoothDevice.ACTION_ACL_DISCONNECTED)) {
87 synchronized (this) {
88 if (mAudioDevices.containsKey(device)) {
89 int state = mAudioDevices.get(device);
90 handleSinkStateChange(device, state, BluetoothA2dp.STATE_DISCONNECTED);
91 }
92 }
Jaikumar Ganesh4f773a12010-02-16 11:57:06 -080093 } else if (action.equals(AudioManager.VOLUME_CHANGED_ACTION)) {
94 int streamType = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, -1);
95 if (streamType == AudioManager.STREAM_MUSIC) {
Jaikumar Ganesh03cd78c2010-10-18 16:41:53 -070096 List<BluetoothDevice> sinks = getConnectedDevices();
Jaikumar Ganesh96a79832010-09-27 17:02:01 -070097
Jaikumar Ganesh03cd78c2010-10-18 16:41:53 -070098 if (sinks.size() != 0 && isPhoneDocked(sinks.get(0))) {
99 String address = sinks.get(0).getAddress();
Jaikumar Ganesh4f773a12010-02-16 11:57:06 -0800100 int newVolLevel =
101 intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_VALUE, 0);
102 int oldVolLevel =
103 intent.getIntExtra(AudioManager.EXTRA_PREV_VOLUME_STREAM_VALUE, 0);
104 String path = mBluetoothService.getObjectPathFromAddress(address);
105 if (newVolLevel > oldVolLevel) {
106 avrcpVolumeUpNative(path);
107 } else if (newVolLevel < oldVolLevel) {
108 avrcpVolumeDownNative(path);
109 }
110 }
111 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800112 }
113 }
114 };
115
Jaikumar Ganesh4f773a12010-02-16 11:57:06 -0800116 private boolean isPhoneDocked(BluetoothDevice device) {
117 // This works only because these broadcast intents are "sticky"
118 Intent i = mContext.registerReceiver(null, new IntentFilter(Intent.ACTION_DOCK_EVENT));
119 if (i != null) {
120 int state = i.getIntExtra(Intent.EXTRA_DOCK_STATE, Intent.EXTRA_DOCK_STATE_UNDOCKED);
121 if (state != Intent.EXTRA_DOCK_STATE_UNDOCKED) {
122 BluetoothDevice dockDevice = i.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
123 if (dockDevice != null && device.equals(dockDevice)) {
124 return true;
125 }
126 }
127 }
128 return false;
129 }
130
Nick Pellybd022f42009-08-14 18:33:38 -0700131 public BluetoothA2dpService(Context context, BluetoothService bluetoothService) {
Jaikumar Ganeshd5ac1ae2009-05-05 22:26:12 -0700132 mContext = context;
133
134 mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
135
136 mBluetoothService = bluetoothService;
137 if (mBluetoothService == null) {
138 throw new RuntimeException("Platform does not support Bluetooth");
139 }
140
141 if (!initNative()) {
142 throw new RuntimeException("Could not init BluetoothA2dpService");
143 }
144
Nick Pellyf242b7b2009-10-08 00:12:45 +0200145 mAdapter = BluetoothAdapter.getDefaultAdapter();
Nick Pellybd022f42009-08-14 18:33:38 -0700146
Nick Pellyde893f52009-09-08 13:15:33 -0700147 mIntentFilter = new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED);
Nick Pelly005b2282009-09-10 10:21:56 -0700148 mIntentFilter.addAction(BluetoothDevice.ACTION_ACL_CONNECTED);
Jaikumar Ganesh4c9a29e2009-09-30 15:59:35 -0700149 mIntentFilter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED);
Jaikumar Ganesh4f773a12010-02-16 11:57:06 -0800150 mIntentFilter.addAction(AudioManager.VOLUME_CHANGED_ACTION);
Jaikumar Ganeshd5ac1ae2009-05-05 22:26:12 -0700151 mContext.registerReceiver(mReceiver, mIntentFilter);
152
Nick Pellybd022f42009-08-14 18:33:38 -0700153 mAudioDevices = new HashMap<BluetoothDevice, Integer>();
Jaikumar Ganeshd5ac1ae2009-05-05 22:26:12 -0700154
155 if (mBluetoothService.isEnabled())
156 onBluetoothEnable();
Eric Laurenta47d1532009-10-16 09:16:26 -0700157 mTargetA2dpState = -1;
Jaikumar Ganesh9b637e52010-06-02 14:36:14 -0700158 mBluetoothService.setA2dpService(this);
Jaikumar Ganeshd5ac1ae2009-05-05 22:26:12 -0700159 }
160
161 @Override
162 protected void finalize() throws Throwable {
163 try {
164 cleanupNative();
165 } finally {
166 super.finalize();
167 }
168 }
169
Jake Hamby9a62c9c2010-12-09 14:47:57 -0800170 private int convertBluezSinkStringToState(String value) {
Jaikumar Ganeshd5ac1ae2009-05-05 22:26:12 -0700171 if (value.equalsIgnoreCase("disconnected"))
172 return BluetoothA2dp.STATE_DISCONNECTED;
173 if (value.equalsIgnoreCase("connecting"))
174 return BluetoothA2dp.STATE_CONNECTING;
175 if (value.equalsIgnoreCase("connected"))
176 return BluetoothA2dp.STATE_CONNECTED;
177 if (value.equalsIgnoreCase("playing"))
178 return BluetoothA2dp.STATE_PLAYING;
179 return -1;
180 }
181
Nick Pellybd022f42009-08-14 18:33:38 -0700182 private boolean isSinkDevice(BluetoothDevice device) {
Jaikumar Ganeshdd0463a2009-09-16 12:30:02 -0700183 ParcelUuid[] uuids = mBluetoothService.getRemoteUuids(device.getAddress());
184 if (uuids != null && BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.AudioSink)) {
185 return true;
Jaikumar Ganesh7f74d532009-07-14 12:21:26 -0700186 }
187 return false;
188 }
189
Jake Hamby9a62c9c2010-12-09 14:47:57 -0800190 private synchronized boolean addAudioSink(BluetoothDevice device) {
Nick Pellybd022f42009-08-14 18:33:38 -0700191 String path = mBluetoothService.getObjectPathFromAddress(device.getAddress());
Jaikumar Ganeshd5ac1ae2009-05-05 22:26:12 -0700192 String propValues[] = (String []) getSinkPropertiesNative(path);
193 if (propValues == null) {
Nick Pellybd022f42009-08-14 18:33:38 -0700194 Log.e(TAG, "Error while getting AudioSink properties for device: " + device);
Jaikumar Ganeshd5ac1ae2009-05-05 22:26:12 -0700195 return false;
196 }
197 Integer state = null;
198 // Properties are name-value pairs
199 for (int i = 0; i < propValues.length; i+=2) {
200 if (propValues[i].equals(PROPERTY_STATE)) {
Jake Hamby9a62c9c2010-12-09 14:47:57 -0800201 state = new Integer(convertBluezSinkStringToState(propValues[i+1]));
Jaikumar Ganeshd5ac1ae2009-05-05 22:26:12 -0700202 break;
203 }
204 }
Nick Pellybd022f42009-08-14 18:33:38 -0700205 mAudioDevices.put(device, state);
206 handleSinkStateChange(device, BluetoothA2dp.STATE_DISCONNECTED, state);
Jaikumar Ganeshd5ac1ae2009-05-05 22:26:12 -0700207 return true;
208 }
209
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800210 private synchronized void onBluetoothEnable() {
Jaikumar Ganeshd5ac1ae2009-05-05 22:26:12 -0700211 String devices = mBluetoothService.getProperty("Devices");
Jaikumar Ganeshd5ac1ae2009-05-05 22:26:12 -0700212 if (devices != null) {
213 String [] paths = devices.split(",");
214 for (String path: paths) {
215 String address = mBluetoothService.getAddressFromObjectPath(path);
Nick Pellybd022f42009-08-14 18:33:38 -0700216 BluetoothDevice device = mAdapter.getRemoteDevice(address);
Jaikumar Ganeshdd0463a2009-09-16 12:30:02 -0700217 ParcelUuid[] remoteUuids = mBluetoothService.getRemoteUuids(address);
218 if (remoteUuids != null)
219 if (BluetoothUuid.containsAnyUuid(remoteUuids,
220 new ParcelUuid[] {BluetoothUuid.AudioSink,
221 BluetoothUuid.AdvAudioDist})) {
222 addAudioSink(device);
Jaikumar Ganeshd5ac1ae2009-05-05 22:26:12 -0700223 }
Jaikumar Ganeshdd0463a2009-09-16 12:30:02 -0700224 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800225 }
Eric Laurenta553c252009-07-17 12:17:14 -0700226 mAudioManager.setParameters(BLUETOOTH_ENABLED+"=true");
Eric Laurent80a6a222009-10-08 10:58:19 -0700227 mAudioManager.setParameters("A2dpSuspended=false");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800228 }
229
230 private synchronized void onBluetoothDisable() {
Jaikumar Ganeshd5ac1ae2009-05-05 22:26:12 -0700231 if (!mAudioDevices.isEmpty()) {
Nick Pellybd022f42009-08-14 18:33:38 -0700232 BluetoothDevice[] devices = new BluetoothDevice[mAudioDevices.size()];
233 devices = mAudioDevices.keySet().toArray(devices);
234 for (BluetoothDevice device : devices) {
Jaikumar Ganesh96a79832010-09-27 17:02:01 -0700235 int state = getConnectionState(device);
Jaikumar Ganeshd5ac1ae2009-05-05 22:26:12 -0700236 switch (state) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800237 case BluetoothA2dp.STATE_CONNECTING:
238 case BluetoothA2dp.STATE_CONNECTED:
239 case BluetoothA2dp.STATE_PLAYING:
Nick Pellybd022f42009-08-14 18:33:38 -0700240 disconnectSinkNative(mBluetoothService.getObjectPathFromAddress(
241 device.getAddress()));
242 handleSinkStateChange(device, state, BluetoothA2dp.STATE_DISCONNECTED);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800243 break;
244 case BluetoothA2dp.STATE_DISCONNECTING:
Nick Pellybd022f42009-08-14 18:33:38 -0700245 handleSinkStateChange(device, BluetoothA2dp.STATE_DISCONNECTING,
246 BluetoothA2dp.STATE_DISCONNECTED);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800247 break;
248 }
249 }
Jaikumar Ganeshd5ac1ae2009-05-05 22:26:12 -0700250 mAudioDevices.clear();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800251 }
Eric Laurenta553c252009-07-17 12:17:14 -0700252
Nick Pellybd022f42009-08-14 18:33:38 -0700253 mAudioManager.setParameters(BLUETOOTH_ENABLED + "=false");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800254 }
255
Jaikumar Ganesh9b637e52010-06-02 14:36:14 -0700256 private synchronized boolean isConnectSinkFeasible(BluetoothDevice device) {
257 if (!mBluetoothService.isEnabled() || !isSinkDevice(device) ||
Jaikumar Ganesh96a79832010-09-27 17:02:01 -0700258 getPriority(device) == BluetoothA2dp.PRIORITY_OFF) {
Jake Hamby9a62c9c2010-12-09 14:47:57 -0800259 return false;
260 }
Jaikumar Ganesh9b637e52010-06-02 14:36:14 -0700261
Jake Hamby9a62c9c2010-12-09 14:47:57 -0800262 if (mAudioDevices.get(device) == null && !addAudioSink(device)) {
263 return false;
264 }
Jaikumar Ganesh9b637e52010-06-02 14:36:14 -0700265
Jake Hamby9a62c9c2010-12-09 14:47:57 -0800266 String path = mBluetoothService.getObjectPathFromAddress(device.getAddress());
267 if (path == null) {
268 return false;
269 }
270 return true;
Jaikumar Ganesh9b637e52010-06-02 14:36:14 -0700271 }
272
Jaikumar Ganesh96a79832010-09-27 17:02:01 -0700273 public synchronized boolean isA2dpPlaying(BluetoothDevice device) {
274 mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
275 "Need BLUETOOTH_ADMIN permission");
276 if (DBG) log("isA2dpPlaying(" + device + ")");
277 if (device.equals(mPlayingA2dpDevice)) return true;
278 return false;
279 }
280
281 public synchronized boolean connect(BluetoothDevice device) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800282 mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
283 "Need BLUETOOTH_ADMIN permission");
Nick Pellybd022f42009-08-14 18:33:38 -0700284 if (DBG) log("connectSink(" + device + ")");
Jaikumar Ganesh9b637e52010-06-02 14:36:14 -0700285 if (!isConnectSinkFeasible(device)) return false;
Jaikumar Ganeshd5ac1ae2009-05-05 22:26:12 -0700286
Jaikumar Ganesh96a79832010-09-27 17:02:01 -0700287 for (BluetoothDevice sinkDevice : mAudioDevices.keySet()) {
288 if (getConnectionState(sinkDevice) != BluetoothProfile.STATE_DISCONNECTED) {
289 disconnect(sinkDevice);
290 }
291 }
292
Jaikumar Ganesh9b637e52010-06-02 14:36:14 -0700293 return mBluetoothService.connectSink(device.getAddress());
294 }
295
296 public synchronized boolean connectSinkInternal(BluetoothDevice device) {
Jaikumar Ganesh4aae54c2010-04-07 17:28:08 -0700297 if (!mBluetoothService.isEnabled()) return false;
298
Jaikumar Ganesh9b637e52010-06-02 14:36:14 -0700299 int state = mAudioDevices.get(device);
300
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700301 // ignore if there are any active sinks
Jaikumar Ganesh96a79832010-09-27 17:02:01 -0700302 if (getDevicesMatchingConnectionStates(new int[] {
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700303 BluetoothA2dp.STATE_CONNECTING,
304 BluetoothA2dp.STATE_CONNECTED,
Jaikumar Ganesh03cd78c2010-10-18 16:41:53 -0700305 BluetoothA2dp.STATE_DISCONNECTING}).size() != 0) {
Nick Pellyb24e11b2009-09-08 17:40:43 -0700306 return false;
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700307 }
308
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800309 switch (state) {
310 case BluetoothA2dp.STATE_CONNECTED:
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800311 case BluetoothA2dp.STATE_DISCONNECTING:
Nick Pellyb24e11b2009-09-08 17:40:43 -0700312 return false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800313 case BluetoothA2dp.STATE_CONNECTING:
Nick Pellyb24e11b2009-09-08 17:40:43 -0700314 return true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800315 }
316
Nick Pellybd022f42009-08-14 18:33:38 -0700317 String path = mBluetoothService.getObjectPathFromAddress(device.getAddress());
Jaikumar Ganeshd5ac1ae2009-05-05 22:26:12 -0700318
Jaikumar Ganeshbcf5cf42010-10-12 15:15:55 -0700319 // State is DISCONNECTED and we are connecting.
Jaikumar Ganeshaa4b2352010-10-14 09:36:39 -0700320 if (getPriority(device) < BluetoothA2dp.PRIORITY_AUTO_CONNECT) {
321 setPriority(device, BluetoothA2dp.PRIORITY_AUTO_CONNECT);
Jaikumar Ganeshbcf5cf42010-10-12 15:15:55 -0700322 }
Jaikumar Ganeshc0e32f12009-12-16 11:36:39 -0800323 handleSinkStateChange(device, state, BluetoothA2dp.STATE_CONNECTING);
324
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800325 if (!connectSinkNative(path)) {
Jaikumar Ganeshc0e32f12009-12-16 11:36:39 -0800326 // Restore previous state
327 handleSinkStateChange(device, mAudioDevices.get(device), state);
Nick Pellyb24e11b2009-09-08 17:40:43 -0700328 return false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800329 }
Nick Pellyb24e11b2009-09-08 17:40:43 -0700330 return true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800331 }
332
Jaikumar Ganesh9b637e52010-06-02 14:36:14 -0700333 private synchronized boolean isDisconnectSinkFeasible(BluetoothDevice device) {
Nick Pellybd022f42009-08-14 18:33:38 -0700334 String path = mBluetoothService.getObjectPathFromAddress(device.getAddress());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800335 if (path == null) {
Nick Pellyb24e11b2009-09-08 17:40:43 -0700336 return false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800337 }
Jaikumar Ganeshd5ac1ae2009-05-05 22:26:12 -0700338
Jaikumar Ganesh96a79832010-09-27 17:02:01 -0700339 int state = getConnectionState(device);
Jaikumar Ganeshc0e32f12009-12-16 11:36:39 -0800340 switch (state) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800341 case BluetoothA2dp.STATE_DISCONNECTED:
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800342 case BluetoothA2dp.STATE_DISCONNECTING:
Jaikumar Ganesh96a79832010-09-27 17:02:01 -0700343 return false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800344 }
Jaikumar Ganesh9b637e52010-06-02 14:36:14 -0700345 return true;
346 }
347
Jaikumar Ganesh96a79832010-09-27 17:02:01 -0700348 public synchronized boolean disconnect(BluetoothDevice device) {
Jaikumar Ganesh9b637e52010-06-02 14:36:14 -0700349 mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
350 "Need BLUETOOTH_ADMIN permission");
351 if (DBG) log("disconnectSink(" + device + ")");
352 if (!isDisconnectSinkFeasible(device)) return false;
353 return mBluetoothService.disconnectSink(device.getAddress());
354 }
355
356 public synchronized boolean disconnectSinkInternal(BluetoothDevice device) {
Jaikumar Ganesh96a79832010-09-27 17:02:01 -0700357 int state = getConnectionState(device);
Jaikumar Ganesh9b637e52010-06-02 14:36:14 -0700358 String path = mBluetoothService.getObjectPathFromAddress(device.getAddress());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800359
Jaikumar Ganesh5844f1d2010-10-14 12:11:25 -0700360 switch (state) {
361 case BluetoothA2dp.STATE_DISCONNECTED:
362 case BluetoothA2dp.STATE_DISCONNECTING:
363 return false;
364 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800365 // State is CONNECTING or CONNECTED or PLAYING
Jaikumar Ganeshc0e32f12009-12-16 11:36:39 -0800366 handleSinkStateChange(device, state, BluetoothA2dp.STATE_DISCONNECTING);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800367 if (!disconnectSinkNative(path)) {
Jaikumar Ganeshc0e32f12009-12-16 11:36:39 -0800368 // Restore previous state
369 handleSinkStateChange(device, mAudioDevices.get(device), state);
Nick Pellyb24e11b2009-09-08 17:40:43 -0700370 return false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800371 }
Jaikumar Ganeshc0e32f12009-12-16 11:36:39 -0800372 return true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800373 }
374
Zhu Lanf9bbe1e2009-06-24 10:51:57 +0800375 public synchronized boolean suspendSink(BluetoothDevice device) {
376 mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
377 "Need BLUETOOTH_ADMIN permission");
Eric Laurenta47d1532009-10-16 09:16:26 -0700378 if (DBG) log("suspendSink(" + device + "), mTargetA2dpState: "+mTargetA2dpState);
Zhu Lanf9bbe1e2009-06-24 10:51:57 +0800379 if (device == null || mAudioDevices == null) {
380 return false;
381 }
382 String path = mBluetoothService.getObjectPathFromAddress(device.getAddress());
Nick Pelly62895a62009-09-30 15:30:43 -0700383 Integer state = mAudioDevices.get(device);
384 if (path == null || state == null) {
Zhu Lanf9bbe1e2009-06-24 10:51:57 +0800385 return false;
386 }
Eric Laurenta47d1532009-10-16 09:16:26 -0700387
388 mTargetA2dpState = BluetoothA2dp.STATE_CONNECTED;
389 return checkSinkSuspendState(state.intValue());
Zhu Lanf9bbe1e2009-06-24 10:51:57 +0800390 }
391
392 public synchronized boolean resumeSink(BluetoothDevice device) {
393 mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
394 "Need BLUETOOTH_ADMIN permission");
Eric Laurenta47d1532009-10-16 09:16:26 -0700395 if (DBG) log("resumeSink(" + device + "), mTargetA2dpState: "+mTargetA2dpState);
Zhu Lanf9bbe1e2009-06-24 10:51:57 +0800396 if (device == null || mAudioDevices == null) {
397 return false;
398 }
399 String path = mBluetoothService.getObjectPathFromAddress(device.getAddress());
Nick Pelly62895a62009-09-30 15:30:43 -0700400 Integer state = mAudioDevices.get(device);
401 if (path == null || state == null) {
Zhu Lanf9bbe1e2009-06-24 10:51:57 +0800402 return false;
403 }
Eric Laurenta47d1532009-10-16 09:16:26 -0700404 mTargetA2dpState = BluetoothA2dp.STATE_PLAYING;
405 return checkSinkSuspendState(state.intValue());
Zhu Lanf9bbe1e2009-06-24 10:51:57 +0800406 }
407
Jaikumar Ganesh96a79832010-09-27 17:02:01 -0700408 public synchronized int getConnectionState(BluetoothDevice device) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800409 mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
Nick Pellybd022f42009-08-14 18:33:38 -0700410 Integer state = mAudioDevices.get(device);
Jaikumar Ganeshd5ac1ae2009-05-05 22:26:12 -0700411 if (state == null)
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800412 return BluetoothA2dp.STATE_DISCONNECTED;
Jaikumar Ganeshd5ac1ae2009-05-05 22:26:12 -0700413 return state;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800414 }
415
Jaikumar Ganesh03cd78c2010-10-18 16:41:53 -0700416 public synchronized List<BluetoothDevice> getConnectedDevices() {
Jaikumar Ganesh96a79832010-09-27 17:02:01 -0700417 mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
Jaikumar Ganesh03cd78c2010-10-18 16:41:53 -0700418 List<BluetoothDevice> sinks = getDevicesMatchingConnectionStates(
Jaikumar Ganesh96a79832010-09-27 17:02:01 -0700419 new int[] {BluetoothA2dp.STATE_CONNECTED});
420 return sinks;
421 }
422
Jaikumar Ganesh03cd78c2010-10-18 16:41:53 -0700423 public synchronized List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
Jaikumar Ganesh96a79832010-09-27 17:02:01 -0700424 mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
Jaikumar Ganesh03cd78c2010-10-18 16:41:53 -0700425 ArrayList<BluetoothDevice> sinks = new ArrayList<BluetoothDevice>();
Jaikumar Ganesh96a79832010-09-27 17:02:01 -0700426 for (BluetoothDevice device: mAudioDevices.keySet()) {
427 int sinkState = getConnectionState(device);
428 for (int state : states) {
429 if (state == sinkState) {
430 sinks.add(device);
431 break;
432 }
433 }
434 }
Jaikumar Ganesh03cd78c2010-10-18 16:41:53 -0700435 return sinks;
Jaikumar Ganesh96a79832010-09-27 17:02:01 -0700436 }
437
438 public synchronized int getPriority(BluetoothDevice device) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800439 mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800440 return Settings.Secure.getInt(mContext.getContentResolver(),
Nick Pellybd022f42009-08-14 18:33:38 -0700441 Settings.Secure.getBluetoothA2dpSinkPriorityKey(device.getAddress()),
Jaikumar Ganeshccaebfc2010-01-08 18:26:14 -0800442 BluetoothA2dp.PRIORITY_UNDEFINED);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800443 }
444
Jaikumar Ganesh96a79832010-09-27 17:02:01 -0700445 public synchronized boolean setPriority(BluetoothDevice device, int priority) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800446 mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
447 "Need BLUETOOTH_ADMIN permission");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800448 return Settings.Secure.putInt(mContext.getContentResolver(),
Nick Pellyb24e11b2009-09-08 17:40:43 -0700449 Settings.Secure.getBluetoothA2dpSinkPriorityKey(device.getAddress()), priority);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800450 }
451
Jake Hamby9a62c9c2010-12-09 14:47:57 -0800452 /**
453 * Called by native code on a PropertyChanged signal from
454 * org.bluez.AudioSink.
455 *
456 * @param path the object path for the changed device
457 * @param propValues a string array containing the key and one or more
458 * values.
459 */
460 private synchronized void onSinkPropertyChanged(String path, String[] propValues) {
Jaikumar Ganesh9488cbd2009-08-03 17:17:37 -0700461 if (!mBluetoothService.isEnabled()) {
462 return;
463 }
464
Jaikumar Ganeshd5ac1ae2009-05-05 22:26:12 -0700465 String name = propValues[0];
466 String address = mBluetoothService.getAddressFromObjectPath(path);
467 if (address == null) {
468 Log.e(TAG, "onSinkPropertyChanged: Address of the remote device in null");
469 return;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800470 }
471
Nick Pellybd022f42009-08-14 18:33:38 -0700472 BluetoothDevice device = mAdapter.getRemoteDevice(address);
473
Jaikumar Ganeshd5ac1ae2009-05-05 22:26:12 -0700474 if (name.equals(PROPERTY_STATE)) {
Jake Hamby9a62c9c2010-12-09 14:47:57 -0800475 int state = convertBluezSinkStringToState(propValues[1]);
Jaikumar Ganesh8febf882010-10-26 00:28:39 -0700476 log("A2DP: onSinkPropertyChanged newState is: " + state + "mPlayingA2dpDevice: " + mPlayingA2dpDevice);
477
Nick Pellybd022f42009-08-14 18:33:38 -0700478 if (mAudioDevices.get(device) == null) {
Jaikumar Ganesh9488cbd2009-08-03 17:17:37 -0700479 // This is for an incoming connection for a device not known to us.
480 // We have authorized it and bluez state has changed.
Nick Pellybd022f42009-08-14 18:33:38 -0700481 addAudioSink(device);
Jaikumar Ganesh9488cbd2009-08-03 17:17:37 -0700482 } else {
Jaikumar Ganesh96a79832010-09-27 17:02:01 -0700483 if (state == BluetoothA2dp.STATE_PLAYING && mPlayingA2dpDevice == null) {
484 mPlayingA2dpDevice = device;
485 handleSinkPlayingStateChange(device, state, BluetoothA2dp.STATE_NOT_PLAYING);
486 } else if (state == BluetoothA2dp.STATE_CONNECTED && mPlayingA2dpDevice != null) {
487 mPlayingA2dpDevice = null;
488 handleSinkPlayingStateChange(device, BluetoothA2dp.STATE_NOT_PLAYING,
489 BluetoothA2dp.STATE_PLAYING);
490 } else {
Jaikumar Ganeshb2ec4fa2010-10-26 11:22:48 -0700491 mPlayingA2dpDevice = null;
Jaikumar Ganesh96a79832010-09-27 17:02:01 -0700492 int prevState = mAudioDevices.get(device);
493 handleSinkStateChange(device, prevState, state);
494 }
Jaikumar Ganesh9488cbd2009-08-03 17:17:37 -0700495 }
Mike Lockwooda0de3552009-03-24 21:08:33 -0700496 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800497 }
498
Nick Pellybd022f42009-08-14 18:33:38 -0700499 private void handleSinkStateChange(BluetoothDevice device, int prevState, int state) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800500 if (state != prevState) {
Nick Pellybd022f42009-08-14 18:33:38 -0700501 mAudioDevices.put(device, state);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800502
Eric Laurenta47d1532009-10-16 09:16:26 -0700503 checkSinkSuspendState(state);
504 mTargetA2dpState = -1;
Eric Laurent80a6a222009-10-08 10:58:19 -0700505
Jaikumar Ganesh96a79832010-09-27 17:02:01 -0700506 if (getPriority(device) > BluetoothA2dp.PRIORITY_OFF &&
Jaikumar Ganesh721361f2009-11-20 15:21:47 -0800507 state == BluetoothA2dp.STATE_CONNECTED) {
508 // We have connected or attempting to connect.
509 // Bump priority
Jaikumar Ganesh96a79832010-09-27 17:02:01 -0700510 setPriority(device, BluetoothA2dp.PRIORITY_AUTO_CONNECT);
Jaikumar Ganesh9b637e52010-06-02 14:36:14 -0700511 // We will only have 1 device with AUTO_CONNECT priority
512 // To be backward compatible set everyone else to have PRIORITY_ON
513 adjustOtherSinkPriorities(device);
514 }
515
Jaikumar Ganesh96a79832010-09-27 17:02:01 -0700516 Intent intent = new Intent(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED);
Nick Pelly005b2282009-09-10 10:21:56 -0700517 intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
Jaikumar Ganesh96a79832010-09-27 17:02:01 -0700518 intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, prevState);
519 intent.putExtra(BluetoothProfile.EXTRA_STATE, state);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800520 mContext.sendBroadcast(intent, BLUETOOTH_PERM);
521
Nick Pellybd022f42009-08-14 18:33:38 -0700522 if (DBG) log("A2DP state : device: " + device + " State:" + prevState + "->" + state);
Jaikumar Ganesha46f2fb2010-10-21 14:55:05 -0700523
524 mBluetoothService.sendConnectionStateChange(device, state, prevState);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800525 }
526 }
527
Jaikumar Ganesh96a79832010-09-27 17:02:01 -0700528 private void handleSinkPlayingStateChange(BluetoothDevice device, int state, int prevState) {
529 Intent intent = new Intent(BluetoothA2dp.ACTION_PLAYING_STATE_CHANGED);
530 intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
531 intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, prevState);
532 intent.putExtra(BluetoothProfile.EXTRA_STATE, state);
533 mContext.sendBroadcast(intent, BLUETOOTH_PERM);
534
535 if (DBG) log("A2DP Playing state : device: " + device + " State:" + prevState + "->" + state);
536 }
537
Jaikumar Ganesh9b637e52010-06-02 14:36:14 -0700538 private void adjustOtherSinkPriorities(BluetoothDevice connectedDevice) {
Jaikumar Ganeshaa4b2352010-10-14 09:36:39 -0700539 for (BluetoothDevice device : mAdapter.getBondedDevices()) {
540 if (getPriority(device) >= BluetoothA2dp.PRIORITY_AUTO_CONNECT &&
541 !device.equals(connectedDevice)) {
542 setPriority(device, BluetoothA2dp.PRIORITY_ON);
Jaikumar Ganesh9b637e52010-06-02 14:36:14 -0700543 }
Jaikumar Ganesh9b637e52010-06-02 14:36:14 -0700544 }
545 }
546
Eric Laurenta47d1532009-10-16 09:16:26 -0700547 private boolean checkSinkSuspendState(int state) {
548 boolean result = true;
549
550 if (state != mTargetA2dpState) {
551 if (state == BluetoothA2dp.STATE_PLAYING &&
552 mTargetA2dpState == BluetoothA2dp.STATE_CONNECTED) {
553 mAudioManager.setParameters("A2dpSuspended=true");
554 } else if (state == BluetoothA2dp.STATE_CONNECTED &&
555 mTargetA2dpState == BluetoothA2dp.STATE_PLAYING) {
556 mAudioManager.setParameters("A2dpSuspended=false");
557 } else {
558 result = false;
559 }
560 }
561 return result;
562 }
563
Jake Hamby9a62c9c2010-12-09 14:47:57 -0800564 /**
565 * Called by native code for the async response to a Connect
566 * method call to org.bluez.AudioSink.
567 *
568 * @param deviceObjectPath the object path for the connecting device
569 * @param result true on success; false on error
570 */
Jaikumar Ganeshc0e32f12009-12-16 11:36:39 -0800571 private void onConnectSinkResult(String deviceObjectPath, boolean result) {
572 // If the call was a success, ignore we will update the state
573 // when we a Sink Property Change
574 if (!result) {
575 if (deviceObjectPath != null) {
576 String address = mBluetoothService.getAddressFromObjectPath(deviceObjectPath);
Jaikumar Ganeshc0cec622010-04-19 17:19:31 -0700577 if (address == null) return;
Jaikumar Ganeshc0e32f12009-12-16 11:36:39 -0800578 BluetoothDevice device = mAdapter.getRemoteDevice(address);
Jaikumar Ganesh96a79832010-09-27 17:02:01 -0700579 int state = getConnectionState(device);
Jaikumar Ganeshc0e32f12009-12-16 11:36:39 -0800580 handleSinkStateChange(device, state, BluetoothA2dp.STATE_DISCONNECTED);
581 }
582 }
583 }
584
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800585 @Override
586 protected synchronized void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
Jaikumar Ganeshd5ac1ae2009-05-05 22:26:12 -0700587 if (mAudioDevices.isEmpty()) return;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800588 pw.println("Cached audio devices:");
Nick Pellybd022f42009-08-14 18:33:38 -0700589 for (BluetoothDevice device : mAudioDevices.keySet()) {
590 int state = mAudioDevices.get(device);
591 pw.println(device + " " + BluetoothA2dp.stateToString(state));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800592 }
593 }
594
595 private static void log(String msg) {
596 Log.d(TAG, msg);
597 }
598
599 private native boolean initNative();
600 private native void cleanupNative();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800601 private synchronized native boolean connectSinkNative(String path);
602 private synchronized native boolean disconnectSinkNative(String path);
Zhu Lanf9bbe1e2009-06-24 10:51:57 +0800603 private synchronized native boolean suspendSinkNative(String path);
604 private synchronized native boolean resumeSinkNative(String path);
Jaikumar Ganeshd5ac1ae2009-05-05 22:26:12 -0700605 private synchronized native Object []getSinkPropertiesNative(String path);
Jaikumar Ganesh4f773a12010-02-16 11:57:06 -0800606 private synchronized native boolean avrcpVolumeUpNative(String path);
607 private synchronized native boolean avrcpVolumeDownNative(String path);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800608}