LocalBluetoothProfileManager.java revision 8038834f8e768f6a62b4c7d5498160eb2c8b4895
1/* 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 17package com.android.settings.bluetooth; 18 19import com.android.settings.R; 20 21import android.bluetooth.BluetoothA2dp; 22import android.bluetooth.BluetoothDevice; 23import android.bluetooth.BluetoothHeadset; 24import android.bluetooth.BluetoothUuid; 25import android.os.Handler; 26import android.os.ParcelUuid; 27import android.util.Log; 28 29import java.util.HashMap; 30import java.util.HashSet; 31import java.util.Iterator; 32import java.util.LinkedList; 33import java.util.List; 34import java.util.Map; 35import java.util.Set; 36 37/** 38 * LocalBluetoothProfileManager is an abstract class defining the basic 39 * functionality related to a profile. 40 */ 41public abstract class LocalBluetoothProfileManager { 42 private static final String TAG = "LocalBluetoothProfileManager"; 43 44 /* package */ static final ParcelUuid[] HEADSET_PROFILE_UUIDS = new ParcelUuid[] { 45 BluetoothUuid.HSP, 46 BluetoothUuid.Handsfree, 47 }; 48 49 /* package */ static final ParcelUuid[] A2DP_PROFILE_UUIDS = new ParcelUuid[] { 50 BluetoothUuid.AudioSink, 51 BluetoothUuid.AdvAudioDist, 52 }; 53 54 /* package */ static final ParcelUuid[] OPP_PROFILE_UUIDS = new ParcelUuid[] { 55 BluetoothUuid.ObexObjectPush 56 }; 57 58 /** 59 * An interface for notifying BluetoothHeadset IPC clients when they have 60 * been connected to the BluetoothHeadset service. 61 */ 62 public interface ServiceListener { 63 /** 64 * Called to notify the client when this proxy object has been 65 * connected to the BluetoothHeadset service. Clients must wait for 66 * this callback before making IPC calls on the BluetoothHeadset 67 * service. 68 */ 69 public void onServiceConnected(); 70 71 /** 72 * Called to notify the client that this proxy object has been 73 * disconnected from the BluetoothHeadset service. Clients must not 74 * make IPC calls on the BluetoothHeadset service after this callback. 75 * This callback will currently only occur if the application hosting 76 * the BluetoothHeadset service, but may be called more often in future. 77 */ 78 public void onServiceDisconnected(); 79 } 80 81 // TODO: close profiles when we're shutting down 82 private static Map<Profile, LocalBluetoothProfileManager> sProfileMap = 83 new HashMap<Profile, LocalBluetoothProfileManager>(); 84 85 protected LocalBluetoothManager mLocalManager; 86 87 public static void init(LocalBluetoothManager localManager) { 88 synchronized (sProfileMap) { 89 if (sProfileMap.size() == 0) { 90 LocalBluetoothProfileManager profileManager; 91 92 profileManager = new A2dpProfileManager(localManager); 93 sProfileMap.put(Profile.A2DP, profileManager); 94 95 profileManager = new HeadsetProfileManager(localManager); 96 sProfileMap.put(Profile.HEADSET, profileManager); 97 98 profileManager = new OppProfileManager(localManager); 99 sProfileMap.put(Profile.OPP, profileManager); 100 } 101 } 102 } 103 104 private static LinkedList<ServiceListener> mServiceListeners = new LinkedList<ServiceListener>(); 105 106 public static void addServiceListener(ServiceListener l) { 107 mServiceListeners.add(l); 108 } 109 110 public static void removeServiceListener(ServiceListener l) { 111 mServiceListeners.remove(l); 112 } 113 114 public static boolean isManagerReady() { 115 // Getting just the headset profile is fine for now. Will need to deal with A2DP 116 // and others if they aren't always in a ready state. 117 LocalBluetoothProfileManager profileManager = sProfileMap.get(Profile.HEADSET); 118 if (profileManager == null) { 119 return sProfileMap.size() > 0; 120 } 121 return profileManager.isProfileReady(); 122 } 123 124 public static LocalBluetoothProfileManager getProfileManager(LocalBluetoothManager localManager, 125 Profile profile) { 126 // Note: This code assumes that "localManager" is same as the 127 // LocalBluetoothManager that was used to initialize the sProfileMap. 128 // If that every changes, we can't just keep one copy of sProfileMap. 129 synchronized (sProfileMap) { 130 LocalBluetoothProfileManager profileManager = sProfileMap.get(profile); 131 if (profileManager == null) { 132 Log.e(TAG, "profileManager can't be found for " + profile.toString()); 133 } 134 return profileManager; 135 } 136 } 137 138 /** 139 * Temporary method to fill profiles based on a device's class. 140 * 141 * NOTE: This list happens to define the connection order. We should put this logic in a more 142 * well known place when this method is no longer temporary. 143 * @param uuids of the remote device 144 * @param profiles The list of profiles to fill 145 */ 146 public static void updateProfiles(ParcelUuid[] uuids, List<Profile> profiles) { 147 profiles.clear(); 148 149 if (uuids == null) { 150 return; 151 } 152 153 if (BluetoothUuid.containsAnyUuid(uuids, HEADSET_PROFILE_UUIDS)) { 154 profiles.add(Profile.HEADSET); 155 } 156 157 if (BluetoothUuid.containsAnyUuid(uuids, A2DP_PROFILE_UUIDS)) { 158 profiles.add(Profile.A2DP); 159 } 160 161 if (BluetoothUuid.containsAnyUuid(uuids, OPP_PROFILE_UUIDS)) { 162 profiles.add(Profile.OPP); 163 } 164 } 165 166 protected LocalBluetoothProfileManager(LocalBluetoothManager localManager) { 167 mLocalManager = localManager; 168 } 169 170 public abstract Set<BluetoothDevice> getConnectedDevices(); 171 172 public abstract boolean connect(BluetoothDevice device); 173 174 public abstract boolean disconnect(BluetoothDevice device); 175 176 public abstract int getConnectionStatus(BluetoothDevice device); 177 178 public abstract int getSummary(BluetoothDevice device); 179 180 public abstract int convertState(int a2dpState); 181 182 public abstract boolean isPreferred(BluetoothDevice device); 183 184 public abstract int getPreferred(BluetoothDevice device); 185 186 public abstract void setPreferred(BluetoothDevice device, boolean preferred); 187 188 public boolean isConnected(BluetoothDevice device) { 189 return SettingsBtStatus.isConnectionStatusConnected(getConnectionStatus(device)); 190 } 191 192 public abstract boolean isProfileReady(); 193 194 // TODO: int instead of enum 195 public enum Profile { 196 HEADSET(R.string.bluetooth_profile_headset), 197 A2DP(R.string.bluetooth_profile_a2dp), 198 OPP(R.string.bluetooth_profile_opp); 199 200 public final int localizedString; 201 202 private Profile(int localizedString) { 203 this.localizedString = localizedString; 204 } 205 } 206 207 /** 208 * A2dpProfileManager is an abstraction for the {@link BluetoothA2dp} service. 209 */ 210 private static class A2dpProfileManager extends LocalBluetoothProfileManager { 211 private BluetoothA2dp mService; 212 213 public A2dpProfileManager(LocalBluetoothManager localManager) { 214 super(localManager); 215 mService = new BluetoothA2dp(localManager.getContext()); 216 } 217 218 @Override 219 public Set<BluetoothDevice> getConnectedDevices() { 220 return mService.getNonDisconnectedSinks(); 221 } 222 223 @Override 224 public boolean connect(BluetoothDevice device) { 225 Set<BluetoothDevice> sinks = mService.getNonDisconnectedSinks(); 226 if (sinks != null) { 227 for (BluetoothDevice sink : sinks) { 228 mService.disconnectSink(sink); 229 } 230 } 231 return mService.connectSink(device); 232 } 233 234 @Override 235 public boolean disconnect(BluetoothDevice device) { 236 // Downgrade priority as user is disconnecting the sink. 237 if (mService.getSinkPriority(device) > BluetoothA2dp.PRIORITY_ON) { 238 mService.setSinkPriority(device, BluetoothA2dp.PRIORITY_ON); 239 } 240 return mService.disconnectSink(device); 241 } 242 243 @Override 244 public int getConnectionStatus(BluetoothDevice device) { 245 return convertState(mService.getSinkState(device)); 246 } 247 248 @Override 249 public int getSummary(BluetoothDevice device) { 250 int connectionStatus = getConnectionStatus(device); 251 252 if (SettingsBtStatus.isConnectionStatusConnected(connectionStatus)) { 253 return R.string.bluetooth_a2dp_profile_summary_connected; 254 } else { 255 return SettingsBtStatus.getConnectionStatusSummary(connectionStatus); 256 } 257 } 258 259 @Override 260 public boolean isPreferred(BluetoothDevice device) { 261 return mService.getSinkPriority(device) > BluetoothA2dp.PRIORITY_OFF; 262 } 263 264 @Override 265 public int getPreferred(BluetoothDevice device) { 266 return mService.getSinkPriority(device); 267 } 268 269 @Override 270 public void setPreferred(BluetoothDevice device, boolean preferred) { 271 if (preferred) { 272 if (mService.getSinkPriority(device) < BluetoothA2dp.PRIORITY_ON) { 273 mService.setSinkPriority(device, BluetoothA2dp.PRIORITY_ON); 274 } 275 } else { 276 mService.setSinkPriority(device, BluetoothA2dp.PRIORITY_OFF); 277 } 278 } 279 280 @Override 281 public int convertState(int a2dpState) { 282 switch (a2dpState) { 283 case BluetoothA2dp.STATE_CONNECTED: 284 return SettingsBtStatus.CONNECTION_STATUS_CONNECTED; 285 case BluetoothA2dp.STATE_CONNECTING: 286 return SettingsBtStatus.CONNECTION_STATUS_CONNECTING; 287 case BluetoothA2dp.STATE_DISCONNECTED: 288 return SettingsBtStatus.CONNECTION_STATUS_DISCONNECTED; 289 case BluetoothA2dp.STATE_DISCONNECTING: 290 return SettingsBtStatus.CONNECTION_STATUS_DISCONNECTING; 291 case BluetoothA2dp.STATE_PLAYING: 292 return SettingsBtStatus.CONNECTION_STATUS_ACTIVE; 293 default: 294 return SettingsBtStatus.CONNECTION_STATUS_UNKNOWN; 295 } 296 } 297 298 @Override 299 public boolean isProfileReady() { 300 return true; 301 } 302 } 303 304 /** 305 * HeadsetProfileManager is an abstraction for the {@link BluetoothHeadset} service. 306 */ 307 private static class HeadsetProfileManager extends LocalBluetoothProfileManager 308 implements BluetoothHeadset.ServiceListener { 309 private BluetoothHeadset mService; 310 private Handler mUiHandler = new Handler(); 311 private boolean profileReady = false; 312 313 public HeadsetProfileManager(LocalBluetoothManager localManager) { 314 super(localManager); 315 mService = new BluetoothHeadset(localManager.getContext(), this); 316 } 317 318 public void onServiceConnected() { 319 profileReady = true; 320 // This could be called on a non-UI thread, funnel to UI thread. 321 mUiHandler.post(new Runnable() { 322 public void run() { 323 /* 324 * We just bound to the service, so refresh the UI of the 325 * headset device. 326 */ 327 BluetoothDevice device = mService.getCurrentHeadset(); 328 if (device == null) return; 329 mLocalManager.getCachedDeviceManager() 330 .onProfileStateChanged(device, Profile.HEADSET, 331 BluetoothHeadset.STATE_CONNECTED); 332 } 333 }); 334 335 if (mServiceListeners.size() > 0) { 336 Iterator<ServiceListener> it = mServiceListeners.iterator(); 337 while(it.hasNext()) { 338 it.next().onServiceConnected(); 339 } 340 } 341 } 342 343 public void onServiceDisconnected() { 344 profileReady = false; 345 if (mServiceListeners.size() > 0) { 346 Iterator<ServiceListener> it = mServiceListeners.iterator(); 347 while(it.hasNext()) { 348 it.next().onServiceDisconnected(); 349 } 350 } 351 } 352 353 @Override 354 public boolean isProfileReady() { 355 return profileReady; 356 } 357 358 @Override 359 public Set<BluetoothDevice> getConnectedDevices() { 360 Set<BluetoothDevice> devices = null; 361 BluetoothDevice device = mService.getCurrentHeadset(); 362 if (device != null) { 363 devices = new HashSet<BluetoothDevice>(); 364 devices.add(device); 365 } 366 return devices; 367 } 368 369 @Override 370 public boolean connect(BluetoothDevice device) { 371 // Since connectHeadset fails if already connected to a headset, we 372 // disconnect from any headset first 373 BluetoothDevice currDevice = mService.getCurrentHeadset(); 374 if (currDevice != null) { 375 mService.disconnectHeadset(currDevice); 376 } 377 return mService.connectHeadset(device); 378 } 379 380 @Override 381 public boolean disconnect(BluetoothDevice device) { 382 if (mService.getCurrentHeadset().equals(device)) { 383 // Downgrade prority as user is disconnecting the headset. 384 if (mService.getPriority(device) > BluetoothHeadset.PRIORITY_ON) { 385 mService.setPriority(device, BluetoothHeadset.PRIORITY_ON); 386 } 387 return mService.disconnectHeadset(device); 388 } else { 389 return false; 390 } 391 } 392 393 @Override 394 public int getConnectionStatus(BluetoothDevice device) { 395 BluetoothDevice currentDevice = mService.getCurrentHeadset(); 396 return currentDevice != null && currentDevice.equals(device) 397 ? convertState(mService.getState(device)) 398 : SettingsBtStatus.CONNECTION_STATUS_DISCONNECTED; 399 } 400 401 @Override 402 public int getSummary(BluetoothDevice device) { 403 int connectionStatus = getConnectionStatus(device); 404 405 if (SettingsBtStatus.isConnectionStatusConnected(connectionStatus)) { 406 return R.string.bluetooth_headset_profile_summary_connected; 407 } else { 408 return SettingsBtStatus.getConnectionStatusSummary(connectionStatus); 409 } 410 } 411 412 @Override 413 public boolean isPreferred(BluetoothDevice device) { 414 return mService.getPriority(device) > BluetoothHeadset.PRIORITY_OFF; 415 } 416 417 @Override 418 public int getPreferred(BluetoothDevice device) { 419 return mService.getPriority(device); 420 } 421 422 @Override 423 public void setPreferred(BluetoothDevice device, boolean preferred) { 424 if (preferred) { 425 if (mService.getPriority(device) < BluetoothHeadset.PRIORITY_ON) { 426 mService.setPriority(device, BluetoothHeadset.PRIORITY_ON); 427 } 428 } else { 429 mService.setPriority(device, BluetoothHeadset.PRIORITY_OFF); 430 } 431 } 432 433 @Override 434 public int convertState(int headsetState) { 435 switch (headsetState) { 436 case BluetoothHeadset.STATE_CONNECTED: 437 return SettingsBtStatus.CONNECTION_STATUS_CONNECTED; 438 case BluetoothHeadset.STATE_CONNECTING: 439 return SettingsBtStatus.CONNECTION_STATUS_CONNECTING; 440 case BluetoothHeadset.STATE_DISCONNECTED: 441 return SettingsBtStatus.CONNECTION_STATUS_DISCONNECTED; 442 default: 443 return SettingsBtStatus.CONNECTION_STATUS_UNKNOWN; 444 } 445 } 446 } 447 448 /** 449 * OppProfileManager 450 */ 451 private static class OppProfileManager extends LocalBluetoothProfileManager { 452 453 public OppProfileManager(LocalBluetoothManager localManager) { 454 super(localManager); 455 } 456 457 @Override 458 public Set<BluetoothDevice> getConnectedDevices() { 459 return null; 460 } 461 462 @Override 463 public boolean connect(BluetoothDevice device) { 464 return false; 465 } 466 467 @Override 468 public boolean disconnect(BluetoothDevice device) { 469 return false; 470 } 471 472 @Override 473 public int getConnectionStatus(BluetoothDevice device) { 474 return -1; 475 } 476 477 @Override 478 public int getSummary(BluetoothDevice device) { 479 int connectionStatus = getConnectionStatus(device); 480 481 if (SettingsBtStatus.isConnectionStatusConnected(connectionStatus)) { 482 return R.string.bluetooth_opp_profile_summary_connected; 483 } else { 484 return R.string.bluetooth_opp_profile_summary_not_connected; 485 } 486 } 487 488 @Override 489 public boolean isPreferred(BluetoothDevice device) { 490 return false; 491 } 492 493 @Override 494 public int getPreferred(BluetoothDevice device) { 495 return -1; 496 } 497 498 @Override 499 public void setPreferred(BluetoothDevice device, boolean preferred) { 500 } 501 502 @Override 503 public boolean isProfileReady() { 504 return true; 505 } 506 507 @Override 508 public int convertState(int oppState) { 509 switch (oppState) { 510 case 0: 511 return SettingsBtStatus.CONNECTION_STATUS_CONNECTED; 512 case 1: 513 return SettingsBtStatus.CONNECTION_STATUS_CONNECTING; 514 case 2: 515 return SettingsBtStatus.CONNECTION_STATUS_DISCONNECTED; 516 default: 517 return SettingsBtStatus.CONNECTION_STATUS_UNKNOWN; 518 } 519 } 520 } 521} 522