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         for (CachedBluetoothDevice cachedDevice : mDeviceManager.getCachedDevicesCopy()) {
216             cachedDevice.onAudioModeChanged();
217         }
218         for (BluetoothCallback callback : mCallbacks) {
219             callback.onAudioModeChanged();
220         }
221     }
222 
223     @VisibleForTesting
dispatchActiveDeviceChanged(CachedBluetoothDevice activeDevice, int bluetoothProfile)224     void dispatchActiveDeviceChanged(CachedBluetoothDevice activeDevice,
225             int bluetoothProfile) {
226         for (CachedBluetoothDevice cachedDevice : mDeviceManager.getCachedDevicesCopy()) {
227             boolean isActive = Objects.equals(cachedDevice, activeDevice);
228             cachedDevice.onActiveDeviceChanged(isActive, bluetoothProfile);
229         }
230         for (BluetoothCallback callback : mCallbacks) {
231             callback.onActiveDeviceChanged(activeDevice, bluetoothProfile);
232         }
233     }
234 
dispatchAclStateChanged(CachedBluetoothDevice activeDevice, int state)235     private void dispatchAclStateChanged(CachedBluetoothDevice activeDevice, int state) {
236         for (BluetoothCallback callback : mCallbacks) {
237             callback.onAclConnectionStateChanged(activeDevice, state);
238         }
239     }
240 
241     @VisibleForTesting
addHandler(String action, Handler handler)242     void addHandler(String action, Handler handler) {
243         mHandlerMap.put(action, handler);
244         mAdapterIntentFilter.addAction(action);
245     }
246 
247     private class BluetoothBroadcastReceiver extends BroadcastReceiver {
248         @Override
onReceive(Context context, Intent intent)249         public void onReceive(Context context, Intent intent) {
250             String action = intent.getAction();
251             BluetoothDevice device = intent
252                     .getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
253 
254             Handler handler = mHandlerMap.get(action);
255             if (handler != null) {
256                 handler.onReceive(context, intent, device);
257             }
258         }
259     }
260 
261     private class AdapterStateChangedHandler implements Handler {
onReceive(Context context, Intent intent, BluetoothDevice device)262         public void onReceive(Context context, Intent intent, BluetoothDevice device) {
263             int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE,
264                     BluetoothAdapter.ERROR);
265             // update local profiles and get paired devices
266             mLocalAdapter.setBluetoothStateInt(state);
267             // send callback to update UI and possibly start scanning
268             for (BluetoothCallback callback : mCallbacks) {
269                 callback.onBluetoothStateChanged(state);
270             }
271             // Inform CachedDeviceManager that the adapter state has changed
272             mDeviceManager.onBluetoothStateChanged(state);
273         }
274     }
275 
276     private class ScanningStateChangedHandler implements Handler {
277         private final boolean mStarted;
278 
ScanningStateChangedHandler(boolean started)279         ScanningStateChangedHandler(boolean started) {
280             mStarted = started;
281         }
282 
onReceive(Context context, Intent intent, BluetoothDevice device)283         public void onReceive(Context context, Intent intent, BluetoothDevice device) {
284             for (BluetoothCallback callback : mCallbacks) {
285                 callback.onScanningStateChanged(mStarted);
286             }
287             mDeviceManager.onScanningStateChanged(mStarted);
288         }
289     }
290 
291     private class DeviceFoundHandler implements Handler {
onReceive(Context context, Intent intent, BluetoothDevice device)292         public void onReceive(Context context, Intent intent,
293                 BluetoothDevice device) {
294             short rssi = intent.getShortExtra(BluetoothDevice.EXTRA_RSSI, Short.MIN_VALUE);
295             String name = intent.getStringExtra(BluetoothDevice.EXTRA_NAME);
296             // TODO Pick up UUID. They should be available for 2.1 devices.
297             // Skip for now, there's a bluez problem and we are not getting uuids even for 2.1.
298             CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(device);
299             if (cachedDevice == null) {
300                 cachedDevice = mDeviceManager.addDevice(device);
301                 Log.d(TAG, "DeviceFoundHandler created new CachedBluetoothDevice: "
302                         + cachedDevice);
303             } else if (cachedDevice.getBondState() == BluetoothDevice.BOND_BONDED
304                     && !cachedDevice.getDevice().isConnected()) {
305                 // Dispatch device add callback to show bonded but
306                 // not connected devices in discovery mode
307                 dispatchDeviceAdded(cachedDevice);
308                 Log.d(TAG, "DeviceFoundHandler found bonded and not connected device:"
309                         + cachedDevice);
310             } else {
311                 Log.d(TAG, "DeviceFoundHandler found existing CachedBluetoothDevice:"
312                         + cachedDevice);
313             }
314             cachedDevice.setRssi(rssi);
315             cachedDevice.setJustDiscovered(true);
316         }
317     }
318 
319     private class ConnectionStateChangedHandler implements Handler {
320         @Override
onReceive(Context context, Intent intent, BluetoothDevice device)321         public void onReceive(Context context, Intent intent, BluetoothDevice device) {
322             CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(device);
323             int state = intent.getIntExtra(BluetoothAdapter.EXTRA_CONNECTION_STATE,
324                     BluetoothAdapter.ERROR);
325             dispatchConnectionStateChanged(cachedDevice, state);
326         }
327     }
328 
329     private class NameChangedHandler implements Handler {
onReceive(Context context, Intent intent, BluetoothDevice device)330         public void onReceive(Context context, Intent intent,
331                 BluetoothDevice device) {
332             mDeviceManager.onDeviceNameUpdated(device);
333         }
334     }
335 
336     private class BondStateChangedHandler implements Handler {
onReceive(Context context, Intent intent, BluetoothDevice device)337         public void onReceive(Context context, Intent intent, BluetoothDevice device) {
338             if (device == null) {
339                 Log.e(TAG, "ACTION_BOND_STATE_CHANGED with no EXTRA_DEVICE");
340                 return;
341             }
342             int bondState = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE,
343                     BluetoothDevice.ERROR);
344             CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(device);
345             if (cachedDevice == null) {
346                 Log.w(TAG, "Got bonding state changed for " + device +
347                         ", but we have no record of that device.");
348                 cachedDevice = mDeviceManager.addDevice(device);
349             }
350 
351             for (BluetoothCallback callback : mCallbacks) {
352                 callback.onDeviceBondStateChanged(cachedDevice, bondState);
353             }
354             cachedDevice.onBondingStateChanged(bondState);
355 
356             if (bondState == BluetoothDevice.BOND_NONE) {
357                 /* Check if we need to remove other Hearing Aid devices */
358                 if (cachedDevice.getHiSyncId() != BluetoothHearingAid.HI_SYNC_ID_INVALID) {
359                     mDeviceManager.onDeviceUnpaired(cachedDevice);
360                 }
361                 int reason = intent.getIntExtra(BluetoothDevice.EXTRA_REASON,
362                         BluetoothDevice.ERROR);
363 
364                 showUnbondMessage(context, cachedDevice.getName(), reason);
365             }
366         }
367 
368         /**
369          * Called when we have reached the unbonded state.
370          *
371          * @param reason one of the error reasons from
372          *               BluetoothDevice.UNBOND_REASON_*
373          */
showUnbondMessage(Context context, String name, int reason)374         private void showUnbondMessage(Context context, String name, int reason) {
375             int errorMsg;
376 
377             switch (reason) {
378                 case BluetoothDevice.UNBOND_REASON_AUTH_FAILED:
379                     errorMsg = R.string.bluetooth_pairing_pin_error_message;
380                     break;
381                 case BluetoothDevice.UNBOND_REASON_AUTH_REJECTED:
382                     errorMsg = R.string.bluetooth_pairing_rejected_error_message;
383                     break;
384                 case BluetoothDevice.UNBOND_REASON_REMOTE_DEVICE_DOWN:
385                     errorMsg = R.string.bluetooth_pairing_device_down_error_message;
386                     break;
387                 case BluetoothDevice.UNBOND_REASON_DISCOVERY_IN_PROGRESS:
388                 case BluetoothDevice.UNBOND_REASON_AUTH_TIMEOUT:
389                 case BluetoothDevice.UNBOND_REASON_REPEATED_ATTEMPTS:
390                 case BluetoothDevice.UNBOND_REASON_REMOTE_AUTH_CANCELED:
391                     errorMsg = R.string.bluetooth_pairing_error_message;
392                     break;
393                 default:
394                     Log.w(TAG,
395                             "showUnbondMessage: Not displaying any message for reason: " + reason);
396                     return;
397             }
398             BluetoothUtils.showError(context, name, errorMsg);
399         }
400     }
401 
402     private class ClassChangedHandler implements Handler {
onReceive(Context context, Intent intent, BluetoothDevice device)403         public void onReceive(Context context, Intent intent, BluetoothDevice device) {
404             CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(device);
405             if (cachedDevice != null) {
406                 cachedDevice.refresh();
407             }
408         }
409     }
410 
411     private class UuidChangedHandler implements Handler {
onReceive(Context context, Intent intent, BluetoothDevice device)412         public void onReceive(Context context, Intent intent, BluetoothDevice device) {
413             CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(device);
414             if (cachedDevice != null) {
415                 cachedDevice.onUuidChanged();
416             }
417         }
418     }
419 
420     private class BatteryLevelChangedHandler implements Handler {
onReceive(Context context, Intent intent, BluetoothDevice device)421         public void onReceive(Context context, Intent intent, BluetoothDevice device) {
422             CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(device);
423             if (cachedDevice != null) {
424                 cachedDevice.refresh();
425             }
426         }
427     }
428 
429     private class ActiveDeviceChangedHandler implements Handler {
430         @Override
onReceive(Context context, Intent intent, BluetoothDevice device)431         public void onReceive(Context context, Intent intent, BluetoothDevice device) {
432             String action = intent.getAction();
433             if (action == null) {
434                 Log.w(TAG, "ActiveDeviceChangedHandler: action is null");
435                 return;
436             }
437             CachedBluetoothDevice activeDevice = mDeviceManager.findDevice(device);
438             int bluetoothProfile = 0;
439             if (Objects.equals(action, BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED)) {
440                 bluetoothProfile = BluetoothProfile.A2DP;
441             } else if (Objects.equals(action, BluetoothHeadset.ACTION_ACTIVE_DEVICE_CHANGED)) {
442                 bluetoothProfile = BluetoothProfile.HEADSET;
443             } else if (Objects.equals(action, BluetoothHearingAid.ACTION_ACTIVE_DEVICE_CHANGED)) {
444                 bluetoothProfile = BluetoothProfile.HEARING_AID;
445             } else {
446                 Log.w(TAG, "ActiveDeviceChangedHandler: unknown action " + action);
447                 return;
448             }
449             dispatchActiveDeviceChanged(activeDevice, bluetoothProfile);
450         }
451     }
452 
453     private class AclStateChangedHandler implements Handler {
454         @Override
onReceive(Context context, Intent intent, BluetoothDevice device)455         public void onReceive(Context context, Intent intent, BluetoothDevice device) {
456             if (device == null) {
457                 Log.w(TAG, "AclStateChangedHandler: device is null");
458                 return;
459             }
460 
461             // Avoid to notify Settings UI for Hearing Aid sub device.
462             if (mDeviceManager.isSubDevice(device)) {
463                 return;
464             }
465 
466             final String action = intent.getAction();
467             if (action == null) {
468                 Log.w(TAG, "AclStateChangedHandler: action is null");
469                 return;
470             }
471             final CachedBluetoothDevice activeDevice = mDeviceManager.findDevice(device);
472             if (activeDevice == null) {
473                 Log.w(TAG, "AclStateChangedHandler: activeDevice is null");
474                 return;
475             }
476             final int state;
477             switch (action) {
478                 case BluetoothDevice.ACTION_ACL_CONNECTED:
479                     state = BluetoothAdapter.STATE_CONNECTED;
480                     break;
481                 case BluetoothDevice.ACTION_ACL_DISCONNECTED:
482                     state = BluetoothAdapter.STATE_DISCONNECTED;
483                     break;
484                 default:
485                     Log.w(TAG, "ActiveDeviceChangedHandler: unknown action " + action);
486                     return;
487 
488             }
489             dispatchAclStateChanged(activeDevice, state);
490         }
491     }
492 
493     private class AudioModeChangedHandler implements Handler {
494 
495         @Override
onReceive(Context context, Intent intent, BluetoothDevice device)496         public void onReceive(Context context, Intent intent, BluetoothDevice device) {
497             final String action = intent.getAction();
498             if (action == null) {
499                 Log.w(TAG, "AudioModeChangedHandler() action is null");
500                 return;
501             }
502             dispatchAudioModeChanged();
503         }
504     }
505 }
506