blob: 132c346f2a79e70385ed2ae2be94116a2088ba69 [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 }
Nick Pelly005b2282009-09-10 10:21:56 -070086 } else if (action.equals(BluetoothDevice.ACTION_BOND_STATE_CHANGED)) {
87 int bondState = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE,
Nick Pellyb24e11b2009-09-08 17:40:43 -070088 BluetoothDevice.ERROR);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080089 switch(bondState) {
90 case BluetoothDevice.BOND_BONDED:
Jaikumar Ganesh96a79832010-09-27 17:02:01 -070091 if (getPriority(device) == BluetoothA2dp.PRIORITY_UNDEFINED) {
92 setPriority(device, BluetoothA2dp.PRIORITY_ON);
Jaikumar Ganesh551ed722009-12-11 12:00:31 -080093 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080094 break;
Nick Pelly005b2282009-09-10 10:21:56 -070095 case BluetoothDevice.BOND_NONE:
Jaikumar Ganesh96a79832010-09-27 17:02:01 -070096 setPriority(device, BluetoothA2dp.PRIORITY_UNDEFINED);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080097 break;
98 }
Jaikumar Ganesh4c9a29e2009-09-30 15:59:35 -070099 } else if (action.equals(BluetoothDevice.ACTION_ACL_DISCONNECTED)) {
100 synchronized (this) {
101 if (mAudioDevices.containsKey(device)) {
102 int state = mAudioDevices.get(device);
103 handleSinkStateChange(device, state, BluetoothA2dp.STATE_DISCONNECTED);
104 }
105 }
Jaikumar Ganesh4f773a12010-02-16 11:57:06 -0800106 } else if (action.equals(AudioManager.VOLUME_CHANGED_ACTION)) {
107 int streamType = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, -1);
108 if (streamType == AudioManager.STREAM_MUSIC) {
Jaikumar Ganesh03cd78c2010-10-18 16:41:53 -0700109 List<BluetoothDevice> sinks = getConnectedDevices();
Jaikumar Ganesh96a79832010-09-27 17:02:01 -0700110
Jaikumar Ganesh03cd78c2010-10-18 16:41:53 -0700111 if (sinks.size() != 0 && isPhoneDocked(sinks.get(0))) {
112 String address = sinks.get(0).getAddress();
Jaikumar Ganesh4f773a12010-02-16 11:57:06 -0800113 int newVolLevel =
114 intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_VALUE, 0);
115 int oldVolLevel =
116 intent.getIntExtra(AudioManager.EXTRA_PREV_VOLUME_STREAM_VALUE, 0);
117 String path = mBluetoothService.getObjectPathFromAddress(address);
118 if (newVolLevel > oldVolLevel) {
119 avrcpVolumeUpNative(path);
120 } else if (newVolLevel < oldVolLevel) {
121 avrcpVolumeDownNative(path);
122 }
123 }
124 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800125 }
126 }
127 };
128
Jaikumar Ganesh4f773a12010-02-16 11:57:06 -0800129 private boolean isPhoneDocked(BluetoothDevice device) {
130 // This works only because these broadcast intents are "sticky"
131 Intent i = mContext.registerReceiver(null, new IntentFilter(Intent.ACTION_DOCK_EVENT));
132 if (i != null) {
133 int state = i.getIntExtra(Intent.EXTRA_DOCK_STATE, Intent.EXTRA_DOCK_STATE_UNDOCKED);
134 if (state != Intent.EXTRA_DOCK_STATE_UNDOCKED) {
135 BluetoothDevice dockDevice = i.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
136 if (dockDevice != null && device.equals(dockDevice)) {
137 return true;
138 }
139 }
140 }
141 return false;
142 }
143
Nick Pellybd022f42009-08-14 18:33:38 -0700144 public BluetoothA2dpService(Context context, BluetoothService bluetoothService) {
Jaikumar Ganeshd5ac1ae2009-05-05 22:26:12 -0700145 mContext = context;
146
147 mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
148
149 mBluetoothService = bluetoothService;
150 if (mBluetoothService == null) {
151 throw new RuntimeException("Platform does not support Bluetooth");
152 }
153
154 if (!initNative()) {
155 throw new RuntimeException("Could not init BluetoothA2dpService");
156 }
157
Nick Pellyf242b7b2009-10-08 00:12:45 +0200158 mAdapter = BluetoothAdapter.getDefaultAdapter();
Nick Pellybd022f42009-08-14 18:33:38 -0700159
Nick Pellyde893f52009-09-08 13:15:33 -0700160 mIntentFilter = new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED);
Nick Pelly005b2282009-09-10 10:21:56 -0700161 mIntentFilter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
162 mIntentFilter.addAction(BluetoothDevice.ACTION_ACL_CONNECTED);
Jaikumar Ganesh4c9a29e2009-09-30 15:59:35 -0700163 mIntentFilter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED);
Jaikumar Ganesh4f773a12010-02-16 11:57:06 -0800164 mIntentFilter.addAction(AudioManager.VOLUME_CHANGED_ACTION);
Jaikumar Ganeshd5ac1ae2009-05-05 22:26:12 -0700165 mContext.registerReceiver(mReceiver, mIntentFilter);
166
Nick Pellybd022f42009-08-14 18:33:38 -0700167 mAudioDevices = new HashMap<BluetoothDevice, Integer>();
Jaikumar Ganeshd5ac1ae2009-05-05 22:26:12 -0700168
169 if (mBluetoothService.isEnabled())
170 onBluetoothEnable();
Eric Laurenta47d1532009-10-16 09:16:26 -0700171 mTargetA2dpState = -1;
Jaikumar Ganesh9b637e52010-06-02 14:36:14 -0700172 mBluetoothService.setA2dpService(this);
Jaikumar Ganeshd5ac1ae2009-05-05 22:26:12 -0700173 }
174
175 @Override
176 protected void finalize() throws Throwable {
177 try {
178 cleanupNative();
179 } finally {
180 super.finalize();
181 }
182 }
183
Jake Hamby9a62c9c2010-12-09 14:47:57 -0800184 private int convertBluezSinkStringToState(String value) {
Jaikumar Ganeshd5ac1ae2009-05-05 22:26:12 -0700185 if (value.equalsIgnoreCase("disconnected"))
186 return BluetoothA2dp.STATE_DISCONNECTED;
187 if (value.equalsIgnoreCase("connecting"))
188 return BluetoothA2dp.STATE_CONNECTING;
189 if (value.equalsIgnoreCase("connected"))
190 return BluetoothA2dp.STATE_CONNECTED;
191 if (value.equalsIgnoreCase("playing"))
192 return BluetoothA2dp.STATE_PLAYING;
193 return -1;
194 }
195
Nick Pellybd022f42009-08-14 18:33:38 -0700196 private boolean isSinkDevice(BluetoothDevice device) {
Jaikumar Ganeshdd0463a2009-09-16 12:30:02 -0700197 ParcelUuid[] uuids = mBluetoothService.getRemoteUuids(device.getAddress());
198 if (uuids != null && BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.AudioSink)) {
199 return true;
Jaikumar Ganesh7f74d532009-07-14 12:21:26 -0700200 }
201 return false;
202 }
203
Jake Hamby9a62c9c2010-12-09 14:47:57 -0800204 private synchronized boolean addAudioSink(BluetoothDevice device) {
Nick Pellybd022f42009-08-14 18:33:38 -0700205 String path = mBluetoothService.getObjectPathFromAddress(device.getAddress());
Jaikumar Ganeshd5ac1ae2009-05-05 22:26:12 -0700206 String propValues[] = (String []) getSinkPropertiesNative(path);
207 if (propValues == null) {
Nick Pellybd022f42009-08-14 18:33:38 -0700208 Log.e(TAG, "Error while getting AudioSink properties for device: " + device);
Jaikumar Ganeshd5ac1ae2009-05-05 22:26:12 -0700209 return false;
210 }
211 Integer state = null;
212 // Properties are name-value pairs
213 for (int i = 0; i < propValues.length; i+=2) {
214 if (propValues[i].equals(PROPERTY_STATE)) {
Jake Hamby9a62c9c2010-12-09 14:47:57 -0800215 state = new Integer(convertBluezSinkStringToState(propValues[i+1]));
Jaikumar Ganeshd5ac1ae2009-05-05 22:26:12 -0700216 break;
217 }
218 }
Nick Pellybd022f42009-08-14 18:33:38 -0700219 mAudioDevices.put(device, state);
220 handleSinkStateChange(device, BluetoothA2dp.STATE_DISCONNECTED, state);
Jaikumar Ganeshd5ac1ae2009-05-05 22:26:12 -0700221 return true;
222 }
223
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800224 private synchronized void onBluetoothEnable() {
Jaikumar Ganeshd5ac1ae2009-05-05 22:26:12 -0700225 String devices = mBluetoothService.getProperty("Devices");
Jaikumar Ganeshd5ac1ae2009-05-05 22:26:12 -0700226 if (devices != null) {
227 String [] paths = devices.split(",");
228 for (String path: paths) {
229 String address = mBluetoothService.getAddressFromObjectPath(path);
Nick Pellybd022f42009-08-14 18:33:38 -0700230 BluetoothDevice device = mAdapter.getRemoteDevice(address);
Jaikumar Ganeshdd0463a2009-09-16 12:30:02 -0700231 ParcelUuid[] remoteUuids = mBluetoothService.getRemoteUuids(address);
232 if (remoteUuids != null)
233 if (BluetoothUuid.containsAnyUuid(remoteUuids,
234 new ParcelUuid[] {BluetoothUuid.AudioSink,
235 BluetoothUuid.AdvAudioDist})) {
236 addAudioSink(device);
Jaikumar Ganeshd5ac1ae2009-05-05 22:26:12 -0700237 }
Jaikumar Ganeshdd0463a2009-09-16 12:30:02 -0700238 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800239 }
Eric Laurenta553c252009-07-17 12:17:14 -0700240 mAudioManager.setParameters(BLUETOOTH_ENABLED+"=true");
Eric Laurent80a6a222009-10-08 10:58:19 -0700241 mAudioManager.setParameters("A2dpSuspended=false");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800242 }
243
244 private synchronized void onBluetoothDisable() {
Jaikumar Ganeshd5ac1ae2009-05-05 22:26:12 -0700245 if (!mAudioDevices.isEmpty()) {
Nick Pellybd022f42009-08-14 18:33:38 -0700246 BluetoothDevice[] devices = new BluetoothDevice[mAudioDevices.size()];
247 devices = mAudioDevices.keySet().toArray(devices);
248 for (BluetoothDevice device : devices) {
Jaikumar Ganesh96a79832010-09-27 17:02:01 -0700249 int state = getConnectionState(device);
Jaikumar Ganeshd5ac1ae2009-05-05 22:26:12 -0700250 switch (state) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800251 case BluetoothA2dp.STATE_CONNECTING:
252 case BluetoothA2dp.STATE_CONNECTED:
253 case BluetoothA2dp.STATE_PLAYING:
Nick Pellybd022f42009-08-14 18:33:38 -0700254 disconnectSinkNative(mBluetoothService.getObjectPathFromAddress(
255 device.getAddress()));
256 handleSinkStateChange(device, state, BluetoothA2dp.STATE_DISCONNECTED);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800257 break;
258 case BluetoothA2dp.STATE_DISCONNECTING:
Nick Pellybd022f42009-08-14 18:33:38 -0700259 handleSinkStateChange(device, BluetoothA2dp.STATE_DISCONNECTING,
260 BluetoothA2dp.STATE_DISCONNECTED);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800261 break;
262 }
263 }
Jaikumar Ganeshd5ac1ae2009-05-05 22:26:12 -0700264 mAudioDevices.clear();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800265 }
Eric Laurenta553c252009-07-17 12:17:14 -0700266
Nick Pellybd022f42009-08-14 18:33:38 -0700267 mAudioManager.setParameters(BLUETOOTH_ENABLED + "=false");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800268 }
269
Jaikumar Ganesh9b637e52010-06-02 14:36:14 -0700270 private synchronized boolean isConnectSinkFeasible(BluetoothDevice device) {
271 if (!mBluetoothService.isEnabled() || !isSinkDevice(device) ||
Jaikumar Ganesh96a79832010-09-27 17:02:01 -0700272 getPriority(device) == BluetoothA2dp.PRIORITY_OFF) {
Jake Hamby9a62c9c2010-12-09 14:47:57 -0800273 return false;
274 }
Jaikumar Ganesh9b637e52010-06-02 14:36:14 -0700275
Jake Hamby9a62c9c2010-12-09 14:47:57 -0800276 if (mAudioDevices.get(device) == null && !addAudioSink(device)) {
277 return false;
278 }
Jaikumar Ganesh9b637e52010-06-02 14:36:14 -0700279
Jake Hamby9a62c9c2010-12-09 14:47:57 -0800280 String path = mBluetoothService.getObjectPathFromAddress(device.getAddress());
281 if (path == null) {
282 return false;
283 }
284 return true;
Jaikumar Ganesh9b637e52010-06-02 14:36:14 -0700285 }
286
Jaikumar Ganesh96a79832010-09-27 17:02:01 -0700287 public synchronized boolean isA2dpPlaying(BluetoothDevice device) {
288 mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
289 "Need BLUETOOTH_ADMIN permission");
290 if (DBG) log("isA2dpPlaying(" + device + ")");
291 if (device.equals(mPlayingA2dpDevice)) return true;
292 return false;
293 }
294
295 public synchronized boolean connect(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 Ganesh96a79832010-09-27 17:02:01 -0700301 for (BluetoothDevice sinkDevice : mAudioDevices.keySet()) {
302 if (getConnectionState(sinkDevice) != BluetoothProfile.STATE_DISCONNECTED) {
303 disconnect(sinkDevice);
304 }
305 }
306
Jaikumar Ganesh9b637e52010-06-02 14:36:14 -0700307 return mBluetoothService.connectSink(device.getAddress());
308 }
309
310 public synchronized boolean connectSinkInternal(BluetoothDevice device) {
Jaikumar Ganesh4aae54c2010-04-07 17:28:08 -0700311 if (!mBluetoothService.isEnabled()) return false;
312
Jaikumar Ganesh9b637e52010-06-02 14:36:14 -0700313 int state = mAudioDevices.get(device);
314
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700315 // ignore if there are any active sinks
Jaikumar Ganesh96a79832010-09-27 17:02:01 -0700316 if (getDevicesMatchingConnectionStates(new int[] {
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700317 BluetoothA2dp.STATE_CONNECTING,
318 BluetoothA2dp.STATE_CONNECTED,
Jaikumar Ganesh03cd78c2010-10-18 16:41:53 -0700319 BluetoothA2dp.STATE_DISCONNECTING}).size() != 0) {
Nick Pellyb24e11b2009-09-08 17:40:43 -0700320 return false;
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700321 }
322
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800323 switch (state) {
324 case BluetoothA2dp.STATE_CONNECTED:
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800325 case BluetoothA2dp.STATE_DISCONNECTING:
Nick Pellyb24e11b2009-09-08 17:40:43 -0700326 return false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800327 case BluetoothA2dp.STATE_CONNECTING:
Nick Pellyb24e11b2009-09-08 17:40:43 -0700328 return true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800329 }
330
Nick Pellybd022f42009-08-14 18:33:38 -0700331 String path = mBluetoothService.getObjectPathFromAddress(device.getAddress());
Jaikumar Ganeshd5ac1ae2009-05-05 22:26:12 -0700332
Jaikumar Ganeshbcf5cf42010-10-12 15:15:55 -0700333 // State is DISCONNECTED and we are connecting.
Jaikumar Ganeshaa4b2352010-10-14 09:36:39 -0700334 if (getPriority(device) < BluetoothA2dp.PRIORITY_AUTO_CONNECT) {
335 setPriority(device, BluetoothA2dp.PRIORITY_AUTO_CONNECT);
Jaikumar Ganeshbcf5cf42010-10-12 15:15:55 -0700336 }
Jaikumar Ganeshc0e32f12009-12-16 11:36:39 -0800337 handleSinkStateChange(device, state, BluetoothA2dp.STATE_CONNECTING);
338
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800339 if (!connectSinkNative(path)) {
Jaikumar Ganeshc0e32f12009-12-16 11:36:39 -0800340 // Restore previous state
341 handleSinkStateChange(device, mAudioDevices.get(device), state);
Nick Pellyb24e11b2009-09-08 17:40:43 -0700342 return false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800343 }
Nick Pellyb24e11b2009-09-08 17:40:43 -0700344 return true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800345 }
346
Jaikumar Ganesh9b637e52010-06-02 14:36:14 -0700347 private synchronized boolean isDisconnectSinkFeasible(BluetoothDevice device) {
Nick Pellybd022f42009-08-14 18:33:38 -0700348 String path = mBluetoothService.getObjectPathFromAddress(device.getAddress());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800349 if (path == null) {
Nick Pellyb24e11b2009-09-08 17:40:43 -0700350 return false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800351 }
Jaikumar Ganeshd5ac1ae2009-05-05 22:26:12 -0700352
Jaikumar Ganesh96a79832010-09-27 17:02:01 -0700353 int state = getConnectionState(device);
Jaikumar Ganeshc0e32f12009-12-16 11:36:39 -0800354 switch (state) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800355 case BluetoothA2dp.STATE_DISCONNECTED:
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800356 case BluetoothA2dp.STATE_DISCONNECTING:
Jaikumar Ganesh96a79832010-09-27 17:02:01 -0700357 return false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800358 }
Jaikumar Ganesh9b637e52010-06-02 14:36:14 -0700359 return true;
360 }
361
Jaikumar Ganesh96a79832010-09-27 17:02:01 -0700362 public synchronized boolean disconnect(BluetoothDevice device) {
Jaikumar Ganesh9b637e52010-06-02 14:36:14 -0700363 mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
364 "Need BLUETOOTH_ADMIN permission");
365 if (DBG) log("disconnectSink(" + device + ")");
366 if (!isDisconnectSinkFeasible(device)) return false;
367 return mBluetoothService.disconnectSink(device.getAddress());
368 }
369
370 public synchronized boolean disconnectSinkInternal(BluetoothDevice device) {
Jaikumar Ganesh96a79832010-09-27 17:02:01 -0700371 int state = getConnectionState(device);
Jaikumar Ganesh9b637e52010-06-02 14:36:14 -0700372 String path = mBluetoothService.getObjectPathFromAddress(device.getAddress());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800373
Jaikumar Ganesh5844f1d2010-10-14 12:11:25 -0700374 switch (state) {
375 case BluetoothA2dp.STATE_DISCONNECTED:
376 case BluetoothA2dp.STATE_DISCONNECTING:
377 return false;
378 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800379 // State is CONNECTING or CONNECTED or PLAYING
Jaikumar Ganeshc0e32f12009-12-16 11:36:39 -0800380 handleSinkStateChange(device, state, BluetoothA2dp.STATE_DISCONNECTING);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800381 if (!disconnectSinkNative(path)) {
Jaikumar Ganeshc0e32f12009-12-16 11:36:39 -0800382 // Restore previous state
383 handleSinkStateChange(device, mAudioDevices.get(device), state);
Nick Pellyb24e11b2009-09-08 17:40:43 -0700384 return false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800385 }
Jaikumar Ganeshc0e32f12009-12-16 11:36:39 -0800386 return true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800387 }
388
Zhu Lanf9bbe1e2009-06-24 10:51:57 +0800389 public synchronized boolean suspendSink(BluetoothDevice device) {
390 mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
391 "Need BLUETOOTH_ADMIN permission");
Eric Laurenta47d1532009-10-16 09:16:26 -0700392 if (DBG) log("suspendSink(" + device + "), mTargetA2dpState: "+mTargetA2dpState);
Zhu Lanf9bbe1e2009-06-24 10:51:57 +0800393 if (device == null || mAudioDevices == null) {
394 return false;
395 }
396 String path = mBluetoothService.getObjectPathFromAddress(device.getAddress());
Nick Pelly62895a62009-09-30 15:30:43 -0700397 Integer state = mAudioDevices.get(device);
398 if (path == null || state == null) {
Zhu Lanf9bbe1e2009-06-24 10:51:57 +0800399 return false;
400 }
Eric Laurenta47d1532009-10-16 09:16:26 -0700401
402 mTargetA2dpState = BluetoothA2dp.STATE_CONNECTED;
403 return checkSinkSuspendState(state.intValue());
Zhu Lanf9bbe1e2009-06-24 10:51:57 +0800404 }
405
406 public synchronized boolean resumeSink(BluetoothDevice device) {
407 mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
408 "Need BLUETOOTH_ADMIN permission");
Eric Laurenta47d1532009-10-16 09:16:26 -0700409 if (DBG) log("resumeSink(" + device + "), mTargetA2dpState: "+mTargetA2dpState);
Zhu Lanf9bbe1e2009-06-24 10:51:57 +0800410 if (device == null || mAudioDevices == null) {
411 return false;
412 }
413 String path = mBluetoothService.getObjectPathFromAddress(device.getAddress());
Nick Pelly62895a62009-09-30 15:30:43 -0700414 Integer state = mAudioDevices.get(device);
415 if (path == null || state == null) {
Zhu Lanf9bbe1e2009-06-24 10:51:57 +0800416 return false;
417 }
Eric Laurenta47d1532009-10-16 09:16:26 -0700418 mTargetA2dpState = BluetoothA2dp.STATE_PLAYING;
419 return checkSinkSuspendState(state.intValue());
Zhu Lanf9bbe1e2009-06-24 10:51:57 +0800420 }
421
Jaikumar Ganesh96a79832010-09-27 17:02:01 -0700422 public synchronized int getConnectionState(BluetoothDevice device) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800423 mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
Nick Pellybd022f42009-08-14 18:33:38 -0700424 Integer state = mAudioDevices.get(device);
Jaikumar Ganeshd5ac1ae2009-05-05 22:26:12 -0700425 if (state == null)
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800426 return BluetoothA2dp.STATE_DISCONNECTED;
Jaikumar Ganeshd5ac1ae2009-05-05 22:26:12 -0700427 return state;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800428 }
429
Jaikumar Ganesh03cd78c2010-10-18 16:41:53 -0700430 public synchronized List<BluetoothDevice> getConnectedDevices() {
Jaikumar Ganesh96a79832010-09-27 17:02:01 -0700431 mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
Jaikumar Ganesh03cd78c2010-10-18 16:41:53 -0700432 List<BluetoothDevice> sinks = getDevicesMatchingConnectionStates(
Jaikumar Ganesh96a79832010-09-27 17:02:01 -0700433 new int[] {BluetoothA2dp.STATE_CONNECTED});
434 return sinks;
435 }
436
Jaikumar Ganesh03cd78c2010-10-18 16:41:53 -0700437 public synchronized List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
Jaikumar Ganesh96a79832010-09-27 17:02:01 -0700438 mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
Jaikumar Ganesh03cd78c2010-10-18 16:41:53 -0700439 ArrayList<BluetoothDevice> sinks = new ArrayList<BluetoothDevice>();
Jaikumar Ganesh96a79832010-09-27 17:02:01 -0700440 for (BluetoothDevice device: mAudioDevices.keySet()) {
441 int sinkState = getConnectionState(device);
442 for (int state : states) {
443 if (state == sinkState) {
444 sinks.add(device);
445 break;
446 }
447 }
448 }
Jaikumar Ganesh03cd78c2010-10-18 16:41:53 -0700449 return sinks;
Jaikumar Ganesh96a79832010-09-27 17:02:01 -0700450 }
451
452 public synchronized int getPriority(BluetoothDevice device) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800453 mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800454 return Settings.Secure.getInt(mContext.getContentResolver(),
Nick Pellybd022f42009-08-14 18:33:38 -0700455 Settings.Secure.getBluetoothA2dpSinkPriorityKey(device.getAddress()),
Jaikumar Ganeshccaebfc2010-01-08 18:26:14 -0800456 BluetoothA2dp.PRIORITY_UNDEFINED);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800457 }
458
Jaikumar Ganesh96a79832010-09-27 17:02:01 -0700459 public synchronized boolean setPriority(BluetoothDevice device, int priority) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800460 mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
461 "Need BLUETOOTH_ADMIN permission");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800462 return Settings.Secure.putInt(mContext.getContentResolver(),
Nick Pellyb24e11b2009-09-08 17:40:43 -0700463 Settings.Secure.getBluetoothA2dpSinkPriorityKey(device.getAddress()), priority);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800464 }
465
Jake Hamby9a62c9c2010-12-09 14:47:57 -0800466 /**
467 * Called by native code on a PropertyChanged signal from
468 * org.bluez.AudioSink.
469 *
470 * @param path the object path for the changed device
471 * @param propValues a string array containing the key and one or more
472 * values.
473 */
474 private synchronized void onSinkPropertyChanged(String path, String[] propValues) {
Jaikumar Ganesh9488cbd2009-08-03 17:17:37 -0700475 if (!mBluetoothService.isEnabled()) {
476 return;
477 }
478
Jaikumar Ganeshd5ac1ae2009-05-05 22:26:12 -0700479 String name = propValues[0];
480 String address = mBluetoothService.getAddressFromObjectPath(path);
481 if (address == null) {
482 Log.e(TAG, "onSinkPropertyChanged: Address of the remote device in null");
483 return;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800484 }
485
Nick Pellybd022f42009-08-14 18:33:38 -0700486 BluetoothDevice device = mAdapter.getRemoteDevice(address);
487
Jaikumar Ganeshd5ac1ae2009-05-05 22:26:12 -0700488 if (name.equals(PROPERTY_STATE)) {
Jake Hamby9a62c9c2010-12-09 14:47:57 -0800489 int state = convertBluezSinkStringToState(propValues[1]);
Jaikumar Ganesh8febf882010-10-26 00:28:39 -0700490 log("A2DP: onSinkPropertyChanged newState is: " + state + "mPlayingA2dpDevice: " + mPlayingA2dpDevice);
491
Nick Pellybd022f42009-08-14 18:33:38 -0700492 if (mAudioDevices.get(device) == null) {
Jaikumar Ganesh9488cbd2009-08-03 17:17:37 -0700493 // This is for an incoming connection for a device not known to us.
494 // We have authorized it and bluez state has changed.
Nick Pellybd022f42009-08-14 18:33:38 -0700495 addAudioSink(device);
Jaikumar Ganesh9488cbd2009-08-03 17:17:37 -0700496 } else {
Jaikumar Ganesh96a79832010-09-27 17:02:01 -0700497 if (state == BluetoothA2dp.STATE_PLAYING && mPlayingA2dpDevice == null) {
498 mPlayingA2dpDevice = device;
499 handleSinkPlayingStateChange(device, state, BluetoothA2dp.STATE_NOT_PLAYING);
500 } else if (state == BluetoothA2dp.STATE_CONNECTED && mPlayingA2dpDevice != null) {
501 mPlayingA2dpDevice = null;
502 handleSinkPlayingStateChange(device, BluetoothA2dp.STATE_NOT_PLAYING,
503 BluetoothA2dp.STATE_PLAYING);
504 } else {
Jaikumar Ganeshb2ec4fa2010-10-26 11:22:48 -0700505 mPlayingA2dpDevice = null;
Jaikumar Ganesh96a79832010-09-27 17:02:01 -0700506 int prevState = mAudioDevices.get(device);
507 handleSinkStateChange(device, prevState, state);
508 }
Jaikumar Ganesh9488cbd2009-08-03 17:17:37 -0700509 }
Mike Lockwooda0de3552009-03-24 21:08:33 -0700510 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800511 }
512
Nick Pellybd022f42009-08-14 18:33:38 -0700513 private void handleSinkStateChange(BluetoothDevice device, int prevState, int state) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800514 if (state != prevState) {
Nick Pellybd022f42009-08-14 18:33:38 -0700515 mAudioDevices.put(device, state);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800516
Eric Laurenta47d1532009-10-16 09:16:26 -0700517 checkSinkSuspendState(state);
518 mTargetA2dpState = -1;
Eric Laurent80a6a222009-10-08 10:58:19 -0700519
Jaikumar Ganesh96a79832010-09-27 17:02:01 -0700520 if (getPriority(device) > BluetoothA2dp.PRIORITY_OFF &&
Jaikumar Ganesh721361f2009-11-20 15:21:47 -0800521 state == BluetoothA2dp.STATE_CONNECTED) {
522 // We have connected or attempting to connect.
523 // Bump priority
Jaikumar Ganesh96a79832010-09-27 17:02:01 -0700524 setPriority(device, BluetoothA2dp.PRIORITY_AUTO_CONNECT);
Jaikumar Ganesh9b637e52010-06-02 14:36:14 -0700525 // We will only have 1 device with AUTO_CONNECT priority
526 // To be backward compatible set everyone else to have PRIORITY_ON
527 adjustOtherSinkPriorities(device);
528 }
529
Jaikumar Ganesh96a79832010-09-27 17:02:01 -0700530 Intent intent = new Intent(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED);
Nick Pelly005b2282009-09-10 10:21:56 -0700531 intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
Jaikumar Ganesh96a79832010-09-27 17:02:01 -0700532 intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, prevState);
533 intent.putExtra(BluetoothProfile.EXTRA_STATE, state);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800534 mContext.sendBroadcast(intent, BLUETOOTH_PERM);
535
Nick Pellybd022f42009-08-14 18:33:38 -0700536 if (DBG) log("A2DP state : device: " + device + " State:" + prevState + "->" + state);
Jaikumar Ganesha46f2fb2010-10-21 14:55:05 -0700537
538 mBluetoothService.sendConnectionStateChange(device, state, prevState);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800539 }
540 }
541
Jaikumar Ganesh96a79832010-09-27 17:02:01 -0700542 private void handleSinkPlayingStateChange(BluetoothDevice device, int state, int prevState) {
543 Intent intent = new Intent(BluetoothA2dp.ACTION_PLAYING_STATE_CHANGED);
544 intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
545 intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, prevState);
546 intent.putExtra(BluetoothProfile.EXTRA_STATE, state);
547 mContext.sendBroadcast(intent, BLUETOOTH_PERM);
548
549 if (DBG) log("A2DP Playing state : device: " + device + " State:" + prevState + "->" + state);
550 }
551
Jaikumar Ganesh9b637e52010-06-02 14:36:14 -0700552 private void adjustOtherSinkPriorities(BluetoothDevice connectedDevice) {
Jaikumar Ganeshaa4b2352010-10-14 09:36:39 -0700553 for (BluetoothDevice device : mAdapter.getBondedDevices()) {
554 if (getPriority(device) >= BluetoothA2dp.PRIORITY_AUTO_CONNECT &&
555 !device.equals(connectedDevice)) {
556 setPriority(device, BluetoothA2dp.PRIORITY_ON);
Jaikumar Ganesh9b637e52010-06-02 14:36:14 -0700557 }
Jaikumar Ganesh9b637e52010-06-02 14:36:14 -0700558 }
559 }
560
Eric Laurenta47d1532009-10-16 09:16:26 -0700561 private boolean checkSinkSuspendState(int state) {
562 boolean result = true;
563
564 if (state != mTargetA2dpState) {
565 if (state == BluetoothA2dp.STATE_PLAYING &&
566 mTargetA2dpState == BluetoothA2dp.STATE_CONNECTED) {
567 mAudioManager.setParameters("A2dpSuspended=true");
568 } else if (state == BluetoothA2dp.STATE_CONNECTED &&
569 mTargetA2dpState == BluetoothA2dp.STATE_PLAYING) {
570 mAudioManager.setParameters("A2dpSuspended=false");
571 } else {
572 result = false;
573 }
574 }
575 return result;
576 }
577
Jake Hamby9a62c9c2010-12-09 14:47:57 -0800578 /**
579 * Called by native code for the async response to a Connect
580 * method call to org.bluez.AudioSink.
581 *
582 * @param deviceObjectPath the object path for the connecting device
583 * @param result true on success; false on error
584 */
Jaikumar Ganeshc0e32f12009-12-16 11:36:39 -0800585 private void onConnectSinkResult(String deviceObjectPath, boolean result) {
586 // If the call was a success, ignore we will update the state
587 // when we a Sink Property Change
588 if (!result) {
589 if (deviceObjectPath != null) {
590 String address = mBluetoothService.getAddressFromObjectPath(deviceObjectPath);
Jaikumar Ganeshc0cec622010-04-19 17:19:31 -0700591 if (address == null) return;
Jaikumar Ganeshc0e32f12009-12-16 11:36:39 -0800592 BluetoothDevice device = mAdapter.getRemoteDevice(address);
Jaikumar Ganesh96a79832010-09-27 17:02:01 -0700593 int state = getConnectionState(device);
Jaikumar Ganeshc0e32f12009-12-16 11:36:39 -0800594 handleSinkStateChange(device, state, BluetoothA2dp.STATE_DISCONNECTED);
595 }
596 }
597 }
598
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800599 @Override
600 protected synchronized void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
Jaikumar Ganeshd5ac1ae2009-05-05 22:26:12 -0700601 if (mAudioDevices.isEmpty()) return;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800602 pw.println("Cached audio devices:");
Nick Pellybd022f42009-08-14 18:33:38 -0700603 for (BluetoothDevice device : mAudioDevices.keySet()) {
604 int state = mAudioDevices.get(device);
605 pw.println(device + " " + BluetoothA2dp.stateToString(state));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800606 }
607 }
608
609 private static void log(String msg) {
610 Log.d(TAG, msg);
611 }
612
613 private native boolean initNative();
614 private native void cleanupNative();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800615 private synchronized native boolean connectSinkNative(String path);
616 private synchronized native boolean disconnectSinkNative(String path);
Zhu Lanf9bbe1e2009-06-24 10:51:57 +0800617 private synchronized native boolean suspendSinkNative(String path);
618 private synchronized native boolean resumeSinkNative(String path);
Jaikumar Ganeshd5ac1ae2009-05-05 22:26:12 -0700619 private synchronized native Object []getSinkPropertiesNative(String path);
Jaikumar Ganesh4f773a12010-02-16 11:57:06 -0800620 private synchronized native boolean avrcpVolumeUpNative(String path);
621 private synchronized native boolean avrcpVolumeDownNative(String path);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800622}