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 17package com.android.settings.bluetooth; 18 19import com.android.settings.R; 20 21import android.bluetooth.BluetoothAdapter; 22import android.bluetooth.BluetoothClass; 23import android.bluetooth.BluetoothDevice; 24import android.content.BroadcastReceiver; 25import android.content.Context; 26import android.content.Intent; 27import android.content.IntentFilter; 28import android.util.Log; 29 30import java.util.ArrayList; 31import java.util.Collection; 32import java.util.HashMap; 33import java.util.Map; 34import 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 */ 41final 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 { 55 void onReceive(Context context, Intent intent, BluetoothDevice device); 56 } 57 58 void addHandler(String action, Handler handler) { 59 mHandlerMap.put(action, handler); 60 mAdapterIntentFilter.addAction(action); 61 } 62 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 69 void setProfileManager(LocalBluetoothProfileManager manager) { 70 mProfileManager = manager; 71 } 72 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 105 void registerProfileIntentReceiver() { 106 mContext.registerReceiver(mBroadcastReceiver, mProfileIntentFilter); 107 } 108 109 /** Register to start receiving callbacks for Bluetooth events. */ 110 void registerCallback(BluetoothCallback callback) { 111 synchronized (mCallbacks) { 112 mCallbacks.add(callback); 113 } 114 } 115 116 /** Unregister to stop receiving callbacks for Bluetooth events. */ 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. 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 Log.v(TAG, "Received " + intent.getAction()); 143 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 } 169 } 170 171 private class ScanningStateChangedHandler implements Handler { 172 private final boolean mStarted; 173 174 ScanningStateChangedHandler(boolean started) { 175 mStarted = started; 176 } 177 public void onReceive(Context context, Intent intent, 178 BluetoothDevice device) { 179 synchronized (mCallbacks) { 180 for (BluetoothCallback callback : mCallbacks) { 181 callback.onScanningStateChanged(mStarted); 182 } 183 } 184 mDeviceManager.onScanningStateChanged(mStarted); 185 LocalBluetoothPreferences.persistDiscoveringTimestamp(context); 186 } 187 } 188 189 private class DeviceFoundHandler implements Handler { 190 public void onReceive(Context context, Intent intent, 191 BluetoothDevice device) { 192 short rssi = intent.getShortExtra(BluetoothDevice.EXTRA_RSSI, Short.MIN_VALUE); 193 BluetoothClass btClass = intent.getParcelableExtra(BluetoothDevice.EXTRA_CLASS); 194 String name = intent.getStringExtra(BluetoothDevice.EXTRA_NAME); 195 // TODO Pick up UUID. They should be available for 2.1 devices. 196 // Skip for now, there's a bluez problem and we are not getting uuids even for 2.1. 197 CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(device); 198 if (cachedDevice == null) { 199 cachedDevice = mDeviceManager.addDevice(mLocalAdapter, mProfileManager, device); 200 Log.d(TAG, "DeviceFoundHandler created new CachedBluetoothDevice: " 201 + cachedDevice); 202 // callback to UI to create Preference for new device 203 dispatchDeviceAdded(cachedDevice); 204 } 205 cachedDevice.setRssi(rssi); 206 cachedDevice.setBtClass(btClass); 207 cachedDevice.setName(name); 208 cachedDevice.setVisible(true); 209 } 210 } 211 212 private void dispatchDeviceAdded(CachedBluetoothDevice cachedDevice) { 213 synchronized (mCallbacks) { 214 for (BluetoothCallback callback : mCallbacks) { 215 callback.onDeviceAdded(cachedDevice); 216 } 217 } 218 } 219 220 private class DeviceDisappearedHandler implements Handler { 221 public void onReceive(Context context, Intent intent, 222 BluetoothDevice device) { 223 CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(device); 224 if (cachedDevice == null) { 225 Log.w(TAG, "received ACTION_DISAPPEARED for an unknown device: " + device); 226 return; 227 } 228 if (CachedBluetoothDeviceManager.onDeviceDisappeared(cachedDevice)) { 229 synchronized (mCallbacks) { 230 for (BluetoothCallback callback : mCallbacks) { 231 callback.onDeviceDeleted(cachedDevice); 232 } 233 } 234 } 235 } 236 } 237 238 private class NameChangedHandler implements Handler { 239 public void onReceive(Context context, Intent intent, 240 BluetoothDevice device) { 241 mDeviceManager.onDeviceNameUpdated(device); 242 } 243 } 244 245 private class BondStateChangedHandler implements Handler { 246 public void onReceive(Context context, Intent intent, 247 BluetoothDevice device) { 248 if (device == null) { 249 Log.e(TAG, "ACTION_BOND_STATE_CHANGED with no EXTRA_DEVICE"); 250 return; 251 } 252 int bondState = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, 253 BluetoothDevice.ERROR); 254 CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(device); 255 if (cachedDevice == null) { 256 Log.w(TAG, "CachedBluetoothDevice for device " + device + 257 " not found, calling readPairedDevices()."); 258 if (!readPairedDevices()) { 259 Log.e(TAG, "Got bonding state changed for " + device + 260 ", but we have no record of that device."); 261 return; 262 } 263 cachedDevice = mDeviceManager.findDevice(device); 264 if (cachedDevice == null) { 265 Log.e(TAG, "Got bonding state changed for " + device + 266 ", but device not added in cache."); 267 return; 268 } 269 } 270 271 synchronized (mCallbacks) { 272 for (BluetoothCallback callback : mCallbacks) { 273 callback.onDeviceBondStateChanged(cachedDevice, bondState); 274 } 275 } 276 cachedDevice.onBondingStateChanged(bondState); 277 278 if (bondState == BluetoothDevice.BOND_NONE) { 279 if (device.isBluetoothDock()) { 280 // After a dock is unpaired, we will forget the settings 281 LocalBluetoothPreferences 282 .removeDockAutoConnectSetting(context, device.getAddress()); 283 284 // if the device is undocked, remove it from the list as well 285 if (!device.getAddress().equals(getDockedDeviceAddress(context))) { 286 cachedDevice.setVisible(false); 287 } 288 } 289 int reason = intent.getIntExtra(BluetoothDevice.EXTRA_REASON, 290 BluetoothDevice.ERROR); 291 292 showUnbondMessage(context, cachedDevice.getName(), reason); 293 } 294 } 295 296 /** 297 * Called when we have reached the unbonded state. 298 * 299 * @param reason one of the error reasons from 300 * BluetoothDevice.UNBOND_REASON_* 301 */ 302 private void showUnbondMessage(Context context, String name, int reason) { 303 int errorMsg; 304 305 switch(reason) { 306 case BluetoothDevice.UNBOND_REASON_AUTH_FAILED: 307 errorMsg = R.string.bluetooth_pairing_pin_error_message; 308 break; 309 case BluetoothDevice.UNBOND_REASON_AUTH_REJECTED: 310 errorMsg = R.string.bluetooth_pairing_rejected_error_message; 311 break; 312 case BluetoothDevice.UNBOND_REASON_REMOTE_DEVICE_DOWN: 313 errorMsg = R.string.bluetooth_pairing_device_down_error_message; 314 break; 315 case BluetoothDevice.UNBOND_REASON_DISCOVERY_IN_PROGRESS: 316 case BluetoothDevice.UNBOND_REASON_AUTH_TIMEOUT: 317 case BluetoothDevice.UNBOND_REASON_REPEATED_ATTEMPTS: 318 case BluetoothDevice.UNBOND_REASON_REMOTE_AUTH_CANCELED: 319 errorMsg = R.string.bluetooth_pairing_error_message; 320 break; 321 default: 322 Log.w(TAG, "showUnbondMessage: Not displaying any message for reason: " + reason); 323 return; 324 } 325 Utils.showError(context, name, errorMsg); 326 } 327 } 328 329 private class ClassChangedHandler implements Handler { 330 public void onReceive(Context context, Intent intent, 331 BluetoothDevice device) { 332 mDeviceManager.onBtClassChanged(device); 333 } 334 } 335 336 private class UuidChangedHandler implements Handler { 337 public void onReceive(Context context, Intent intent, 338 BluetoothDevice device) { 339 mDeviceManager.onUuidChanged(device); 340 } 341 } 342 343 private class PairingCancelHandler implements Handler { 344 public void onReceive(Context context, Intent intent, BluetoothDevice device) { 345 if (device == null) { 346 Log.e(TAG, "ACTION_PAIRING_CANCEL with no EXTRA_DEVICE"); 347 return; 348 } 349 int errorMsg = R.string.bluetooth_pairing_error_message; 350 CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(device); 351 Utils.showError(context, cachedDevice.getName(), errorMsg); 352 } 353 } 354 355 private class DockEventHandler implements Handler { 356 public void onReceive(Context context, Intent intent, BluetoothDevice device) { 357 // Remove if unpair device upon undocking 358 int anythingButUnDocked = Intent.EXTRA_DOCK_STATE_UNDOCKED + 1; 359 int state = intent.getIntExtra(Intent.EXTRA_DOCK_STATE, anythingButUnDocked); 360 if (state == Intent.EXTRA_DOCK_STATE_UNDOCKED) { 361 if (device != null && device.getBondState() == BluetoothDevice.BOND_NONE) { 362 CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(device); 363 if (cachedDevice != null) { 364 cachedDevice.setVisible(false); 365 } 366 } 367 } 368 } 369 } 370 371 boolean readPairedDevices() { 372 Set<BluetoothDevice> bondedDevices = mLocalAdapter.getBondedDevices(); 373 if (bondedDevices == null) { 374 return false; 375 } 376 377 boolean deviceAdded = false; 378 for (BluetoothDevice device : bondedDevices) { 379 CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(device); 380 if (cachedDevice == null) { 381 cachedDevice = mDeviceManager.addDevice(mLocalAdapter, mProfileManager, device); 382 dispatchDeviceAdded(cachedDevice); 383 deviceAdded = true; 384 } 385 } 386 387 return deviceAdded; 388 } 389} 390