blob: a52a221e01aa41decba956f0b7d00f999370c07b [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
19 * and make the contructor package private again.
20 * @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 Ganeshd5ac1ae2009-05-05 22:26:12 -070028import android.bluetooth.BluetoothUuid;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080029import android.bluetooth.IBluetoothA2dp;
30import android.content.BroadcastReceiver;
31import android.content.Context;
32import android.content.Intent;
33import android.content.IntentFilter;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080034import android.media.AudioManager;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080035import android.os.Handler;
36import android.os.Message;
Jaikumar Ganesh9b637e52010-06-02 14:36:14 -070037import android.os.ParcelUuid;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080038import android.provider.Settings;
39import android.util.Log;
40
41import java.io.FileDescriptor;
42import java.io.PrintWriter;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080043import java.util.HashMap;
Nick Pellybd022f42009-08-14 18:33:38 -070044import java.util.HashSet;
45import java.util.Set;
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
60 private static final String SINK_STATE_DISCONNECTED = "disconnected";
61 private static final String SINK_STATE_CONNECTING = "connecting";
62 private static final String SINK_STATE_CONNECTED = "connected";
63 private static final String SINK_STATE_PLAYING = "playing";
64
65 private static int mSinkCount;
66
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080067 private final Context mContext;
68 private final IntentFilter mIntentFilter;
Nick Pellybd022f42009-08-14 18:33:38 -070069 private HashMap<BluetoothDevice, Integer> mAudioDevices;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080070 private final AudioManager mAudioManager;
Nick Pellybd022f42009-08-14 18:33:38 -070071 private final BluetoothService mBluetoothService;
72 private final BluetoothAdapter mAdapter;
Eric Laurenta47d1532009-10-16 09:16:26 -070073 private int mTargetA2dpState;
Jaikumar Ganesh9b637e52010-06-02 14:36:14 -070074 private boolean mAdjustedPriority = false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080075
76 private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
77 @Override
78 public void onReceive(Context context, Intent intent) {
79 String action = intent.getAction();
Nick Pellybd022f42009-08-14 18:33:38 -070080 BluetoothDevice device =
Nick Pelly005b2282009-09-10 10:21:56 -070081 intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
Nick Pellyde893f52009-09-08 13:15:33 -070082 if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) {
83 int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE,
Nick Pellyb24e11b2009-09-08 17:40:43 -070084 BluetoothAdapter.ERROR);
The Android Open Source Project10592532009-03-18 17:39:46 -070085 switch (state) {
Nick Pellyde893f52009-09-08 13:15:33 -070086 case BluetoothAdapter.STATE_ON:
The Android Open Source Project10592532009-03-18 17:39:46 -070087 onBluetoothEnable();
88 break;
Nick Pellyde893f52009-09-08 13:15:33 -070089 case BluetoothAdapter.STATE_TURNING_OFF:
The Android Open Source Project10592532009-03-18 17:39:46 -070090 onBluetoothDisable();
91 break;
92 }
Nick Pelly005b2282009-09-10 10:21:56 -070093 } else if (action.equals(BluetoothDevice.ACTION_BOND_STATE_CHANGED)) {
94 int bondState = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE,
Nick Pellyb24e11b2009-09-08 17:40:43 -070095 BluetoothDevice.ERROR);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080096 switch(bondState) {
97 case BluetoothDevice.BOND_BONDED:
Jaikumar Ganesh551ed722009-12-11 12:00:31 -080098 if (getSinkPriority(device) == BluetoothA2dp.PRIORITY_UNDEFINED) {
99 setSinkPriority(device, BluetoothA2dp.PRIORITY_ON);
100 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800101 break;
Nick Pelly005b2282009-09-10 10:21:56 -0700102 case BluetoothDevice.BOND_NONE:
Jaikumar Ganesh551ed722009-12-11 12:00:31 -0800103 setSinkPriority(device, BluetoothA2dp.PRIORITY_UNDEFINED);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800104 break;
105 }
Jaikumar Ganesh4c9a29e2009-09-30 15:59:35 -0700106 } else if (action.equals(BluetoothDevice.ACTION_ACL_DISCONNECTED)) {
107 synchronized (this) {
108 if (mAudioDevices.containsKey(device)) {
109 int state = mAudioDevices.get(device);
110 handleSinkStateChange(device, state, BluetoothA2dp.STATE_DISCONNECTED);
111 }
112 }
Jaikumar Ganesh4f773a12010-02-16 11:57:06 -0800113 } else if (action.equals(AudioManager.VOLUME_CHANGED_ACTION)) {
114 int streamType = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, -1);
115 if (streamType == AudioManager.STREAM_MUSIC) {
116 BluetoothDevice sinks[] = getConnectedSinks();
117 if (sinks.length != 0 && isPhoneDocked(sinks[0])) {
118 String address = sinks[0].getAddress();
119 int newVolLevel =
120 intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_VALUE, 0);
121 int oldVolLevel =
122 intent.getIntExtra(AudioManager.EXTRA_PREV_VOLUME_STREAM_VALUE, 0);
123 String path = mBluetoothService.getObjectPathFromAddress(address);
124 if (newVolLevel > oldVolLevel) {
125 avrcpVolumeUpNative(path);
126 } else if (newVolLevel < oldVolLevel) {
127 avrcpVolumeDownNative(path);
128 }
129 }
130 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800131 }
132 }
133 };
134
Jaikumar Ganesh4f773a12010-02-16 11:57:06 -0800135
136 private boolean isPhoneDocked(BluetoothDevice device) {
137 // This works only because these broadcast intents are "sticky"
138 Intent i = mContext.registerReceiver(null, new IntentFilter(Intent.ACTION_DOCK_EVENT));
139 if (i != null) {
140 int state = i.getIntExtra(Intent.EXTRA_DOCK_STATE, Intent.EXTRA_DOCK_STATE_UNDOCKED);
141 if (state != Intent.EXTRA_DOCK_STATE_UNDOCKED) {
142 BluetoothDevice dockDevice = i.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
143 if (dockDevice != null && device.equals(dockDevice)) {
144 return true;
145 }
146 }
147 }
148 return false;
149 }
150
Nick Pellybd022f42009-08-14 18:33:38 -0700151 public BluetoothA2dpService(Context context, BluetoothService bluetoothService) {
Jaikumar Ganeshd5ac1ae2009-05-05 22:26:12 -0700152 mContext = context;
153
154 mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
155
156 mBluetoothService = bluetoothService;
157 if (mBluetoothService == null) {
158 throw new RuntimeException("Platform does not support Bluetooth");
159 }
160
161 if (!initNative()) {
162 throw new RuntimeException("Could not init BluetoothA2dpService");
163 }
164
Nick Pellyf242b7b2009-10-08 00:12:45 +0200165 mAdapter = BluetoothAdapter.getDefaultAdapter();
Nick Pellybd022f42009-08-14 18:33:38 -0700166
Nick Pellyde893f52009-09-08 13:15:33 -0700167 mIntentFilter = new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED);
Nick Pelly005b2282009-09-10 10:21:56 -0700168 mIntentFilter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
169 mIntentFilter.addAction(BluetoothDevice.ACTION_ACL_CONNECTED);
Jaikumar Ganesh4c9a29e2009-09-30 15:59:35 -0700170 mIntentFilter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED);
Jaikumar Ganesh4f773a12010-02-16 11:57:06 -0800171 mIntentFilter.addAction(AudioManager.VOLUME_CHANGED_ACTION);
Jaikumar Ganeshd5ac1ae2009-05-05 22:26:12 -0700172 mContext.registerReceiver(mReceiver, mIntentFilter);
173
Nick Pellybd022f42009-08-14 18:33:38 -0700174 mAudioDevices = new HashMap<BluetoothDevice, Integer>();
Jaikumar Ganeshd5ac1ae2009-05-05 22:26:12 -0700175
176 if (mBluetoothService.isEnabled())
177 onBluetoothEnable();
Eric Laurenta47d1532009-10-16 09:16:26 -0700178 mTargetA2dpState = -1;
Jaikumar Ganesh9b637e52010-06-02 14:36:14 -0700179 mBluetoothService.setA2dpService(this);
Jaikumar Ganeshd5ac1ae2009-05-05 22:26:12 -0700180 }
181
182 @Override
183 protected void finalize() throws Throwable {
184 try {
185 cleanupNative();
186 } finally {
187 super.finalize();
188 }
189 }
190
Jaikumar Ganeshd5ac1ae2009-05-05 22:26:12 -0700191 private int convertBluezSinkStringtoState(String value) {
192 if (value.equalsIgnoreCase("disconnected"))
193 return BluetoothA2dp.STATE_DISCONNECTED;
194 if (value.equalsIgnoreCase("connecting"))
195 return BluetoothA2dp.STATE_CONNECTING;
196 if (value.equalsIgnoreCase("connected"))
197 return BluetoothA2dp.STATE_CONNECTED;
198 if (value.equalsIgnoreCase("playing"))
199 return BluetoothA2dp.STATE_PLAYING;
200 return -1;
201 }
202
Nick Pellybd022f42009-08-14 18:33:38 -0700203 private boolean isSinkDevice(BluetoothDevice device) {
Jaikumar Ganeshdd0463a2009-09-16 12:30:02 -0700204 ParcelUuid[] uuids = mBluetoothService.getRemoteUuids(device.getAddress());
205 if (uuids != null && BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.AudioSink)) {
206 return true;
Jaikumar Ganesh7f74d532009-07-14 12:21:26 -0700207 }
208 return false;
209 }
210
Nick Pellybd022f42009-08-14 18:33:38 -0700211 private synchronized boolean addAudioSink (BluetoothDevice device) {
212 String path = mBluetoothService.getObjectPathFromAddress(device.getAddress());
Jaikumar Ganeshd5ac1ae2009-05-05 22:26:12 -0700213 String propValues[] = (String []) getSinkPropertiesNative(path);
214 if (propValues == null) {
Nick Pellybd022f42009-08-14 18:33:38 -0700215 Log.e(TAG, "Error while getting AudioSink properties for device: " + device);
Jaikumar Ganeshd5ac1ae2009-05-05 22:26:12 -0700216 return false;
217 }
218 Integer state = null;
219 // Properties are name-value pairs
220 for (int i = 0; i < propValues.length; i+=2) {
221 if (propValues[i].equals(PROPERTY_STATE)) {
222 state = new Integer(convertBluezSinkStringtoState(propValues[i+1]));
223 break;
224 }
225 }
Nick Pellybd022f42009-08-14 18:33:38 -0700226 mAudioDevices.put(device, state);
227 handleSinkStateChange(device, BluetoothA2dp.STATE_DISCONNECTED, state);
Jaikumar Ganeshd5ac1ae2009-05-05 22:26:12 -0700228 return true;
229 }
230
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800231 private synchronized void onBluetoothEnable() {
Jaikumar Ganeshd5ac1ae2009-05-05 22:26:12 -0700232 String devices = mBluetoothService.getProperty("Devices");
233 mSinkCount = 0;
234 if (devices != null) {
235 String [] paths = devices.split(",");
236 for (String path: paths) {
237 String address = mBluetoothService.getAddressFromObjectPath(path);
Nick Pellybd022f42009-08-14 18:33:38 -0700238 BluetoothDevice device = mAdapter.getRemoteDevice(address);
Jaikumar Ganeshdd0463a2009-09-16 12:30:02 -0700239 ParcelUuid[] remoteUuids = mBluetoothService.getRemoteUuids(address);
240 if (remoteUuids != null)
241 if (BluetoothUuid.containsAnyUuid(remoteUuids,
242 new ParcelUuid[] {BluetoothUuid.AudioSink,
243 BluetoothUuid.AdvAudioDist})) {
244 addAudioSink(device);
Jaikumar Ganeshd5ac1ae2009-05-05 22:26:12 -0700245 }
Jaikumar Ganeshdd0463a2009-09-16 12:30:02 -0700246 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800247 }
Eric Laurenta553c252009-07-17 12:17:14 -0700248 mAudioManager.setParameters(BLUETOOTH_ENABLED+"=true");
Eric Laurent80a6a222009-10-08 10:58:19 -0700249 mAudioManager.setParameters("A2dpSuspended=false");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800250 }
251
252 private synchronized void onBluetoothDisable() {
Jaikumar Ganeshd5ac1ae2009-05-05 22:26:12 -0700253 if (!mAudioDevices.isEmpty()) {
Nick Pellybd022f42009-08-14 18:33:38 -0700254 BluetoothDevice[] devices = new BluetoothDevice[mAudioDevices.size()];
255 devices = mAudioDevices.keySet().toArray(devices);
256 for (BluetoothDevice device : devices) {
257 int state = getSinkState(device);
Jaikumar Ganeshd5ac1ae2009-05-05 22:26:12 -0700258 switch (state) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800259 case BluetoothA2dp.STATE_CONNECTING:
260 case BluetoothA2dp.STATE_CONNECTED:
261 case BluetoothA2dp.STATE_PLAYING:
Nick Pellybd022f42009-08-14 18:33:38 -0700262 disconnectSinkNative(mBluetoothService.getObjectPathFromAddress(
263 device.getAddress()));
264 handleSinkStateChange(device, state, BluetoothA2dp.STATE_DISCONNECTED);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800265 break;
266 case BluetoothA2dp.STATE_DISCONNECTING:
Nick Pellybd022f42009-08-14 18:33:38 -0700267 handleSinkStateChange(device, BluetoothA2dp.STATE_DISCONNECTING,
268 BluetoothA2dp.STATE_DISCONNECTED);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800269 break;
270 }
271 }
Jaikumar Ganeshd5ac1ae2009-05-05 22:26:12 -0700272 mAudioDevices.clear();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800273 }
Eric Laurenta553c252009-07-17 12:17:14 -0700274
Nick Pellybd022f42009-08-14 18:33:38 -0700275 mAudioManager.setParameters(BLUETOOTH_ENABLED + "=false");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800276 }
277
Jaikumar Ganesh9b637e52010-06-02 14:36:14 -0700278 private synchronized boolean isConnectSinkFeasible(BluetoothDevice device) {
279 if (!mBluetoothService.isEnabled() || !isSinkDevice(device) ||
280 getSinkPriority(device) == BluetoothA2dp.PRIORITY_OFF) {
281 return false;
282 }
283
284 if (mAudioDevices.get(device) == null && !addAudioSink(device)) {
285 return false;
286 }
287
288 String path = mBluetoothService.getObjectPathFromAddress(device.getAddress());
289 if (path == null) {
290 return false;
291 }
292 return true;
293 }
294
Nick Pellyb24e11b2009-09-08 17:40:43 -0700295 public synchronized boolean connectSink(BluetoothDevice device) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800296 mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
297 "Need BLUETOOTH_ADMIN permission");
Nick Pellybd022f42009-08-14 18:33:38 -0700298 if (DBG) log("connectSink(" + device + ")");
Jaikumar Ganesh9b637e52010-06-02 14:36:14 -0700299 if (!isConnectSinkFeasible(device)) return false;
Jaikumar Ganeshd5ac1ae2009-05-05 22:26:12 -0700300
Jaikumar Ganesh9b637e52010-06-02 14:36:14 -0700301 return mBluetoothService.connectSink(device.getAddress());
302 }
303
304 public synchronized boolean connectSinkInternal(BluetoothDevice device) {
Jaikumar Ganesh4aae54c2010-04-07 17:28:08 -0700305 if (!mBluetoothService.isEnabled()) return false;
306
Jaikumar Ganesh9b637e52010-06-02 14:36:14 -0700307 int state = mAudioDevices.get(device);
308
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700309 // ignore if there are any active sinks
310 if (lookupSinksMatchingStates(new int[] {
311 BluetoothA2dp.STATE_CONNECTING,
312 BluetoothA2dp.STATE_CONNECTED,
313 BluetoothA2dp.STATE_PLAYING,
314 BluetoothA2dp.STATE_DISCONNECTING}).size() != 0) {
Nick Pellyb24e11b2009-09-08 17:40:43 -0700315 return false;
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700316 }
317
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800318 switch (state) {
319 case BluetoothA2dp.STATE_CONNECTED:
320 case BluetoothA2dp.STATE_PLAYING:
321 case BluetoothA2dp.STATE_DISCONNECTING:
Nick Pellyb24e11b2009-09-08 17:40:43 -0700322 return false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800323 case BluetoothA2dp.STATE_CONNECTING:
Nick Pellyb24e11b2009-09-08 17:40:43 -0700324 return true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800325 }
326
Nick Pellybd022f42009-08-14 18:33:38 -0700327 String path = mBluetoothService.getObjectPathFromAddress(device.getAddress());
Jaikumar Ganeshd5ac1ae2009-05-05 22:26:12 -0700328
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800329 // State is DISCONNECTED
Jaikumar Ganeshc0e32f12009-12-16 11:36:39 -0800330 handleSinkStateChange(device, state, BluetoothA2dp.STATE_CONNECTING);
331
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800332 if (!connectSinkNative(path)) {
Jaikumar Ganeshc0e32f12009-12-16 11:36:39 -0800333 // Restore previous state
334 handleSinkStateChange(device, mAudioDevices.get(device), state);
Nick Pellyb24e11b2009-09-08 17:40:43 -0700335 return false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800336 }
Nick Pellyb24e11b2009-09-08 17:40:43 -0700337 return true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800338 }
339
Jaikumar Ganesh9b637e52010-06-02 14:36:14 -0700340 private synchronized boolean isDisconnectSinkFeasible(BluetoothDevice device) {
Nick Pellybd022f42009-08-14 18:33:38 -0700341 String path = mBluetoothService.getObjectPathFromAddress(device.getAddress());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800342 if (path == null) {
Nick Pellyb24e11b2009-09-08 17:40:43 -0700343 return false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800344 }
Jaikumar Ganeshd5ac1ae2009-05-05 22:26:12 -0700345
Jaikumar Ganeshc0e32f12009-12-16 11:36:39 -0800346 int state = getSinkState(device);
347 switch (state) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800348 case BluetoothA2dp.STATE_DISCONNECTED:
Nick Pellyb24e11b2009-09-08 17:40:43 -0700349 return false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800350 case BluetoothA2dp.STATE_DISCONNECTING:
Nick Pellyb24e11b2009-09-08 17:40:43 -0700351 return true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800352 }
Jaikumar Ganesh9b637e52010-06-02 14:36:14 -0700353 return true;
354 }
355
356 public synchronized boolean disconnectSink(BluetoothDevice device) {
357 mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
358 "Need BLUETOOTH_ADMIN permission");
359 if (DBG) log("disconnectSink(" + device + ")");
360 if (!isDisconnectSinkFeasible(device)) return false;
361 return mBluetoothService.disconnectSink(device.getAddress());
362 }
363
364 public synchronized boolean disconnectSinkInternal(BluetoothDevice device) {
365 int state = getSinkState(device);
366 String path = mBluetoothService.getObjectPathFromAddress(device.getAddress());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800367
368 // State is CONNECTING or CONNECTED or PLAYING
Jaikumar Ganeshc0e32f12009-12-16 11:36:39 -0800369 handleSinkStateChange(device, state, BluetoothA2dp.STATE_DISCONNECTING);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800370 if (!disconnectSinkNative(path)) {
Jaikumar Ganeshc0e32f12009-12-16 11:36:39 -0800371 // Restore previous state
372 handleSinkStateChange(device, mAudioDevices.get(device), state);
Nick Pellyb24e11b2009-09-08 17:40:43 -0700373 return false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800374 }
Jaikumar Ganeshc0e32f12009-12-16 11:36:39 -0800375 return true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800376 }
377
Zhu Lanf9bbe1e2009-06-24 10:51:57 +0800378 public synchronized boolean suspendSink(BluetoothDevice device) {
379 mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
380 "Need BLUETOOTH_ADMIN permission");
Eric Laurenta47d1532009-10-16 09:16:26 -0700381 if (DBG) log("suspendSink(" + device + "), mTargetA2dpState: "+mTargetA2dpState);
Zhu Lanf9bbe1e2009-06-24 10:51:57 +0800382 if (device == null || mAudioDevices == null) {
383 return false;
384 }
385 String path = mBluetoothService.getObjectPathFromAddress(device.getAddress());
Nick Pelly62895a62009-09-30 15:30:43 -0700386 Integer state = mAudioDevices.get(device);
387 if (path == null || state == null) {
Zhu Lanf9bbe1e2009-06-24 10:51:57 +0800388 return false;
389 }
Eric Laurenta47d1532009-10-16 09:16:26 -0700390
391 mTargetA2dpState = BluetoothA2dp.STATE_CONNECTED;
392 return checkSinkSuspendState(state.intValue());
Zhu Lanf9bbe1e2009-06-24 10:51:57 +0800393 }
394
395 public synchronized boolean resumeSink(BluetoothDevice device) {
396 mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
397 "Need BLUETOOTH_ADMIN permission");
Eric Laurenta47d1532009-10-16 09:16:26 -0700398 if (DBG) log("resumeSink(" + device + "), mTargetA2dpState: "+mTargetA2dpState);
Zhu Lanf9bbe1e2009-06-24 10:51:57 +0800399 if (device == null || mAudioDevices == null) {
400 return false;
401 }
402 String path = mBluetoothService.getObjectPathFromAddress(device.getAddress());
Nick Pelly62895a62009-09-30 15:30:43 -0700403 Integer state = mAudioDevices.get(device);
404 if (path == null || state == null) {
Zhu Lanf9bbe1e2009-06-24 10:51:57 +0800405 return false;
406 }
Eric Laurenta47d1532009-10-16 09:16:26 -0700407 mTargetA2dpState = BluetoothA2dp.STATE_PLAYING;
408 return checkSinkSuspendState(state.intValue());
Zhu Lanf9bbe1e2009-06-24 10:51:57 +0800409 }
410
Nick Pellybd022f42009-08-14 18:33:38 -0700411 public synchronized BluetoothDevice[] getConnectedSinks() {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800412 mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
Nick Pellybd022f42009-08-14 18:33:38 -0700413 Set<BluetoothDevice> sinks = lookupSinksMatchingStates(
414 new int[] {BluetoothA2dp.STATE_CONNECTED, BluetoothA2dp.STATE_PLAYING});
415 return sinks.toArray(new BluetoothDevice[sinks.size()]);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800416 }
417
Jaikumar Ganeshb16c4f72009-12-04 15:10:54 -0800418 public synchronized BluetoothDevice[] getNonDisconnectedSinks() {
419 mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
420 Set<BluetoothDevice> sinks = lookupSinksMatchingStates(
421 new int[] {BluetoothA2dp.STATE_CONNECTED,
422 BluetoothA2dp.STATE_PLAYING,
423 BluetoothA2dp.STATE_CONNECTING,
424 BluetoothA2dp.STATE_DISCONNECTING});
425 return sinks.toArray(new BluetoothDevice[sinks.size()]);
426 }
427
Nick Pellybd022f42009-08-14 18:33:38 -0700428 public synchronized int getSinkState(BluetoothDevice device) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800429 mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
Nick Pellybd022f42009-08-14 18:33:38 -0700430 Integer state = mAudioDevices.get(device);
Jaikumar Ganeshd5ac1ae2009-05-05 22:26:12 -0700431 if (state == null)
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800432 return BluetoothA2dp.STATE_DISCONNECTED;
Jaikumar Ganeshd5ac1ae2009-05-05 22:26:12 -0700433 return state;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800434 }
435
Nick Pellybd022f42009-08-14 18:33:38 -0700436 public synchronized int getSinkPriority(BluetoothDevice device) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800437 mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800438 return Settings.Secure.getInt(mContext.getContentResolver(),
Nick Pellybd022f42009-08-14 18:33:38 -0700439 Settings.Secure.getBluetoothA2dpSinkPriorityKey(device.getAddress()),
Jaikumar Ganeshccaebfc2010-01-08 18:26:14 -0800440 BluetoothA2dp.PRIORITY_UNDEFINED);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800441 }
442
Nick Pellyb24e11b2009-09-08 17:40:43 -0700443 public synchronized boolean setSinkPriority(BluetoothDevice device, int priority) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800444 mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
445 "Need BLUETOOTH_ADMIN permission");
Nick Pelly005b2282009-09-10 10:21:56 -0700446 if (!BluetoothAdapter.checkBluetoothAddress(device.getAddress())) {
Nick Pellyb24e11b2009-09-08 17:40:43 -0700447 return false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800448 }
449 return Settings.Secure.putInt(mContext.getContentResolver(),
Nick Pellyb24e11b2009-09-08 17:40:43 -0700450 Settings.Secure.getBluetoothA2dpSinkPriorityKey(device.getAddress()), priority);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800451 }
452
Jaikumar Ganeshd5ac1ae2009-05-05 22:26:12 -0700453 private synchronized void onSinkPropertyChanged(String path, String []propValues) {
Jaikumar Ganesh9488cbd2009-08-03 17:17:37 -0700454 if (!mBluetoothService.isEnabled()) {
455 return;
456 }
457
Jaikumar Ganeshd5ac1ae2009-05-05 22:26:12 -0700458 String name = propValues[0];
459 String address = mBluetoothService.getAddressFromObjectPath(path);
460 if (address == null) {
461 Log.e(TAG, "onSinkPropertyChanged: Address of the remote device in null");
462 return;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800463 }
464
Nick Pellybd022f42009-08-14 18:33:38 -0700465 BluetoothDevice device = mAdapter.getRemoteDevice(address);
466
Jaikumar Ganeshd5ac1ae2009-05-05 22:26:12 -0700467 if (name.equals(PROPERTY_STATE)) {
468 int state = convertBluezSinkStringtoState(propValues[1]);
Nick Pellybd022f42009-08-14 18:33:38 -0700469 if (mAudioDevices.get(device) == null) {
Jaikumar Ganesh9488cbd2009-08-03 17:17:37 -0700470 // This is for an incoming connection for a device not known to us.
471 // We have authorized it and bluez state has changed.
Nick Pellybd022f42009-08-14 18:33:38 -0700472 addAudioSink(device);
Jaikumar Ganesh9488cbd2009-08-03 17:17:37 -0700473 } else {
Nick Pellybd022f42009-08-14 18:33:38 -0700474 int prevState = mAudioDevices.get(device);
475 handleSinkStateChange(device, prevState, state);
Jaikumar Ganesh9488cbd2009-08-03 17:17:37 -0700476 }
Mike Lockwooda0de3552009-03-24 21:08:33 -0700477 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800478 }
479
Nick Pellybd022f42009-08-14 18:33:38 -0700480 private void handleSinkStateChange(BluetoothDevice device, int prevState, int state) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800481 if (state != prevState) {
Jaikumar Ganeshd5ac1ae2009-05-05 22:26:12 -0700482 if (state == BluetoothA2dp.STATE_DISCONNECTED ||
483 state == BluetoothA2dp.STATE_DISCONNECTING) {
Eric Laurenta553c252009-07-17 12:17:14 -0700484 mSinkCount--;
Jaikumar Ganeshd5ac1ae2009-05-05 22:26:12 -0700485 } else if (state == BluetoothA2dp.STATE_CONNECTED) {
486 mSinkCount ++;
Mike Lockwooda0de3552009-03-24 21:08:33 -0700487 }
Nick Pellybd022f42009-08-14 18:33:38 -0700488 mAudioDevices.put(device, state);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800489
Eric Laurenta47d1532009-10-16 09:16:26 -0700490 checkSinkSuspendState(state);
491 mTargetA2dpState = -1;
Eric Laurent80a6a222009-10-08 10:58:19 -0700492
Jaikumar Ganesh63096892010-01-06 11:41:41 -0800493 if (getSinkPriority(device) > BluetoothA2dp.PRIORITY_OFF &&
494 state == BluetoothA2dp.STATE_CONNECTING ||
Jaikumar Ganesh721361f2009-11-20 15:21:47 -0800495 state == BluetoothA2dp.STATE_CONNECTED) {
496 // We have connected or attempting to connect.
497 // Bump priority
498 setSinkPriority(device, BluetoothA2dp.PRIORITY_AUTO_CONNECT);
499 }
500
Jaikumar Ganesh9b637e52010-06-02 14:36:14 -0700501 if (state == BluetoothA2dp.STATE_CONNECTED) {
502 // We will only have 1 device with AUTO_CONNECT priority
503 // To be backward compatible set everyone else to have PRIORITY_ON
504 adjustOtherSinkPriorities(device);
505 }
506
Nick Pelly005b2282009-09-10 10:21:56 -0700507 Intent intent = new Intent(BluetoothA2dp.ACTION_SINK_STATE_CHANGED);
508 intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
509 intent.putExtra(BluetoothA2dp.EXTRA_PREVIOUS_SINK_STATE, prevState);
510 intent.putExtra(BluetoothA2dp.EXTRA_SINK_STATE, state);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800511 mContext.sendBroadcast(intent, BLUETOOTH_PERM);
512
Nick Pellybd022f42009-08-14 18:33:38 -0700513 if (DBG) log("A2DP state : device: " + device + " State:" + prevState + "->" + state);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800514 }
515 }
516
Jaikumar Ganesh9b637e52010-06-02 14:36:14 -0700517 private void adjustOtherSinkPriorities(BluetoothDevice connectedDevice) {
518 if (!mAdjustedPriority) {
519 for (BluetoothDevice device : mAdapter.getBondedDevices()) {
520 if (getSinkPriority(device) >= BluetoothA2dp.PRIORITY_AUTO_CONNECT &&
521 !device.equals(connectedDevice)) {
522 setSinkPriority(device, BluetoothA2dp.PRIORITY_ON);
523 }
524 }
525 mAdjustedPriority = true;
526 }
527 }
528
Nick Pellybd022f42009-08-14 18:33:38 -0700529 private synchronized Set<BluetoothDevice> lookupSinksMatchingStates(int[] states) {
530 Set<BluetoothDevice> sinks = new HashSet<BluetoothDevice>();
Jaikumar Ganeshd5ac1ae2009-05-05 22:26:12 -0700531 if (mAudioDevices.isEmpty()) {
532 return sinks;
533 }
Nick Pellybd022f42009-08-14 18:33:38 -0700534 for (BluetoothDevice device: mAudioDevices.keySet()) {
535 int sinkState = getSinkState(device);
Jaikumar Ganeshd5ac1ae2009-05-05 22:26:12 -0700536 for (int state : states) {
537 if (state == sinkState) {
Nick Pellybd022f42009-08-14 18:33:38 -0700538 sinks.add(device);
Jaikumar Ganeshd5ac1ae2009-05-05 22:26:12 -0700539 break;
540 }
541 }
542 }
543 return sinks;
544 }
545
Eric Laurenta47d1532009-10-16 09:16:26 -0700546 private boolean checkSinkSuspendState(int state) {
547 boolean result = true;
548
549 if (state != mTargetA2dpState) {
550 if (state == BluetoothA2dp.STATE_PLAYING &&
551 mTargetA2dpState == BluetoothA2dp.STATE_CONNECTED) {
552 mAudioManager.setParameters("A2dpSuspended=true");
553 } else if (state == BluetoothA2dp.STATE_CONNECTED &&
554 mTargetA2dpState == BluetoothA2dp.STATE_PLAYING) {
555 mAudioManager.setParameters("A2dpSuspended=false");
556 } else {
557 result = false;
558 }
559 }
560 return result;
561 }
562
Jaikumar Ganeshc0e32f12009-12-16 11:36:39 -0800563 private void onConnectSinkResult(String deviceObjectPath, boolean result) {
564 // If the call was a success, ignore we will update the state
565 // when we a Sink Property Change
566 if (!result) {
567 if (deviceObjectPath != null) {
568 String address = mBluetoothService.getAddressFromObjectPath(deviceObjectPath);
Jaikumar Ganeshc0cec622010-04-19 17:19:31 -0700569 if (address == null) return;
Jaikumar Ganeshc0e32f12009-12-16 11:36:39 -0800570 BluetoothDevice device = mAdapter.getRemoteDevice(address);
571 int state = getSinkState(device);
572 handleSinkStateChange(device, state, BluetoothA2dp.STATE_DISCONNECTED);
573 }
574 }
575 }
576
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800577 @Override
578 protected synchronized void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
Jaikumar Ganeshd5ac1ae2009-05-05 22:26:12 -0700579 if (mAudioDevices.isEmpty()) return;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800580 pw.println("Cached audio devices:");
Nick Pellybd022f42009-08-14 18:33:38 -0700581 for (BluetoothDevice device : mAudioDevices.keySet()) {
582 int state = mAudioDevices.get(device);
583 pw.println(device + " " + BluetoothA2dp.stateToString(state));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800584 }
585 }
586
587 private static void log(String msg) {
588 Log.d(TAG, msg);
589 }
590
591 private native boolean initNative();
592 private native void cleanupNative();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800593 private synchronized native boolean connectSinkNative(String path);
594 private synchronized native boolean disconnectSinkNative(String path);
Zhu Lanf9bbe1e2009-06-24 10:51:57 +0800595 private synchronized native boolean suspendSinkNative(String path);
596 private synchronized native boolean resumeSinkNative(String path);
Jaikumar Ganeshd5ac1ae2009-05-05 22:26:12 -0700597 private synchronized native Object []getSinkPropertiesNative(String path);
Jaikumar Ganesh4f773a12010-02-16 11:57:06 -0800598 private synchronized native boolean avrcpVolumeUpNative(String path);
599 private synchronized native boolean avrcpVolumeDownNative(String path);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800600}