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