1 /* 2 * Copyright (C) 2011 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 com.android.settings.R; 20 21 import android.bluetooth.BluetoothAdapter; 22 import android.bluetooth.BluetoothClass; 23 import android.bluetooth.BluetoothDevice; 24 import android.bluetooth.BluetoothProfile; 25 import android.content.BroadcastReceiver; 26 import android.content.Context; 27 import android.content.Intent; 28 import android.content.IntentFilter; 29 import android.util.Log; 30 31 import java.util.ArrayList; 32 import java.util.Collection; 33 import java.util.HashMap; 34 import java.util.Map; 35 import java.util.Set; 36 37 /** 38 * BluetoothEventManager receives broadcasts and callbacks from the Bluetooth 39 * API and dispatches the event on the UI thread to the right class in the 40 * Settings. 41 */ 42 final class BluetoothEventManager { 43 private static final String TAG = "BluetoothEventManager"; 44 45 private final LocalBluetoothAdapter mLocalAdapter; 46 private final CachedBluetoothDeviceManager mDeviceManager; 47 private LocalBluetoothProfileManager mProfileManager; 48 private final IntentFilter mAdapterIntentFilter, mProfileIntentFilter; 49 private final Map<String, Handler> mHandlerMap; 50 private Context mContext; 51 52 private final Collection<BluetoothCallback> mCallbacks = 53 new ArrayList<BluetoothCallback>(); 54 55 interface Handler { 56 void onReceive(Context context, Intent intent, BluetoothDevice device); 57 } 58 59 void addHandler(String action, Handler handler) { 60 mHandlerMap.put(action, handler); 61 mAdapterIntentFilter.addAction(action); 62 } 63 64 void addProfileHandler(String action, Handler handler) { 65 mHandlerMap.put(action, handler); 66 mProfileIntentFilter.addAction(action); 67 } 68 69 // Set profile manager after construction due to circular dependency 70 void setProfileManager(LocalBluetoothProfileManager manager) { 71 mProfileManager = manager; 72 } 73 74 BluetoothEventManager(LocalBluetoothAdapter adapter, 75 CachedBluetoothDeviceManager deviceManager, Context context) { 76 mLocalAdapter = adapter; 77 mDeviceManager = deviceManager; 78 mAdapterIntentFilter = new IntentFilter(); 79 mProfileIntentFilter = new IntentFilter(); 80 mHandlerMap = new HashMap<String, Handler>(); 81 mContext = context; 82 83 // Bluetooth on/off broadcasts 84 addHandler(BluetoothAdapter.ACTION_STATE_CHANGED, new AdapterStateChangedHandler()); 85 86 // Discovery broadcasts 87 addHandler(BluetoothAdapter.ACTION_DISCOVERY_STARTED, new ScanningStateChangedHandler(true)); 88 addHandler(BluetoothAdapter.ACTION_DISCOVERY_FINISHED, new ScanningStateChangedHandler(false)); 89 addHandler(BluetoothDevice.ACTION_FOUND, new DeviceFoundHandler()); 90 addHandler(BluetoothDevice.ACTION_DISAPPEARED, new DeviceDisappearedHandler()); 91 addHandler(BluetoothDevice.ACTION_NAME_CHANGED, new NameChangedHandler()); 92 93 // Pairing broadcasts 94 addHandler(BluetoothDevice.ACTION_BOND_STATE_CHANGED, new BondStateChangedHandler()); 95 addHandler(BluetoothDevice.ACTION_PAIRING_CANCEL, new PairingCancelHandler()); 96 97 // Fine-grained state broadcasts 98 addHandler(BluetoothDevice.ACTION_CLASS_CHANGED, new ClassChangedHandler()); 99 addHandler(BluetoothDevice.ACTION_UUID, new UuidChangedHandler()); 100 101 // Dock event broadcasts 102 addHandler(Intent.ACTION_DOCK_EVENT, new DockEventHandler()); 103 104 mContext.registerReceiver(mBroadcastReceiver, mAdapterIntentFilter); 105 } 106 107 void registerProfileIntentReceiver() { 108 mContext.registerReceiver(mBroadcastReceiver, mProfileIntentFilter); 109 } 110 111 /** Register to start receiving callbacks for Bluetooth events. */ 112 void registerCallback(BluetoothCallback callback) { 113 synchronized (mCallbacks) { 114 mCallbacks.add(callback); 115 } 116 } 117 118 /** Unregister to stop receiving callbacks for Bluetooth events. */ 119 void unregisterCallback(BluetoothCallback callback) { 120 synchronized (mCallbacks) { 121 mCallbacks.remove(callback); 122 } 123 } 124 125 // This can't be called from a broadcast receiver where the filter is set in the Manifest. 126 private static String getDockedDeviceAddress(Context context) { 127 // This works only because these broadcast intents are "sticky" 128 Intent i = context.registerReceiver(null, new IntentFilter(Intent.ACTION_DOCK_EVENT)); 129 if (i != null) { 130 int state = i.getIntExtra(Intent.EXTRA_DOCK_STATE, Intent.EXTRA_DOCK_STATE_UNDOCKED); 131 if (state != Intent.EXTRA_DOCK_STATE_UNDOCKED) { 132 BluetoothDevice device = i.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); 133 if (device != null) { 134 return device.getAddress(); 135 } 136 } 137 } 138 return null; 139 } 140 141 private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { 142 @Override 143 public void onReceive(Context context, Intent intent) { 144 String action = intent.getAction(); 145 BluetoothDevice device = intent 146 .getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); 147 148 Handler handler = mHandlerMap.get(action); 149 if (handler != null) { 150 handler.onReceive(context, intent, device); 151 } 152 } 153 }; 154 155 private class AdapterStateChangedHandler implements Handler { 156 public void onReceive(Context context, Intent intent, 157 BluetoothDevice device) { 158 int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, 159 BluetoothAdapter.ERROR); 160 // update local profiles and get paired devices 161 mLocalAdapter.setBluetoothStateInt(state); 162 // send callback to update UI and possibly start scanning 163 synchronized (mCallbacks) { 164 for (BluetoothCallback callback : mCallbacks) { 165 callback.onBluetoothStateChanged(state); 166 } 167 } 168 // Inform CachedDeviceManager that the adapter state has changed 169 mDeviceManager.onBluetoothStateChanged(state); 170 } 171 } 172 173 private class ScanningStateChangedHandler implements Handler { 174 private final boolean mStarted; 175 176 ScanningStateChangedHandler(boolean started) { 177 mStarted = started; 178 } 179 public void onReceive(Context context, Intent intent, 180 BluetoothDevice device) { 181 synchronized (mCallbacks) { 182 for (BluetoothCallback callback : mCallbacks) { 183 callback.onScanningStateChanged(mStarted); 184 } 185 } 186 mDeviceManager.onScanningStateChanged(mStarted); 187 LocalBluetoothPreferences.persistDiscoveringTimestamp(context); 188 } 189 } 190 191 private class DeviceFoundHandler implements Handler { 192 public void onReceive(Context context, Intent intent, 193 BluetoothDevice device) { 194 short rssi = intent.getShortExtra(BluetoothDevice.EXTRA_RSSI, Short.MIN_VALUE); 195 BluetoothClass btClass = intent.getParcelableExtra(BluetoothDevice.EXTRA_CLASS); 196 String name = intent.getStringExtra(BluetoothDevice.EXTRA_NAME); 197 // TODO Pick up UUID. They should be available for 2.1 devices. 198 // Skip for now, there's a bluez problem and we are not getting uuids even for 2.1. 199 CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(device); 200 if (cachedDevice == null) { 201 cachedDevice = mDeviceManager.addDevice(mLocalAdapter, mProfileManager, device); 202 Log.d(TAG, "DeviceFoundHandler created new CachedBluetoothDevice: " 203 + cachedDevice); 204 // callback to UI to create Preference for new device 205 dispatchDeviceAdded(cachedDevice); 206 } 207 cachedDevice.setRssi(rssi); 208 cachedDevice.setBtClass(btClass); 209 cachedDevice.setName(name); 210 cachedDevice.setVisible(true); 211 } 212 } 213 214 private void dispatchDeviceAdded(CachedBluetoothDevice cachedDevice) { 215 synchronized (mCallbacks) { 216 for (BluetoothCallback callback : mCallbacks) { 217 callback.onDeviceAdded(cachedDevice); 218 } 219 } 220 } 221 222 private class DeviceDisappearedHandler implements Handler { 223 public void onReceive(Context context, Intent intent, 224 BluetoothDevice device) { 225 CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(device); 226 if (cachedDevice == null) { 227 Log.w(TAG, "received ACTION_DISAPPEARED for an unknown device: " + device); 228 return; 229 } 230 if (CachedBluetoothDeviceManager.onDeviceDisappeared(cachedDevice)) { 231 synchronized (mCallbacks) { 232 for (BluetoothCallback callback : mCallbacks) { 233 callback.onDeviceDeleted(cachedDevice); 234 } 235 } 236 } 237 } 238 } 239 240 private class NameChangedHandler implements Handler { 241 public void onReceive(Context context, Intent intent, 242 BluetoothDevice device) { 243 mDeviceManager.onDeviceNameUpdated(device); 244 } 245 } 246 247 private class BondStateChangedHandler implements Handler { 248 public void onReceive(Context context, Intent intent, 249 BluetoothDevice device) { 250 if (device == null) { 251 Log.e(TAG, "ACTION_BOND_STATE_CHANGED with no EXTRA_DEVICE"); 252 return; 253 } 254 int bondState = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, 255 BluetoothDevice.ERROR); 256 CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(device); 257 if (cachedDevice == null) { 258 Log.w(TAG, "CachedBluetoothDevice for device " + device + 259 " not found, calling readPairedDevices()."); 260 if (!readPairedDevices()) { 261 Log.e(TAG, "Got bonding state changed for " + device + 262 ", but we have no record of that device."); 263 return; 264 } 265 cachedDevice = mDeviceManager.findDevice(device); 266 if (cachedDevice == null) { 267 Log.e(TAG, "Got bonding state changed for " + device + 268 ", but device not added in cache."); 269 return; 270 } 271 } 272 273 synchronized (mCallbacks) { 274 for (BluetoothCallback callback : mCallbacks) { 275 callback.onDeviceBondStateChanged(cachedDevice, bondState); 276 } 277 } 278 cachedDevice.onBondingStateChanged(bondState); 279 280 if (bondState == BluetoothDevice.BOND_NONE) { 281 if (device.isBluetoothDock()) { 282 // After a dock is unpaired, we will forget the settings 283 LocalBluetoothPreferences 284 .removeDockAutoConnectSetting(context, device.getAddress()); 285 286 // if the device is undocked, remove it from the list as well 287 if (!device.getAddress().equals(getDockedDeviceAddress(context))) { 288 cachedDevice.setVisible(false); 289 } 290 } 291 int reason = intent.getIntExtra(BluetoothDevice.EXTRA_REASON, 292 BluetoothDevice.ERROR); 293 294 showUnbondMessage(context, cachedDevice.getName(), reason); 295 } 296 } 297 298 /** 299 * Called when we have reached the unbonded state. 300 * 301 * @param reason one of the error reasons from 302 * BluetoothDevice.UNBOND_REASON_* 303 */ 304 private void showUnbondMessage(Context context, String name, int reason) { 305 int errorMsg; 306 307 switch(reason) { 308 case BluetoothDevice.UNBOND_REASON_AUTH_FAILED: 309 errorMsg = R.string.bluetooth_pairing_pin_error_message; 310 break; 311 case BluetoothDevice.UNBOND_REASON_AUTH_REJECTED: 312 errorMsg = R.string.bluetooth_pairing_rejected_error_message; 313 break; 314 case BluetoothDevice.UNBOND_REASON_REMOTE_DEVICE_DOWN: 315 errorMsg = R.string.bluetooth_pairing_device_down_error_message; 316 break; 317 case BluetoothDevice.UNBOND_REASON_DISCOVERY_IN_PROGRESS: 318 case BluetoothDevice.UNBOND_REASON_AUTH_TIMEOUT: 319 case BluetoothDevice.UNBOND_REASON_REPEATED_ATTEMPTS: 320 case BluetoothDevice.UNBOND_REASON_REMOTE_AUTH_CANCELED: 321 errorMsg = R.string.bluetooth_pairing_error_message; 322 break; 323 default: 324 Log.w(TAG, "showUnbondMessage: Not displaying any message for reason: " + reason); 325 return; 326 } 327 Utils.showError(context, name, errorMsg); 328 } 329 } 330 331 private class ClassChangedHandler implements Handler { 332 public void onReceive(Context context, Intent intent, 333 BluetoothDevice device) { 334 mDeviceManager.onBtClassChanged(device); 335 } 336 } 337 338 private class UuidChangedHandler implements Handler { 339 public void onReceive(Context context, Intent intent, 340 BluetoothDevice device) { 341 mDeviceManager.onUuidChanged(device); 342 } 343 } 344 345 private class PairingCancelHandler implements Handler { 346 public void onReceive(Context context, Intent intent, BluetoothDevice device) { 347 if (device == null) { 348 Log.e(TAG, "ACTION_PAIRING_CANCEL with no EXTRA_DEVICE"); 349 return; 350 } 351 int errorMsg = R.string.bluetooth_pairing_error_message; 352 CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(device); 353 Utils.showError(context, cachedDevice.getName(), errorMsg); 354 } 355 } 356 357 private class DockEventHandler implements Handler { 358 public void onReceive(Context context, Intent intent, BluetoothDevice device) { 359 // Remove if unpair device upon undocking 360 int anythingButUnDocked = Intent.EXTRA_DOCK_STATE_UNDOCKED + 1; 361 int state = intent.getIntExtra(Intent.EXTRA_DOCK_STATE, anythingButUnDocked); 362 if (state == Intent.EXTRA_DOCK_STATE_UNDOCKED) { 363 if (device != null && device.getBondState() == BluetoothDevice.BOND_NONE) { 364 CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(device); 365 if (cachedDevice != null) { 366 cachedDevice.setVisible(false); 367 } 368 } 369 } 370 } 371 } 372 boolean readPairedDevices() { 373 Set<BluetoothDevice> bondedDevices = mLocalAdapter.getBondedDevices(); 374 if (bondedDevices == null) { 375 return false; 376 } 377 378 boolean deviceAdded = false; 379 for (BluetoothDevice device : bondedDevices) { 380 CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(device); 381 if (cachedDevice == null) { 382 cachedDevice = mDeviceManager.addDevice(mLocalAdapter, mProfileManager, device); 383 dispatchDeviceAdded(cachedDevice); 384 deviceAdded = true; 385 } 386 } 387 388 return deviceAdded; 389 } 390 } 391