LocalBluetoothProfileManager.java revision 1152aff9d0767e528aa4a40cc8acb51b9c21d2e7
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; 27import android.os.Handler; 28import android.text.TextUtils; 29 30import java.util.HashMap; 31import java.util.List; 32import java.util.Map; 33 34/** 35 * LocalBluetoothProfileManager is an abstract class defining the basic 36 * functionality related to a profile. 37 */ 38public abstract class LocalBluetoothProfileManager { 39 40 // TODO: close profiles when we're shutting down 41 private static Map<Profile, LocalBluetoothProfileManager> sProfileMap = 42 new HashMap<Profile, LocalBluetoothProfileManager>(); 43 44 protected LocalBluetoothManager mLocalManager; 45 46 public static LocalBluetoothProfileManager getProfileManager(LocalBluetoothManager localManager, 47 Profile profile) { 48 49 LocalBluetoothProfileManager profileManager; 50 51 synchronized (sProfileMap) { 52 profileManager = sProfileMap.get(profile); 53 54 if (profileManager == null) { 55 switch (profile) { 56 case A2DP: 57 profileManager = new A2dpProfileManager(localManager); 58 break; 59 60 case HEADSET: 61 profileManager = new HeadsetProfileManager(localManager); 62 break; 63 } 64 65 sProfileMap.put(profile, profileManager); 66 } 67 } 68 69 return profileManager; 70 } 71 72 // TODO: remove once the framework has this API 73 public static boolean isPreferredProfile(Context context, String address, Profile profile) { 74 return getPreferredProfileSharedPreferences(context).getBoolean( 75 getPreferredProfileKey(address, profile), true); 76 } 77 78 public static void setPreferredProfile(Context context, String address, Profile profile, 79 boolean preferred) { 80 getPreferredProfileSharedPreferences(context).edit().putBoolean( 81 getPreferredProfileKey(address, profile), preferred).commit(); 82 } 83 84 private static SharedPreferences getPreferredProfileSharedPreferences(Context context) { 85 return context.getSharedPreferences("bluetooth_preferred_profiles", Context.MODE_PRIVATE); 86 } 87 88 private static String getPreferredProfileKey(String address, Profile profile) { 89 return address + "_" + profile.toString(); 90 } 91 92 /** 93 * Temporary method to fill profiles based on a device's class. 94 * 95 * @param btClass The class 96 * @param profiles The list of profiles to fill 97 */ 98 public static void fill(int btClass, List<Profile> profiles) { 99 profiles.clear(); 100 101 if (A2dpProfileManager.doesClassMatch(btClass)) { 102 profiles.add(Profile.A2DP); 103 } 104 105 if (HeadsetProfileManager.doesClassMatch(btClass)) { 106 profiles.add(Profile.HEADSET); 107 } 108 } 109 110 protected LocalBluetoothProfileManager(LocalBluetoothManager localManager) { 111 mLocalManager = localManager; 112 } 113 114 public abstract int connect(String address); 115 116 public abstract int disconnect(String address); 117 118 public abstract int getConnectionStatus(String address); 119 120 public abstract int getSummary(String address); 121 122 public boolean isConnected(String address) { 123 return SettingsBtStatus.isConnectionStatusConnected(getConnectionStatus(address)); 124 } 125 126 // TODO: int instead of enum 127 public enum Profile { 128 HEADSET(R.string.bluetooth_profile_headset), 129 A2DP(R.string.bluetooth_profile_a2dp); 130 131 public final int localizedString; 132 133 private Profile(int localizedString) { 134 this.localizedString = localizedString; 135 } 136 } 137 138 /** 139 * A2dpProfileManager is an abstraction for the {@link BluetoothA2dp} service. 140 */ 141 private static class A2dpProfileManager extends LocalBluetoothProfileManager { 142 private BluetoothA2dp mService; 143 144 public A2dpProfileManager(LocalBluetoothManager localManager) { 145 super(localManager); 146 mService = new BluetoothA2dp(localManager.getContext()); 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 implements BluetoothHeadset.ServiceListener { 218 private BluetoothHeadset mService; 219 private Handler mUiHandler = new Handler(); 220 221 public HeadsetProfileManager(LocalBluetoothManager localManager) { 222 super(localManager); 223 mService = new BluetoothHeadset(localManager.getContext(), this); 224 } 225 226 public void onServiceConnected() { 227 // This could be called on a non-UI thread, funnel to UI thread. 228 mUiHandler.post(new Runnable() { 229 public void run() { 230 /* 231 * We just bound to the service, so refresh the UI of the 232 * headset device. 233 */ 234 String address = mService.getHeadsetAddress(); 235 if (TextUtils.isEmpty(address)) return; 236 mLocalManager.getLocalDeviceManager().onProfileStateChanged(address); 237 } 238 }); 239 } 240 241 public void onServiceDisconnected() { 242 } 243 244 @Override 245 public int connect(String address) { 246 // Since connectHeadset fails if already connected to a headset, we 247 // disconnect from any headset first 248 mService.disconnectHeadset(); 249 return mService.connectHeadset(address, null) 250 ? BluetoothError.SUCCESS : BluetoothError.ERROR; 251 } 252 253 @Override 254 public int disconnect(String address) { 255 if (mService.getHeadsetAddress().equals(address)) { 256 return mService.disconnectHeadset() ? BluetoothError.SUCCESS : BluetoothError.ERROR; 257 } else { 258 return BluetoothError.SUCCESS; 259 } 260 } 261 262 static boolean doesClassMatch(int btClass) { 263 switch (BluetoothClass.Device.getDevice(btClass)) { 264 case BluetoothClass.Device.AUDIO_VIDEO_HANDSFREE: 265 case BluetoothClass.Device.AUDIO_VIDEO_WEARABLE_HEADSET: 266 case BluetoothClass.Device.AUDIO_VIDEO_CAR_AUDIO: 267 return true; 268 269 default: 270 return false; 271 } 272 } 273 274 @Override 275 public int getConnectionStatus(String address) { 276 String headsetAddress = mService.getHeadsetAddress(); 277 return headsetAddress != null && headsetAddress.equals(address) 278 ? convertState(mService.getState()) 279 : SettingsBtStatus.CONNECTION_STATUS_DISCONNECTED; 280 } 281 282 @Override 283 public int getSummary(String address) { 284 int connectionStatus = getConnectionStatus(address); 285 286 if (SettingsBtStatus.isConnectionStatusConnected(connectionStatus)) { 287 return R.string.bluetooth_headset_profile_summary_connected; 288 } else { 289 return SettingsBtStatus.getConnectionStatusSummary(connectionStatus); 290 } 291 } 292 293 private static int convertState(int headsetState) { 294 switch (headsetState) { 295 case BluetoothHeadset.STATE_CONNECTED: 296 return SettingsBtStatus.CONNECTION_STATUS_CONNECTED; 297 case BluetoothHeadset.STATE_CONNECTING: 298 return SettingsBtStatus.CONNECTION_STATUS_CONNECTING; 299 case BluetoothHeadset.STATE_DISCONNECTED: 300 return SettingsBtStatus.CONNECTION_STATUS_DISCONNECTED; 301 default: 302 return SettingsBtStatus.CONNECTION_STATUS_UNKNOWN; 303 } 304 } 305 } 306 307} 308