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