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.settingslib.bluetooth;
     18 
     19 import android.bluetooth.BluetoothA2dp;
     20 import android.bluetooth.BluetoothAdapter;
     21 import android.bluetooth.BluetoothClass;
     22 import android.bluetooth.BluetoothDevice;
     23 import android.bluetooth.BluetoothHeadset;
     24 import android.bluetooth.BluetoothHearingAid;
     25 import android.bluetooth.BluetoothProfile;
     26 import android.content.BroadcastReceiver;
     27 import android.content.Context;
     28 import android.content.Intent;
     29 import android.content.IntentFilter;
     30 import android.telephony.TelephonyManager;
     31 import android.util.Log;
     32 
     33 import com.android.settingslib.R;
     34 
     35 import java.util.ArrayList;
     36 import java.util.Collection;
     37 import java.util.HashMap;
     38 import java.util.Map;
     39 import java.util.Objects;
     40 import java.util.Set;
     41 
     42 /**
     43  * BluetoothEventManager receives broadcasts and callbacks from the Bluetooth
     44  * API and dispatches the event on the UI thread to the right class in the
     45  * Settings.
     46  */
     47 public class BluetoothEventManager {
     48     private static final String TAG = "BluetoothEventManager";
     49 
     50     private final LocalBluetoothAdapter mLocalAdapter;
     51     private final CachedBluetoothDeviceManager mDeviceManager;
     52     private LocalBluetoothProfileManager mProfileManager;
     53     private final IntentFilter mAdapterIntentFilter, mProfileIntentFilter;
     54     private final Map<String, Handler> mHandlerMap;
     55     private Context mContext;
     56 
     57     private final Collection<BluetoothCallback> mCallbacks =
     58             new ArrayList<BluetoothCallback>();
     59 
     60     private android.os.Handler mReceiverHandler;
     61 
     62     interface Handler {
     63         void onReceive(Context context, Intent intent, BluetoothDevice device);
     64     }
     65 
     66     private void addHandler(String action, Handler handler) {
     67         mHandlerMap.put(action, handler);
     68         mAdapterIntentFilter.addAction(action);
     69     }
     70 
     71     void addProfileHandler(String action, Handler handler) {
     72         mHandlerMap.put(action, handler);
     73         mProfileIntentFilter.addAction(action);
     74     }
     75 
     76     // Set profile manager after construction due to circular dependency
     77     void setProfileManager(LocalBluetoothProfileManager manager) {
     78         mProfileManager = manager;
     79     }
     80 
     81     BluetoothEventManager(LocalBluetoothAdapter adapter,
     82             CachedBluetoothDeviceManager deviceManager, Context context) {
     83         mLocalAdapter = adapter;
     84         mDeviceManager = deviceManager;
     85         mAdapterIntentFilter = new IntentFilter();
     86         mProfileIntentFilter = new IntentFilter();
     87         mHandlerMap = new HashMap<String, Handler>();
     88         mContext = context;
     89 
     90         // Bluetooth on/off broadcasts
     91         addHandler(BluetoothAdapter.ACTION_STATE_CHANGED, new AdapterStateChangedHandler());
     92         // Generic connected/not broadcast
     93         addHandler(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED,
     94                 new ConnectionStateChangedHandler());
     95 
     96         // Discovery broadcasts
     97         addHandler(BluetoothAdapter.ACTION_DISCOVERY_STARTED, new ScanningStateChangedHandler(true));
     98         addHandler(BluetoothAdapter.ACTION_DISCOVERY_FINISHED, new ScanningStateChangedHandler(false));
     99         addHandler(BluetoothDevice.ACTION_FOUND, new DeviceFoundHandler());
    100         addHandler(BluetoothDevice.ACTION_DISAPPEARED, new DeviceDisappearedHandler());
    101         addHandler(BluetoothDevice.ACTION_NAME_CHANGED, new NameChangedHandler());
    102         addHandler(BluetoothDevice.ACTION_ALIAS_CHANGED, new NameChangedHandler());
    103 
    104         // Pairing broadcasts
    105         addHandler(BluetoothDevice.ACTION_BOND_STATE_CHANGED, new BondStateChangedHandler());
    106 
    107         // Fine-grained state broadcasts
    108         addHandler(BluetoothDevice.ACTION_CLASS_CHANGED, new ClassChangedHandler());
    109         addHandler(BluetoothDevice.ACTION_UUID, new UuidChangedHandler());
    110         addHandler(BluetoothDevice.ACTION_BATTERY_LEVEL_CHANGED, new BatteryLevelChangedHandler());
    111 
    112         // Dock event broadcasts
    113         addHandler(Intent.ACTION_DOCK_EVENT, new DockEventHandler());
    114 
    115         // Active device broadcasts
    116         addHandler(BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED,
    117                    new ActiveDeviceChangedHandler());
    118         addHandler(BluetoothHeadset.ACTION_ACTIVE_DEVICE_CHANGED,
    119                    new ActiveDeviceChangedHandler());
    120         addHandler(BluetoothHearingAid.ACTION_ACTIVE_DEVICE_CHANGED,
    121                    new ActiveDeviceChangedHandler());
    122 
    123         // Headset state changed broadcasts
    124         addHandler(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED,
    125                 new AudioModeChangedHandler());
    126         addHandler(TelephonyManager.ACTION_PHONE_STATE_CHANGED,
    127                 new AudioModeChangedHandler());
    128 
    129         mContext.registerReceiver(mBroadcastReceiver, mAdapterIntentFilter, null, mReceiverHandler);
    130         mContext.registerReceiver(mProfileBroadcastReceiver, mProfileIntentFilter, null, mReceiverHandler);
    131     }
    132 
    133     void registerProfileIntentReceiver() {
    134         mContext.registerReceiver(mProfileBroadcastReceiver, mProfileIntentFilter, null, mReceiverHandler);
    135     }
    136 
    137     public void setReceiverHandler(android.os.Handler handler) {
    138         mContext.unregisterReceiver(mBroadcastReceiver);
    139         mContext.unregisterReceiver(mProfileBroadcastReceiver);
    140         mReceiverHandler = handler;
    141         mContext.registerReceiver(mBroadcastReceiver, mAdapterIntentFilter, null, mReceiverHandler);
    142         registerProfileIntentReceiver();
    143     }
    144 
    145     /** Register to start receiving callbacks for Bluetooth events. */
    146     public void registerCallback(BluetoothCallback callback) {
    147         synchronized (mCallbacks) {
    148             mCallbacks.add(callback);
    149         }
    150     }
    151 
    152     /** Unregister to stop receiving callbacks for Bluetooth events. */
    153     public void unregisterCallback(BluetoothCallback callback) {
    154         synchronized (mCallbacks) {
    155             mCallbacks.remove(callback);
    156         }
    157     }
    158 
    159     private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
    160         @Override
    161         public void onReceive(Context context, Intent intent) {
    162             String action = intent.getAction();
    163             BluetoothDevice device = intent
    164                     .getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
    165 
    166             Handler handler = mHandlerMap.get(action);
    167             if (handler != null) {
    168                 handler.onReceive(context, intent, device);
    169             }
    170         }
    171     };
    172 
    173     private final BroadcastReceiver mProfileBroadcastReceiver = new BroadcastReceiver() {
    174         @Override
    175         public void onReceive(Context context, Intent intent) {
    176             String action = intent.getAction();
    177             BluetoothDevice device = intent
    178                     .getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
    179 
    180             Handler handler = mHandlerMap.get(action);
    181             if (handler != null) {
    182                 handler.onReceive(context, intent, device);
    183             }
    184         }
    185     };
    186 
    187     private class AdapterStateChangedHandler implements Handler {
    188         public void onReceive(Context context, Intent intent,
    189                 BluetoothDevice device) {
    190             int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE,
    191                                     BluetoothAdapter.ERROR);
    192             // Reregister Profile Broadcast Receiver as part of TURN OFF
    193             if (state == BluetoothAdapter.STATE_OFF)
    194             {
    195                 context.unregisterReceiver(mProfileBroadcastReceiver);
    196                 registerProfileIntentReceiver();
    197             }
    198             // update local profiles and get paired devices
    199             mLocalAdapter.setBluetoothStateInt(state);
    200             // send callback to update UI and possibly start scanning
    201             synchronized (mCallbacks) {
    202                 for (BluetoothCallback callback : mCallbacks) {
    203                     callback.onBluetoothStateChanged(state);
    204                 }
    205             }
    206             // Inform CachedDeviceManager that the adapter state has changed
    207             mDeviceManager.onBluetoothStateChanged(state);
    208         }
    209     }
    210 
    211     private class ScanningStateChangedHandler implements Handler {
    212         private final boolean mStarted;
    213 
    214         ScanningStateChangedHandler(boolean started) {
    215             mStarted = started;
    216         }
    217         public void onReceive(Context context, Intent intent,
    218                 BluetoothDevice device) {
    219             synchronized (mCallbacks) {
    220                 for (BluetoothCallback callback : mCallbacks) {
    221                     callback.onScanningStateChanged(mStarted);
    222                 }
    223             }
    224             mDeviceManager.onScanningStateChanged(mStarted);
    225         }
    226     }
    227 
    228     private class DeviceFoundHandler implements Handler {
    229         public void onReceive(Context context, Intent intent,
    230                 BluetoothDevice device) {
    231             short rssi = intent.getShortExtra(BluetoothDevice.EXTRA_RSSI, Short.MIN_VALUE);
    232             BluetoothClass btClass = intent.getParcelableExtra(BluetoothDevice.EXTRA_CLASS);
    233             String name = intent.getStringExtra(BluetoothDevice.EXTRA_NAME);
    234             // TODO Pick up UUID. They should be available for 2.1 devices.
    235             // Skip for now, there's a bluez problem and we are not getting uuids even for 2.1.
    236             CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(device);
    237             if (cachedDevice == null) {
    238                 cachedDevice = mDeviceManager.addDevice(mLocalAdapter, mProfileManager, device);
    239                 Log.d(TAG, "DeviceFoundHandler created new CachedBluetoothDevice: "
    240                         + cachedDevice);
    241             }
    242             cachedDevice.setRssi(rssi);
    243             cachedDevice.setBtClass(btClass);
    244             cachedDevice.setNewName(name);
    245             cachedDevice.setJustDiscovered(true);
    246         }
    247     }
    248 
    249     private class ConnectionStateChangedHandler implements Handler {
    250         @Override
    251         public void onReceive(Context context, Intent intent, BluetoothDevice device) {
    252             CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(device);
    253             int state = intent.getIntExtra(BluetoothAdapter.EXTRA_CONNECTION_STATE,
    254                     BluetoothAdapter.ERROR);
    255             dispatchConnectionStateChanged(cachedDevice, state);
    256         }
    257     }
    258 
    259     private void dispatchConnectionStateChanged(CachedBluetoothDevice cachedDevice, int state) {
    260         synchronized (mCallbacks) {
    261             for (BluetoothCallback callback : mCallbacks) {
    262                 callback.onConnectionStateChanged(cachedDevice, state);
    263             }
    264         }
    265     }
    266 
    267     void dispatchDeviceAdded(CachedBluetoothDevice cachedDevice) {
    268         synchronized (mCallbacks) {
    269             for (BluetoothCallback callback : mCallbacks) {
    270                 callback.onDeviceAdded(cachedDevice);
    271             }
    272         }
    273     }
    274 
    275     void dispatchDeviceRemoved(CachedBluetoothDevice cachedDevice) {
    276         synchronized (mCallbacks) {
    277             for (BluetoothCallback callback : mCallbacks) {
    278                 callback.onDeviceDeleted(cachedDevice);
    279             }
    280         }
    281     }
    282 
    283     private class DeviceDisappearedHandler implements Handler {
    284         public void onReceive(Context context, Intent intent,
    285                 BluetoothDevice device) {
    286             CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(device);
    287             if (cachedDevice == null) {
    288                 Log.w(TAG, "received ACTION_DISAPPEARED for an unknown device: " + device);
    289                 return;
    290             }
    291             if (CachedBluetoothDeviceManager.onDeviceDisappeared(cachedDevice)) {
    292                 synchronized (mCallbacks) {
    293                     for (BluetoothCallback callback : mCallbacks) {
    294                         callback.onDeviceDeleted(cachedDevice);
    295                     }
    296                 }
    297             }
    298         }
    299     }
    300 
    301     private class NameChangedHandler implements Handler {
    302         public void onReceive(Context context, Intent intent,
    303                 BluetoothDevice device) {
    304             mDeviceManager.onDeviceNameUpdated(device);
    305         }
    306     }
    307 
    308     private class BondStateChangedHandler implements Handler {
    309         public void onReceive(Context context, Intent intent,
    310                 BluetoothDevice device) {
    311             if (device == null) {
    312                 Log.e(TAG, "ACTION_BOND_STATE_CHANGED with no EXTRA_DEVICE");
    313                 return;
    314             }
    315             int bondState = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE,
    316                                                BluetoothDevice.ERROR);
    317             CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(device);
    318             if (cachedDevice == null) {
    319                 Log.w(TAG, "CachedBluetoothDevice for device " + device +
    320                         " not found, calling readPairedDevices().");
    321                 if (readPairedDevices()) {
    322                     cachedDevice = mDeviceManager.findDevice(device);
    323                 }
    324 
    325                 if (cachedDevice == null) {
    326                     Log.w(TAG, "Got bonding state changed for " + device +
    327                             ", but we have no record of that device.");
    328 
    329                     cachedDevice = mDeviceManager.addDevice(mLocalAdapter, mProfileManager, device);
    330                     dispatchDeviceAdded(cachedDevice);
    331                 }
    332             }
    333 
    334             synchronized (mCallbacks) {
    335                 for (BluetoothCallback callback : mCallbacks) {
    336                     callback.onDeviceBondStateChanged(cachedDevice, bondState);
    337                 }
    338             }
    339             cachedDevice.onBondingStateChanged(bondState);
    340 
    341             if (bondState == BluetoothDevice.BOND_NONE) {
    342                 /* Check if we need to remove other Hearing Aid devices */
    343                 if (cachedDevice.getHiSyncId() != BluetoothHearingAid.HI_SYNC_ID_INVALID) {
    344                     mDeviceManager.onDeviceUnpaired(cachedDevice);
    345                 }
    346                 int reason = intent.getIntExtra(BluetoothDevice.EXTRA_REASON,
    347                         BluetoothDevice.ERROR);
    348 
    349                 showUnbondMessage(context, cachedDevice.getName(), reason);
    350             }
    351         }
    352 
    353         /**
    354          * Called when we have reached the unbonded state.
    355          *
    356          * @param reason one of the error reasons from
    357          *            BluetoothDevice.UNBOND_REASON_*
    358          */
    359         private void showUnbondMessage(Context context, String name, int reason) {
    360             int errorMsg;
    361 
    362             switch(reason) {
    363             case BluetoothDevice.UNBOND_REASON_AUTH_FAILED:
    364                 errorMsg = R.string.bluetooth_pairing_pin_error_message;
    365                 break;
    366             case BluetoothDevice.UNBOND_REASON_AUTH_REJECTED:
    367                 errorMsg = R.string.bluetooth_pairing_rejected_error_message;
    368                 break;
    369             case BluetoothDevice.UNBOND_REASON_REMOTE_DEVICE_DOWN:
    370                 errorMsg = R.string.bluetooth_pairing_device_down_error_message;
    371                 break;
    372             case BluetoothDevice.UNBOND_REASON_DISCOVERY_IN_PROGRESS:
    373             case BluetoothDevice.UNBOND_REASON_AUTH_TIMEOUT:
    374             case BluetoothDevice.UNBOND_REASON_REPEATED_ATTEMPTS:
    375             case BluetoothDevice.UNBOND_REASON_REMOTE_AUTH_CANCELED:
    376                 errorMsg = R.string.bluetooth_pairing_error_message;
    377                 break;
    378             default:
    379                 Log.w(TAG, "showUnbondMessage: Not displaying any message for reason: " + reason);
    380                 return;
    381             }
    382             Utils.showError(context, name, errorMsg);
    383         }
    384     }
    385 
    386     private class ClassChangedHandler implements Handler {
    387         public void onReceive(Context context, Intent intent,
    388                 BluetoothDevice device) {
    389             mDeviceManager.onBtClassChanged(device);
    390         }
    391     }
    392 
    393     private class UuidChangedHandler implements Handler {
    394         public void onReceive(Context context, Intent intent,
    395                 BluetoothDevice device) {
    396             mDeviceManager.onUuidChanged(device);
    397         }
    398     }
    399 
    400     private class DockEventHandler implements Handler {
    401         public void onReceive(Context context, Intent intent, BluetoothDevice device) {
    402             // Remove if unpair device upon undocking
    403             int anythingButUnDocked = Intent.EXTRA_DOCK_STATE_UNDOCKED + 1;
    404             int state = intent.getIntExtra(Intent.EXTRA_DOCK_STATE, anythingButUnDocked);
    405             if (state == Intent.EXTRA_DOCK_STATE_UNDOCKED) {
    406                 if (device != null && device.getBondState() == BluetoothDevice.BOND_NONE) {
    407                     CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(device);
    408                     if (cachedDevice != null) {
    409                         cachedDevice.setJustDiscovered(false);
    410                     }
    411                 }
    412             }
    413         }
    414     }
    415 
    416     private class BatteryLevelChangedHandler implements Handler {
    417         public void onReceive(Context context, Intent intent,
    418                 BluetoothDevice device) {
    419             CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(device);
    420             if (cachedDevice != null) {
    421                 cachedDevice.refresh();
    422             }
    423         }
    424     }
    425 
    426     boolean readPairedDevices() {
    427         Set<BluetoothDevice> bondedDevices = mLocalAdapter.getBondedDevices();
    428         if (bondedDevices == null) {
    429             return false;
    430         }
    431 
    432         boolean deviceAdded = false;
    433         for (BluetoothDevice device : bondedDevices) {
    434             CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(device);
    435             if (cachedDevice == null) {
    436                 cachedDevice = mDeviceManager.addDevice(mLocalAdapter, mProfileManager, device);
    437                 dispatchDeviceAdded(cachedDevice);
    438                 deviceAdded = true;
    439             }
    440         }
    441 
    442         return deviceAdded;
    443     }
    444 
    445     private class ActiveDeviceChangedHandler implements Handler {
    446         @Override
    447         public void onReceive(Context context, Intent intent, BluetoothDevice device) {
    448             String action = intent.getAction();
    449             if (action == null) {
    450                 Log.w(TAG, "ActiveDeviceChangedHandler: action is null");
    451                 return;
    452             }
    453             CachedBluetoothDevice activeDevice = mDeviceManager.findDevice(device);
    454             int bluetoothProfile = 0;
    455             if (Objects.equals(action, BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED)) {
    456                 bluetoothProfile = BluetoothProfile.A2DP;
    457             } else if (Objects.equals(action, BluetoothHeadset.ACTION_ACTIVE_DEVICE_CHANGED)) {
    458                 bluetoothProfile = BluetoothProfile.HEADSET;
    459             } else if (Objects.equals(action, BluetoothHearingAid.ACTION_ACTIVE_DEVICE_CHANGED)) {
    460                 bluetoothProfile = BluetoothProfile.HEARING_AID;
    461             } else {
    462                 Log.w(TAG, "ActiveDeviceChangedHandler: unknown action " + action);
    463                 return;
    464             }
    465             dispatchActiveDeviceChanged(activeDevice, bluetoothProfile);
    466         }
    467     }
    468 
    469     private void dispatchActiveDeviceChanged(CachedBluetoothDevice activeDevice,
    470                                              int bluetoothProfile) {
    471         mDeviceManager.onActiveDeviceChanged(activeDevice, bluetoothProfile);
    472         synchronized (mCallbacks) {
    473             for (BluetoothCallback callback : mCallbacks) {
    474                 callback.onActiveDeviceChanged(activeDevice, bluetoothProfile);
    475             }
    476         }
    477     }
    478 
    479     private class AudioModeChangedHandler implements Handler {
    480 
    481         @Override
    482         public void onReceive(Context context, Intent intent, BluetoothDevice device) {
    483             final String action = intent.getAction();
    484             if (action == null) {
    485                 Log.w(TAG, "AudioModeChangedHandler() action is null");
    486                 return;
    487             }
    488             dispatchAudioModeChanged();
    489         }
    490     }
    491 
    492     private void dispatchAudioModeChanged() {
    493         mDeviceManager.dispatchAudioModeChanged();
    494         synchronized (mCallbacks) {
    495             for (BluetoothCallback callback : mCallbacks) {
    496                 callback.onAudioModeChanged();
    497             }
    498         }
    499     }
    500 
    501     void dispatchProfileConnectionStateChanged(CachedBluetoothDevice device, int state,
    502             int bluetoothProfile) {
    503         synchronized (mCallbacks) {
    504             for (BluetoothCallback callback : mCallbacks) {
    505                 callback.onProfileConnectionStateChanged(device, state, bluetoothProfile);
    506             }
    507         }
    508         mDeviceManager.onProfileConnectionStateChanged(device, state, bluetoothProfile);
    509     }
    510 }
    511