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