BluetoothControllerImpl.java revision 650639f9a5aeeec9bb697bea50a27bb702205d70
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.systemui.statusbar.policy; 18 19import static android.bluetooth.BluetoothAdapter.ERROR; 20import static com.android.systemui.statusbar.policy.BluetoothUtil.connectionStateToString; 21import static com.android.systemui.statusbar.policy.BluetoothUtil.deviceToString; 22import static com.android.systemui.statusbar.policy.BluetoothUtil.profileToString; 23import static com.android.systemui.statusbar.policy.BluetoothUtil.uuidToProfile; 24import static com.android.systemui.statusbar.policy.BluetoothUtil.uuidToString; 25import static com.android.systemui.statusbar.policy.BluetoothUtil.uuidsToString; 26 27import android.bluetooth.BluetoothA2dp; 28import android.bluetooth.BluetoothA2dpSink; 29import android.bluetooth.BluetoothAdapter; 30import android.bluetooth.BluetoothDevice; 31import android.bluetooth.BluetoothHeadset; 32import android.bluetooth.BluetoothHeadsetClient; 33import android.bluetooth.BluetoothInputDevice; 34import android.bluetooth.BluetoothManager; 35import android.bluetooth.BluetoothMap; 36import android.bluetooth.BluetoothPan; 37import android.bluetooth.BluetoothProfile; 38import android.bluetooth.BluetoothProfile.ServiceListener; 39import android.content.BroadcastReceiver; 40import android.content.Context; 41import android.content.Intent; 42import android.content.IntentFilter; 43import android.os.ParcelUuid; 44import android.util.ArrayMap; 45import android.util.ArraySet; 46import android.util.Log; 47import android.util.SparseArray; 48 49import com.android.systemui.statusbar.policy.BluetoothUtil.Profile; 50 51import java.io.FileDescriptor; 52import java.io.PrintWriter; 53import java.util.ArrayList; 54import java.util.List; 55import java.util.Set; 56 57public class BluetoothControllerImpl implements BluetoothController { 58 private static final String TAG = "BluetoothController"; 59 private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); 60 // This controls the order in which we check the states. Since a device can only have 61 // one state on screen, but can have multiple profiles, the later states override the 62 // value of earlier states. So if a device has a profile in CONNECTING and one in 63 // CONNECTED, it will show as CONNECTED, theoretically this shouldn't really happen often, 64 // but seemed worth noting. 65 private static final int[] CONNECTION_STATES = { 66 BluetoothProfile.STATE_DISCONNECTED, 67 BluetoothProfile.STATE_DISCONNECTING, 68 BluetoothProfile.STATE_CONNECTING, 69 BluetoothProfile.STATE_CONNECTED, 70 }; 71 72 private final Context mContext; 73 private final ArrayList<Callback> mCallbacks = new ArrayList<Callback>(); 74 private final BluetoothAdapter mAdapter; 75 private final Receiver mReceiver = new Receiver(); 76 private final ArrayMap<BluetoothDevice, DeviceInfo> mDeviceInfo = new ArrayMap<>(); 77 private final SparseArray<BluetoothProfile> mProfiles = new SparseArray<>(); 78 79 private boolean mEnabled; 80 private boolean mConnecting; 81 private BluetoothDevice mLastDevice; 82 83 public BluetoothControllerImpl(Context context) { 84 mContext = context; 85 final BluetoothManager bluetoothManager = 86 (BluetoothManager) context.getSystemService(Context.BLUETOOTH_SERVICE); 87 mAdapter = bluetoothManager.getAdapter(); 88 if (mAdapter == null) { 89 Log.w(TAG, "Default BT adapter not found"); 90 return; 91 } 92 93 mReceiver.register(); 94 setAdapterState(mAdapter.getState()); 95 updateBluetoothDevices(); 96 bindAllProfiles(); 97 } 98 99 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 100 pw.println("BluetoothController state:"); 101 pw.print(" mAdapter="); pw.println(mAdapter); 102 pw.print(" mEnabled="); pw.println(mEnabled); 103 pw.print(" mConnecting="); pw.println(mConnecting); 104 pw.print(" mLastDevice="); pw.println(mLastDevice); 105 pw.print(" mCallbacks.size="); pw.println(mCallbacks.size()); 106 pw.print(" mProfiles="); pw.println(profilesToString(mProfiles)); 107 pw.print(" mDeviceInfo.size="); pw.println(mDeviceInfo.size()); 108 for (int i = 0; i < mDeviceInfo.size(); i++) { 109 final BluetoothDevice device = mDeviceInfo.keyAt(i); 110 final DeviceInfo info = mDeviceInfo.valueAt(i); 111 pw.print(" "); pw.print(deviceToString(device)); 112 pw.print('('); pw.print(uuidsToString(device)); pw.print(')'); 113 pw.print(" "); pw.println(infoToString(info)); 114 } 115 } 116 117 private static String infoToString(DeviceInfo info) { 118 return info == null ? null : ("connectionState=" + 119 connectionStateToString(info.connectionState) + ",bonded=" + info.bonded 120 + ",profiles=" + profilesToString(info.connectedProfiles)); 121 } 122 123 private static String profilesToString(SparseArray<?> profiles) { 124 final int N = profiles.size(); 125 final StringBuffer buffer = new StringBuffer(); 126 buffer.append('['); 127 for (int i = 0; i < N; i++) { 128 if (i != 0) { 129 buffer.append(','); 130 } 131 buffer.append(BluetoothUtil.profileToString(profiles.keyAt(i))); 132 } 133 buffer.append(']'); 134 return buffer.toString(); 135 } 136 137 public void addStateChangedCallback(Callback cb) { 138 mCallbacks.add(cb); 139 fireStateChange(cb); 140 } 141 142 @Override 143 public void removeStateChangedCallback(Callback cb) { 144 mCallbacks.remove(cb); 145 } 146 147 @Override 148 public boolean isBluetoothEnabled() { 149 return mAdapter != null && mAdapter.isEnabled(); 150 } 151 152 @Override 153 public boolean isBluetoothConnected() { 154 return mAdapter != null 155 && mAdapter.getConnectionState() == BluetoothAdapter.STATE_CONNECTED; 156 } 157 158 @Override 159 public boolean isBluetoothConnecting() { 160 return mAdapter != null 161 && mAdapter.getConnectionState() == BluetoothAdapter.STATE_CONNECTING; 162 } 163 164 @Override 165 public void setBluetoothEnabled(boolean enabled) { 166 if (mAdapter != null) { 167 if (enabled) { 168 mAdapter.enable(); 169 } else { 170 mAdapter.disable(); 171 } 172 } 173 } 174 175 @Override 176 public boolean isBluetoothSupported() { 177 return mAdapter != null; 178 } 179 180 @Override 181 public ArraySet<PairedDevice> getPairedDevices() { 182 final ArraySet<PairedDevice> rt = new ArraySet<>(); 183 for (int i = 0; i < mDeviceInfo.size(); i++) { 184 final BluetoothDevice device = mDeviceInfo.keyAt(i); 185 final DeviceInfo info = mDeviceInfo.valueAt(i); 186 if (!info.bonded) continue; 187 final PairedDevice paired = new PairedDevice(); 188 paired.id = device.getAddress(); 189 paired.tag = device; 190 paired.name = device.getAliasName(); 191 paired.state = connectionStateToPairedDeviceState(info.connectionState); 192 rt.add(paired); 193 } 194 return rt; 195 } 196 197 private static int connectionStateToPairedDeviceState(int state) { 198 if (state == BluetoothAdapter.STATE_CONNECTED) return PairedDevice.STATE_CONNECTED; 199 if (state == BluetoothAdapter.STATE_CONNECTING) return PairedDevice.STATE_CONNECTING; 200 if (state == BluetoothAdapter.STATE_DISCONNECTING) return PairedDevice.STATE_DISCONNECTING; 201 return PairedDevice.STATE_DISCONNECTED; 202 } 203 204 @Override 205 public void connect(final PairedDevice pd) { 206 connect(pd, true); 207 } 208 209 @Override 210 public void disconnect(PairedDevice pd) { 211 connect(pd, false); 212 } 213 214 private void connect(PairedDevice pd, final boolean connect) { 215 if (mAdapter == null || pd == null || pd.tag == null) return; 216 final BluetoothDevice device = (BluetoothDevice) pd.tag; 217 final DeviceInfo info = mDeviceInfo.get(device); 218 final String action = connect ? "connect" : "disconnect"; 219 if (DEBUG) Log.d(TAG, action + " " + deviceToString(device)); 220 final ParcelUuid[] uuids = device.getUuids(); 221 if (uuids == null) { 222 Log.w(TAG, "No uuids returned, aborting " + action + " for " + deviceToString(device)); 223 return; 224 } 225 SparseArray<Boolean> profiles = new SparseArray<>(); 226 if (connect) { 227 // When connecting add every profile we can recognize by uuid. 228 for (ParcelUuid uuid : uuids) { 229 final int profile = uuidToProfile(uuid); 230 if (profile == 0) { 231 Log.w(TAG, "Device " + deviceToString(device) + " has an unsupported uuid: " 232 + uuidToString(uuid)); 233 continue; 234 } 235 final boolean connected = info.connectedProfiles.get(profile, false); 236 if (!connected) { 237 profiles.put(profile, true); 238 } 239 } 240 } else { 241 // When disconnecting, just add every profile we know they are connected to. 242 profiles = info.connectedProfiles; 243 } 244 for (int i = 0; i < profiles.size(); i++) { 245 final int profile = profiles.keyAt(i); 246 if (mProfiles.indexOfKey(profile) >= 0) { 247 final Profile p = BluetoothUtil.getProfile(mProfiles.get(profile)); 248 final boolean ok = connect ? p.connect(device) : p.disconnect(device); 249 if (DEBUG) Log.d(TAG, action + " " + profileToString(profile) + " " 250 + (ok ? "succeeded" : "failed")); 251 } else { 252 Log.w(TAG, "Unable get get Profile for " + profileToString(profile)); 253 } 254 } 255 } 256 257 @Override 258 public String getLastDeviceName() { 259 return mLastDevice != null ? mLastDevice.getAliasName() : null; 260 } 261 262 private void updateBluetoothDevices() { 263 if (mAdapter == null) return; 264 final Set<BluetoothDevice> bondedDevices = mAdapter.getBondedDevices(); 265 for (DeviceInfo info : mDeviceInfo.values()) { 266 info.bonded = false; 267 info.connectionState = ERROR; 268 info.connectedProfiles.clear(); 269 } 270 int bondedCount = 0; 271 BluetoothDevice lastBonded = null; 272 if (bondedDevices != null) { 273 for (BluetoothDevice bondedDevice : bondedDevices) { 274 final boolean bonded = bondedDevice.getBondState() != BluetoothDevice.BOND_NONE; 275 updateInfo(bondedDevice).bonded = bonded; 276 if (bonded) { 277 bondedCount++; 278 lastBonded = bondedDevice; 279 } 280 } 281 } 282 final int N = mProfiles.size(); 283 final int[] connectionType = new int[1]; 284 for (int i = 0; i < CONNECTION_STATES.length; i++) { 285 connectionType[0] = CONNECTION_STATES[i]; 286 for (int j = 0; j < N; j++) { 287 int profile = mProfiles.keyAt(j); 288 List<BluetoothDevice> devices = mProfiles.get(profile) 289 .getDevicesMatchingConnectionStates(connectionType); 290 for (int k = 0; k < devices.size(); k++) { 291 DeviceInfo info = mDeviceInfo.get(devices.get(k)); 292 if (info != null) { 293 info.connectionState = CONNECTION_STATES[i]; 294 if (CONNECTION_STATES[i] == BluetoothProfile.STATE_CONNECTED) { 295 info.connectedProfiles.put(profile, true); 296 } 297 } 298 } 299 } 300 } 301 if (mLastDevice == null && bondedCount == 1) { 302 mLastDevice = lastBonded; 303 } 304 // If we are no longer connected to the current device, see if we are connected to 305 // something else, so we don't display a name we aren't connected to. 306 if (mLastDevice != null && 307 mDeviceInfo.get(mLastDevice).connectionState != BluetoothProfile.STATE_CONNECTED) { 308 // Make sure we don't keep this device while it isn't connected. 309 mLastDevice = null; 310 // Look for anything else connected. 311 final int size = mDeviceInfo.size(); 312 for (int i = 0; i < size; i++) { 313 BluetoothDevice device = mDeviceInfo.keyAt(i); 314 DeviceInfo info = mDeviceInfo.valueAt(i); 315 if (info.connectionState == BluetoothProfile.STATE_CONNECTED) { 316 mLastDevice = device; 317 break; 318 } 319 } 320 } 321 firePairedDevicesChanged(); 322 } 323 324 private void bindAllProfiles() { 325 // Note: This needs to contain all of the types that can be returned by BluetoothUtil 326 // otherwise we can't find the profiles we need when we connect/disconnect. 327 mAdapter.getProfileProxy(mContext, mProfileListener, BluetoothProfile.A2DP); 328 mAdapter.getProfileProxy(mContext, mProfileListener, BluetoothProfile.A2DP_SINK); 329 mAdapter.getProfileProxy(mContext, mProfileListener, BluetoothProfile.AVRCP_CONTROLLER); 330 mAdapter.getProfileProxy(mContext, mProfileListener, BluetoothProfile.HEADSET); 331 mAdapter.getProfileProxy(mContext, mProfileListener, BluetoothProfile.HEADSET_CLIENT); 332 mAdapter.getProfileProxy(mContext, mProfileListener, BluetoothProfile.INPUT_DEVICE); 333 mAdapter.getProfileProxy(mContext, mProfileListener, BluetoothProfile.MAP); 334 mAdapter.getProfileProxy(mContext, mProfileListener, BluetoothProfile.PAN); 335 // Note Health is not in this list because health devices aren't 'connected'. 336 // If profiles are expanded to use more than just connection state and connect/disconnect 337 // then it should be added. 338 } 339 340 private void firePairedDevicesChanged() { 341 for (Callback cb : mCallbacks) { 342 cb.onBluetoothPairedDevicesChanged(); 343 } 344 } 345 346 private void setAdapterState(int adapterState) { 347 final boolean enabled = adapterState == BluetoothAdapter.STATE_ON; 348 if (mEnabled == enabled) return; 349 mEnabled = enabled; 350 fireStateChange(); 351 } 352 353 private void setConnecting(boolean connecting) { 354 if (mConnecting == connecting) return; 355 mConnecting = connecting; 356 fireStateChange(); 357 } 358 359 private void fireStateChange() { 360 for (Callback cb : mCallbacks) { 361 fireStateChange(cb); 362 } 363 } 364 365 private void fireStateChange(Callback cb) { 366 cb.onBluetoothStateChange(mEnabled, mConnecting); 367 } 368 369 private final ServiceListener mProfileListener = new ServiceListener() { 370 @Override 371 public void onServiceDisconnected(int profile) { 372 mProfiles.remove(profile); 373 updateBluetoothDevices(); 374 } 375 376 @Override 377 public void onServiceConnected(int profile, BluetoothProfile proxy) { 378 mProfiles.put(profile, proxy); 379 updateBluetoothDevices(); 380 } 381 }; 382 383 private final class Receiver extends BroadcastReceiver { 384 public void register() { 385 final IntentFilter filter = new IntentFilter(); 386 filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED); 387 filter.addAction(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED); 388 filter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED); 389 filter.addAction(BluetoothDevice.ACTION_ALIAS_CHANGED); 390 filter.addAction(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED); 391 filter.addAction(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED); 392 filter.addAction(BluetoothA2dpSink.ACTION_CONNECTION_STATE_CHANGED); 393 filter.addAction(BluetoothHeadsetClient.ACTION_CONNECTION_STATE_CHANGED); 394 filter.addAction(BluetoothInputDevice.ACTION_CONNECTION_STATE_CHANGED); 395 filter.addAction(BluetoothMap.ACTION_CONNECTION_STATE_CHANGED); 396 filter.addAction(BluetoothPan.ACTION_CONNECTION_STATE_CHANGED); 397 mContext.registerReceiver(this, filter); 398 } 399 400 @Override 401 public void onReceive(Context context, Intent intent) { 402 final String action = intent.getAction(); 403 final BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); 404 if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) { 405 setAdapterState(intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, ERROR)); 406 if (DEBUG) Log.d(TAG, "ACTION_STATE_CHANGED " + mEnabled); 407 } else if (action.equals(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED)) { 408 updateInfo(device); 409 final int state = intent.getIntExtra(BluetoothAdapter.EXTRA_CONNECTION_STATE, 410 ERROR); 411 mLastDevice = device; 412 if (DEBUG) Log.d(TAG, "ACTION_CONNECTION_STATE_CHANGED " 413 + connectionStateToString(state) + " " + deviceToString(device)); 414 setConnecting(state == BluetoothAdapter.STATE_CONNECTING); 415 } else if (action.equals(BluetoothDevice.ACTION_ALIAS_CHANGED)) { 416 updateInfo(device); 417 mLastDevice = device; 418 } else if (action.equals(BluetoothDevice.ACTION_BOND_STATE_CHANGED)) { 419 if (DEBUG) Log.d(TAG, "ACTION_BOND_STATE_CHANGED " + device); 420 // we'll update all bonded devices below 421 } 422 // Always update bluetooth devices state. 423 updateBluetoothDevices(); 424 } 425 } 426 427 private DeviceInfo updateInfo(BluetoothDevice device) { 428 DeviceInfo info = mDeviceInfo.get(device); 429 info = info != null ? info : new DeviceInfo(); 430 mDeviceInfo.put(device, info); 431 return info; 432 } 433 434 private static class DeviceInfo { 435 int connectionState = BluetoothAdapter.STATE_DISCONNECTED; 436 boolean bonded; // per getBondedDevices 437 SparseArray<Boolean> connectedProfiles = new SparseArray<>(); 438 } 439} 440