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             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 {
    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 
    172         ScanningStateChangedHandler(boolean started) {
    173             mStarted = started;
    174         }
    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 {
    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 
    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 {
    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 {
    237         public void onReceive(Context context, Intent intent,
    238                 BluetoothDevice device) {
    239             mDeviceManager.onDeviceNameUpdated(device);
    240         }
    241     }
    242 
    243     private class BondStateChangedHandler implements Handler {
    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          */
    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 {
    328         public void onReceive(Context context, Intent intent,
    329                 BluetoothDevice device) {
    330             mDeviceManager.onBtClassChanged(device);
    331         }
    332     }
    333 
    334     private class UuidChangedHandler implements Handler {
    335         public void onReceive(Context context, Intent intent,
    336                 BluetoothDevice device) {
    337             mDeviceManager.onUuidChanged(device);
    338         }
    339     }
    340 
    341     private class PairingCancelHandler implements Handler {
    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 {
    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 
    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