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 17 package com.android.settings.bluetooth; 18 19 import android.bluetooth.BluetoothA2dp; 20 import android.bluetooth.BluetoothDevice; 21 import android.bluetooth.BluetoothHeadset; 22 import android.bluetooth.BluetoothUuid; 23 import android.os.ParcelUuid; 24 import android.os.Handler; 25 import android.util.Log; 26 27 import com.android.settings.R; 28 29 import java.util.HashMap; 30 import java.util.List; 31 import java.util.Map; 32 import java.util.Set; 33 34 /** 35 * LocalBluetoothProfileManager is an abstract class defining the basic 36 * functionality related to a profile. 37 */ 38 public abstract class LocalBluetoothProfileManager { 39 private static final String TAG = "LocalBluetoothProfileManager"; 40 41 /* package */ static final ParcelUuid[] HEADSET_PROFILE_UUIDS = new ParcelUuid[] { 42 BluetoothUuid.HSP, 43 BluetoothUuid.Handsfree, 44 }; 45 46 /* package */ static final ParcelUuid[] A2DP_PROFILE_UUIDS = new ParcelUuid[] { 47 BluetoothUuid.AudioSink, 48 BluetoothUuid.AdvAudioDist, 49 }; 50 51 /* package */ static final ParcelUuid[] OPP_PROFILE_UUIDS = new ParcelUuid[] { 52 BluetoothUuid.ObexObjectPush 53 }; 54 55 // TODO: close profiles when we're shutting down 56 private static Map<Profile, LocalBluetoothProfileManager> sProfileMap = 57 new HashMap<Profile, LocalBluetoothProfileManager>(); 58 59 protected LocalBluetoothManager mLocalManager; 60 init(LocalBluetoothManager localManager)61 public static void init(LocalBluetoothManager localManager) { 62 synchronized (sProfileMap) { 63 if (sProfileMap.size() == 0) { 64 LocalBluetoothProfileManager profileManager; 65 66 profileManager = new A2dpProfileManager(localManager); 67 sProfileMap.put(Profile.A2DP, profileManager); 68 69 profileManager = new HeadsetProfileManager(localManager); 70 sProfileMap.put(Profile.HEADSET, profileManager); 71 72 profileManager = new OppProfileManager(localManager); 73 sProfileMap.put(Profile.OPP, profileManager); 74 } 75 } 76 } 77 getProfileManager(LocalBluetoothManager localManager, Profile profile)78 public static LocalBluetoothProfileManager getProfileManager(LocalBluetoothManager localManager, 79 Profile profile) { 80 // Note: This code assumes that "localManager" is same as the 81 // LocalBluetoothManager that was used to initialize the sProfileMap. 82 // If that every changes, we can't just keep one copy of sProfileMap. 83 synchronized (sProfileMap) { 84 LocalBluetoothProfileManager profileManager = sProfileMap.get(profile); 85 if (profileManager == null) { 86 Log.e(TAG, "profileManager can't be found for " + profile.toString()); 87 } 88 return profileManager; 89 } 90 } 91 92 /** 93 * Temporary method to fill profiles based on a device's class. 94 * 95 * NOTE: This list happens to define the connection order. We should put this logic in a more 96 * well known place when this method is no longer temporary. 97 * @param uuids of the remote device 98 * @param profiles The list of profiles to fill 99 */ updateProfiles(ParcelUuid[] uuids, List<Profile> profiles)100 public static void updateProfiles(ParcelUuid[] uuids, List<Profile> profiles) { 101 profiles.clear(); 102 103 if (uuids == null) { 104 return; 105 } 106 107 if (BluetoothUuid.containsAnyUuid(uuids, HEADSET_PROFILE_UUIDS)) { 108 profiles.add(Profile.HEADSET); 109 } 110 111 if (BluetoothUuid.containsAnyUuid(uuids, A2DP_PROFILE_UUIDS)) { 112 profiles.add(Profile.A2DP); 113 } 114 115 if (BluetoothUuid.containsAnyUuid(uuids, OPP_PROFILE_UUIDS)) { 116 profiles.add(Profile.OPP); 117 } 118 } 119 LocalBluetoothProfileManager(LocalBluetoothManager localManager)120 protected LocalBluetoothProfileManager(LocalBluetoothManager localManager) { 121 mLocalManager = localManager; 122 } 123 connect(BluetoothDevice device)124 public abstract boolean connect(BluetoothDevice device); 125 disconnect(BluetoothDevice device)126 public abstract boolean disconnect(BluetoothDevice device); 127 getConnectionStatus(BluetoothDevice device)128 public abstract int getConnectionStatus(BluetoothDevice device); 129 getSummary(BluetoothDevice device)130 public abstract int getSummary(BluetoothDevice device); 131 convertState(int a2dpState)132 public abstract int convertState(int a2dpState); 133 isPreferred(BluetoothDevice device)134 public abstract boolean isPreferred(BluetoothDevice device); 135 setPreferred(BluetoothDevice device, boolean preferred)136 public abstract void setPreferred(BluetoothDevice device, boolean preferred); 137 isConnected(BluetoothDevice device)138 public boolean isConnected(BluetoothDevice device) { 139 return SettingsBtStatus.isConnectionStatusConnected(getConnectionStatus(device)); 140 } 141 142 // TODO: int instead of enum 143 public enum Profile { 144 HEADSET(R.string.bluetooth_profile_headset), 145 A2DP(R.string.bluetooth_profile_a2dp), 146 OPP(R.string.bluetooth_profile_opp); 147 148 public final int localizedString; 149 Profile(int localizedString)150 private Profile(int localizedString) { 151 this.localizedString = localizedString; 152 } 153 } 154 155 /** 156 * A2dpProfileManager is an abstraction for the {@link BluetoothA2dp} service. 157 */ 158 private static class A2dpProfileManager extends LocalBluetoothProfileManager { 159 private BluetoothA2dp mService; 160 A2dpProfileManager(LocalBluetoothManager localManager)161 public A2dpProfileManager(LocalBluetoothManager localManager) { 162 super(localManager); 163 mService = new BluetoothA2dp(localManager.getContext()); 164 } 165 166 @Override connect(BluetoothDevice device)167 public boolean connect(BluetoothDevice device) { 168 Set<BluetoothDevice> sinks = mService.getConnectedSinks(); 169 if (sinks != null) { 170 for (BluetoothDevice sink : sinks) { 171 mService.disconnectSink(sink); 172 } 173 } 174 return mService.connectSink(device); 175 } 176 177 @Override disconnect(BluetoothDevice device)178 public boolean disconnect(BluetoothDevice device) { 179 return mService.disconnectSink(device); 180 } 181 182 @Override getConnectionStatus(BluetoothDevice device)183 public int getConnectionStatus(BluetoothDevice device) { 184 return convertState(mService.getSinkState(device)); 185 } 186 187 @Override getSummary(BluetoothDevice device)188 public int getSummary(BluetoothDevice device) { 189 int connectionStatus = getConnectionStatus(device); 190 191 if (SettingsBtStatus.isConnectionStatusConnected(connectionStatus)) { 192 return R.string.bluetooth_a2dp_profile_summary_connected; 193 } else { 194 return SettingsBtStatus.getConnectionStatusSummary(connectionStatus); 195 } 196 } 197 198 @Override isPreferred(BluetoothDevice device)199 public boolean isPreferred(BluetoothDevice device) { 200 return mService.getSinkPriority(device) > BluetoothA2dp.PRIORITY_OFF; 201 } 202 203 @Override setPreferred(BluetoothDevice device, boolean preferred)204 public void setPreferred(BluetoothDevice device, boolean preferred) { 205 mService.setSinkPriority(device, 206 preferred ? BluetoothA2dp.PRIORITY_AUTO : BluetoothA2dp.PRIORITY_OFF); 207 } 208 209 @Override convertState(int a2dpState)210 public int convertState(int a2dpState) { 211 switch (a2dpState) { 212 case BluetoothA2dp.STATE_CONNECTED: 213 return SettingsBtStatus.CONNECTION_STATUS_CONNECTED; 214 case BluetoothA2dp.STATE_CONNECTING: 215 return SettingsBtStatus.CONNECTION_STATUS_CONNECTING; 216 case BluetoothA2dp.STATE_DISCONNECTED: 217 return SettingsBtStatus.CONNECTION_STATUS_DISCONNECTED; 218 case BluetoothA2dp.STATE_DISCONNECTING: 219 return SettingsBtStatus.CONNECTION_STATUS_DISCONNECTING; 220 case BluetoothA2dp.STATE_PLAYING: 221 return SettingsBtStatus.CONNECTION_STATUS_ACTIVE; 222 default: 223 return SettingsBtStatus.CONNECTION_STATUS_UNKNOWN; 224 } 225 } 226 } 227 228 /** 229 * HeadsetProfileManager is an abstraction for the {@link BluetoothHeadset} service. 230 */ 231 private static class HeadsetProfileManager extends LocalBluetoothProfileManager 232 implements BluetoothHeadset.ServiceListener { 233 private BluetoothHeadset mService; 234 private Handler mUiHandler = new Handler(); 235 HeadsetProfileManager(LocalBluetoothManager localManager)236 public HeadsetProfileManager(LocalBluetoothManager localManager) { 237 super(localManager); 238 mService = new BluetoothHeadset(localManager.getContext(), this); 239 } 240 onServiceConnected()241 public void onServiceConnected() { 242 // This could be called on a non-UI thread, funnel to UI thread. 243 mUiHandler.post(new Runnable() { 244 public void run() { 245 /* 246 * We just bound to the service, so refresh the UI of the 247 * headset device. 248 */ 249 BluetoothDevice device = mService.getCurrentHeadset(); 250 if (device == null) return; 251 mLocalManager.getCachedDeviceManager() 252 .onProfileStateChanged(device, Profile.HEADSET, 253 BluetoothHeadset.STATE_CONNECTED); 254 } 255 }); 256 } 257 onServiceDisconnected()258 public void onServiceDisconnected() { 259 } 260 261 @Override connect(BluetoothDevice device)262 public boolean connect(BluetoothDevice device) { 263 // Since connectHeadset fails if already connected to a headset, we 264 // disconnect from any headset first 265 mService.disconnectHeadset(); 266 return mService.connectHeadset(device); 267 } 268 269 @Override disconnect(BluetoothDevice device)270 public boolean disconnect(BluetoothDevice device) { 271 if (mService.getCurrentHeadset().equals(device)) { 272 return mService.disconnectHeadset(); 273 } else { 274 return false; 275 } 276 } 277 278 @Override getConnectionStatus(BluetoothDevice device)279 public int getConnectionStatus(BluetoothDevice device) { 280 BluetoothDevice currentDevice = mService.getCurrentHeadset(); 281 return currentDevice != null && currentDevice.equals(device) 282 ? convertState(mService.getState()) 283 : SettingsBtStatus.CONNECTION_STATUS_DISCONNECTED; 284 } 285 286 @Override getSummary(BluetoothDevice device)287 public int getSummary(BluetoothDevice device) { 288 int connectionStatus = getConnectionStatus(device); 289 290 if (SettingsBtStatus.isConnectionStatusConnected(connectionStatus)) { 291 return R.string.bluetooth_headset_profile_summary_connected; 292 } else { 293 return SettingsBtStatus.getConnectionStatusSummary(connectionStatus); 294 } 295 } 296 297 @Override isPreferred(BluetoothDevice device)298 public boolean isPreferred(BluetoothDevice device) { 299 return mService.getPriority(device) > BluetoothHeadset.PRIORITY_OFF; 300 } 301 302 @Override setPreferred(BluetoothDevice device, boolean preferred)303 public void setPreferred(BluetoothDevice device, boolean preferred) { 304 mService.setPriority(device, 305 preferred ? BluetoothHeadset.PRIORITY_AUTO : BluetoothHeadset.PRIORITY_OFF); 306 } 307 308 @Override convertState(int headsetState)309 public int convertState(int headsetState) { 310 switch (headsetState) { 311 case BluetoothHeadset.STATE_CONNECTED: 312 return SettingsBtStatus.CONNECTION_STATUS_CONNECTED; 313 case BluetoothHeadset.STATE_CONNECTING: 314 return SettingsBtStatus.CONNECTION_STATUS_CONNECTING; 315 case BluetoothHeadset.STATE_DISCONNECTED: 316 return SettingsBtStatus.CONNECTION_STATUS_DISCONNECTED; 317 default: 318 return SettingsBtStatus.CONNECTION_STATUS_UNKNOWN; 319 } 320 } 321 } 322 323 /** 324 * OppProfileManager 325 */ 326 private static class OppProfileManager extends LocalBluetoothProfileManager { 327 OppProfileManager(LocalBluetoothManager localManager)328 public OppProfileManager(LocalBluetoothManager localManager) { 329 super(localManager); 330 } 331 332 @Override connect(BluetoothDevice device)333 public boolean connect(BluetoothDevice device) { 334 return false; 335 } 336 337 @Override disconnect(BluetoothDevice device)338 public boolean disconnect(BluetoothDevice device) { 339 return false; 340 } 341 342 @Override getConnectionStatus(BluetoothDevice device)343 public int getConnectionStatus(BluetoothDevice device) { 344 return -1; 345 } 346 347 @Override getSummary(BluetoothDevice device)348 public int getSummary(BluetoothDevice device) { 349 int connectionStatus = getConnectionStatus(device); 350 351 if (SettingsBtStatus.isConnectionStatusConnected(connectionStatus)) { 352 return R.string.bluetooth_opp_profile_summary_connected; 353 } else { 354 return R.string.bluetooth_opp_profile_summary_not_connected; 355 } 356 } 357 358 @Override isPreferred(BluetoothDevice device)359 public boolean isPreferred(BluetoothDevice device) { 360 return false; 361 } 362 363 @Override setPreferred(BluetoothDevice device, boolean preferred)364 public void setPreferred(BluetoothDevice device, boolean preferred) { 365 } 366 367 @Override convertState(int oppState)368 public int convertState(int oppState) { 369 switch (oppState) { 370 case 0: 371 return SettingsBtStatus.CONNECTION_STATUS_CONNECTED; 372 case 1: 373 return SettingsBtStatus.CONNECTION_STATUS_CONNECTING; 374 case 2: 375 return SettingsBtStatus.CONNECTION_STATUS_DISCONNECTED; 376 default: 377 return SettingsBtStatus.CONNECTION_STATUS_UNKNOWN; 378 } 379 } 380 } 381 } 382