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