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