Home | History | Annotate | Download | only in bluetooth
      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 {
     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