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.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