LocalBluetoothProfileManager.java revision abc48f80d8747b4fc051b7dd364355ee667a9bac
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.BluetoothError; 23import android.bluetooth.BluetoothHeadset; 24import android.bluetooth.BluetoothClass; 25import android.content.Context; 26import android.content.SharedPreferences; 27 28import java.util.HashMap; 29import java.util.List; 30import java.util.Map; 31 32/** 33 * LocalBluetoothProfileManager is an abstract class defining the basic 34 * functionality related to a profile. 35 */ 36public abstract class LocalBluetoothProfileManager { 37 38 // TODO: close profiles when we're shutting down 39 private static Map<Profile, LocalBluetoothProfileManager> sProfileMap = 40 new HashMap<Profile, LocalBluetoothProfileManager>(); 41 42 protected LocalBluetoothManager mLocalManager; 43 44 public static LocalBluetoothProfileManager getProfileManager(LocalBluetoothManager localManager, 45 Profile profile) { 46 47 LocalBluetoothProfileManager profileManager; 48 49 synchronized (sProfileMap) { 50 profileManager = sProfileMap.get(profile); 51 52 if (profileManager == null) { 53 switch (profile) { 54 case A2DP: 55 profileManager = new A2dpProfileManager(localManager); 56 break; 57 58 case HEADSET: 59 profileManager = new HeadsetProfileManager(localManager); 60 break; 61 } 62 63 sProfileMap.put(profile, profileManager); 64 } 65 } 66 67 return profileManager; 68 } 69 70 // TODO: remove once the framework has this API 71 public static boolean isPreferredProfile(Context context, String address, Profile profile) { 72 return getPreferredProfileSharedPreferences(context).getBoolean( 73 getPreferredProfileKey(address, profile), true); 74 } 75 76 public static void setPreferredProfile(Context context, String address, Profile profile, 77 boolean preferred) { 78 getPreferredProfileSharedPreferences(context).edit().putBoolean( 79 getPreferredProfileKey(address, profile), preferred).commit(); 80 } 81 82 private static SharedPreferences getPreferredProfileSharedPreferences(Context context) { 83 return context.getSharedPreferences("bluetooth_preferred_profiles", Context.MODE_PRIVATE); 84 } 85 86 private static String getPreferredProfileKey(String address, Profile profile) { 87 return address + "_" + profile.toString(); 88 } 89 90 /** 91 * Temporary method to fill profiles based on a device's class. 92 * 93 * @param btClass The class 94 * @param profiles The list of profiles to fill 95 */ 96 public static void fill(int btClass, List<Profile> profiles) { 97 profiles.clear(); 98 99 if (A2dpProfileManager.doesClassMatch(btClass)) { 100 profiles.add(Profile.A2DP); 101 } 102 103 if (HeadsetProfileManager.doesClassMatch(btClass)) { 104 profiles.add(Profile.HEADSET); 105 } 106 } 107 108 protected LocalBluetoothProfileManager(LocalBluetoothManager localManager) { 109 mLocalManager = localManager; 110 } 111 112 public abstract int connect(String address); 113 114 public abstract int disconnect(String address); 115 116 public abstract int getConnectionStatus(String address); 117 118 public abstract int getSummary(String address); 119 120 public boolean isConnected(String address) { 121 return SettingsBtStatus.isConnectionStatusConnected(getConnectionStatus(address)); 122 } 123 124 // TODO: int instead of enum 125 public enum Profile { 126 HEADSET(R.string.bluetooth_profile_headset), 127 A2DP(R.string.bluetooth_profile_a2dp); 128 129 public final int localizedString; 130 131 private Profile(int localizedString) { 132 this.localizedString = localizedString; 133 } 134 } 135 136 /** 137 * A2dpProfileManager is an abstraction for the {@link BluetoothA2dp} service. 138 */ 139 private static class A2dpProfileManager extends LocalBluetoothProfileManager { 140 private BluetoothA2dp mService; 141 142 public A2dpProfileManager(LocalBluetoothManager localManager) { 143 super(localManager); 144 145 mService = new BluetoothA2dp(localManager.getContext()); 146 // TODO: block until connection? 147 } 148 149 @Override 150 public int connect(String address) { 151 return mService.connectSink(address); 152 } 153 154 @Override 155 public int disconnect(String address) { 156 return mService.disconnectSink(address); 157 } 158 159 static boolean doesClassMatch(int btClass) { 160 if (BluetoothClass.Service.hasService(btClass, BluetoothClass.Service.RENDER)) { 161 return true; 162 } 163 164 // By the specification A2DP sinks must indicate the RENDER service 165 // class, but some do not (Chordette). So match on a few more to be 166 // safe 167 switch (BluetoothClass.Device.getDevice(btClass)) { 168 case BluetoothClass.Device.AUDIO_VIDEO_HIFI_AUDIO: 169 case BluetoothClass.Device.AUDIO_VIDEO_HEADPHONES: 170 case BluetoothClass.Device.AUDIO_VIDEO_LOUDSPEAKER: 171 case BluetoothClass.Device.AUDIO_VIDEO_CAR_AUDIO: 172 return true; 173 174 default: 175 return false; 176 } 177 } 178 179 @Override 180 public int getConnectionStatus(String address) { 181 return convertState(mService.getSinkState(address)); 182 } 183 184 @Override 185 public int getSummary(String address) { 186 int connectionStatus = getConnectionStatus(address); 187 188 if (SettingsBtStatus.isConnectionStatusConnected(connectionStatus)) { 189 return R.string.bluetooth_a2dp_profile_summary_connected; 190 } else { 191 return SettingsBtStatus.getConnectionStatusSummary(connectionStatus); 192 } 193 } 194 195 private static int convertState(int a2dpState) { 196 switch (a2dpState) { 197 case BluetoothA2dp.STATE_CONNECTED: 198 return SettingsBtStatus.CONNECTION_STATUS_CONNECTED; 199 case BluetoothA2dp.STATE_CONNECTING: 200 return SettingsBtStatus.CONNECTION_STATUS_CONNECTING; 201 case BluetoothA2dp.STATE_DISCONNECTED: 202 return SettingsBtStatus.CONNECTION_STATUS_DISCONNECTED; 203 case BluetoothA2dp.STATE_DISCONNECTING: 204 return SettingsBtStatus.CONNECTION_STATUS_DISCONNECTING; 205 case BluetoothA2dp.STATE_PLAYING: 206 return SettingsBtStatus.CONNECTION_STATUS_ACTIVE; 207 default: 208 return SettingsBtStatus.CONNECTION_STATUS_UNKNOWN; 209 } 210 } 211 } 212 213 /** 214 * HeadsetProfileManager is an abstraction for the {@link BluetoothHeadset} service. 215 */ 216 private static class HeadsetProfileManager extends LocalBluetoothProfileManager { 217 private BluetoothHeadset mService; 218 219 public HeadsetProfileManager(LocalBluetoothManager localManager) { 220 super(localManager); 221 222// final boolean[] isServiceConnected = new boolean[1]; 223// BluetoothHeadset.ServiceListener l = new BluetoothHeadset.ServiceListener() { 224// public void onServiceConnected() { 225// synchronized (this) { 226// isServiceConnected[0] = true; 227// notifyAll(); 228// } 229// } 230// public void onServiceDisconnected() { 231// mService = null; 232// } 233// }; 234 235 // TODO: block, but can't on UI thread 236 mService = new BluetoothHeadset(localManager.getContext(), null); 237 238// synchronized (l) { 239// while (!isServiceConnected[0]) { 240// try { 241// l.wait(100); 242// } catch (InterruptedException e) { 243// throw new IllegalStateException(e); 244// } 245// } 246// } 247 } 248 249 @Override 250 public int connect(String address) { 251 // Since connectHeadset fails if already connected to a headset, we 252 // disconnect from any headset first 253 mService.disconnectHeadset(); 254 return mService.connectHeadset(address, null) 255 ? BluetoothError.SUCCESS : BluetoothError.ERROR; 256 } 257 258 @Override 259 public int disconnect(String address) { 260 if (mService.getHeadsetAddress().equals(address)) { 261 return mService.disconnectHeadset() ? BluetoothError.SUCCESS : BluetoothError.ERROR; 262 } else { 263 return BluetoothError.SUCCESS; 264 } 265 } 266 267 static boolean doesClassMatch(int btClass) { 268 switch (BluetoothClass.Device.getDevice(btClass)) { 269 case BluetoothClass.Device.AUDIO_VIDEO_HANDSFREE: 270 case BluetoothClass.Device.AUDIO_VIDEO_WEARABLE_HEADSET: 271 case BluetoothClass.Device.AUDIO_VIDEO_CAR_AUDIO: 272 return true; 273 274 default: 275 return false; 276 } 277 } 278 279 @Override 280 public int getConnectionStatus(String address) { 281 String headsetAddress = mService.getHeadsetAddress(); 282 return headsetAddress != null && headsetAddress.equals(address) 283 ? convertState(mService.getState()) 284 : SettingsBtStatus.CONNECTION_STATUS_DISCONNECTED; 285 } 286 287 @Override 288 public int getSummary(String address) { 289 int connectionStatus = getConnectionStatus(address); 290 291 if (SettingsBtStatus.isConnectionStatusConnected(connectionStatus)) { 292 return R.string.bluetooth_headset_profile_summary_connected; 293 } else { 294 return SettingsBtStatus.getConnectionStatusSummary(connectionStatus); 295 } 296 } 297 298 private static int convertState(int headsetState) { 299 switch (headsetState) { 300 case BluetoothHeadset.STATE_CONNECTED: 301 return SettingsBtStatus.CONNECTION_STATUS_CONNECTED; 302 case BluetoothHeadset.STATE_CONNECTING: 303 return SettingsBtStatus.CONNECTION_STATUS_CONNECTING; 304 case BluetoothHeadset.STATE_DISCONNECTED: 305 return SettingsBtStatus.CONNECTION_STATUS_DISCONNECTED; 306 default: 307 return SettingsBtStatus.CONNECTION_STATUS_UNKNOWN; 308 } 309 } 310 } 311 312} 313