• 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.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 {
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, 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. */
registerCallback(BluetoothCallback callback)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. */
unregisterCallback(BluetoothCallback callback)137     public void unregisterCallback(BluetoothCallback callback) {
138         synchronized (mCallbacks) {
139             mCallbacks.remove(callback);
140         }
141     }
142 
143     @VisibleForTesting
registerProfileIntentReceiver()144     void registerProfileIntentReceiver() {
145         registerIntentReceiver(mProfileBroadcastReceiver, mProfileIntentFilter);
146     }
147 
148     @VisibleForTesting
registerAdapterIntentReceiver()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      */
registerIntentReceiver(BroadcastReceiver receiver, IntentFilter filter)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
addProfileHandler(String action, Handler handler)168     void addProfileHandler(String action, Handler handler) {
169         mHandlerMap.put(action, handler);
170         mProfileIntentFilter.addAction(action);
171     }
172 
readPairedDevices()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 
dispatchDeviceAdded(CachedBluetoothDevice cachedDevice)191     void dispatchDeviceAdded(CachedBluetoothDevice cachedDevice) {
192         synchronized (mCallbacks) {
193             for (BluetoothCallback callback : mCallbacks) {
194                 callback.onDeviceAdded(cachedDevice);
195             }
196         }
197     }
198 
dispatchDeviceRemoved(CachedBluetoothDevice cachedDevice)199     void dispatchDeviceRemoved(CachedBluetoothDevice cachedDevice) {
200         synchronized (mCallbacks) {
201             for (BluetoothCallback callback : mCallbacks) {
202                 callback.onDeviceDeleted(cachedDevice);
203             }
204         }
205     }
206 
dispatchProfileConnectionStateChanged(CachedBluetoothDevice device, int state, int bluetoothProfile)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 
dispatchConnectionStateChanged(CachedBluetoothDevice cachedDevice, int state)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 
dispatchAudioModeChanged()224     private void dispatchAudioModeChanged() {
225         mDeviceManager.dispatchAudioModeChanged();
226         synchronized (mCallbacks) {
227             for (BluetoothCallback callback : mCallbacks) {
228                 callback.onAudioModeChanged();
229             }
230         }
231     }
232 
dispatchActiveDeviceChanged(CachedBluetoothDevice activeDevice, int bluetoothProfile)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 
dispatchAclStateChanged(CachedBluetoothDevice activeDevice, int state)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
addHandler(String action, Handler handler)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
onReceive(Context context, Intent intent)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 {
onReceive(Context context, Intent intent, BluetoothDevice device)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 
ScanningStateChangedHandler(boolean started)293         ScanningStateChangedHandler(boolean started) {
294             mStarted = started;
295         }
onReceive(Context context, Intent intent, BluetoothDevice device)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 {
onReceive(Context context, Intent intent, BluetoothDevice device)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
onReceive(Context context, Intent intent, BluetoothDevice device)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 {
onReceive(Context context, Intent intent, BluetoothDevice device)346         public void onReceive(Context context, Intent intent,
347                 BluetoothDevice device) {
348             mDeviceManager.onDeviceNameUpdated(device);
349         }
350     }
351 
352     private class BondStateChangedHandler implements Handler {
onReceive(Context context, Intent intent, BluetoothDevice device)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          */
showUnbondMessage(Context context, String name, int reason)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 {
onReceive(Context context, Intent intent, BluetoothDevice device)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 {
onReceive(Context context, Intent intent, BluetoothDevice device)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 {
onReceive(Context context, Intent intent, BluetoothDevice device)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
onReceive(Context context, Intent intent, BluetoothDevice device)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
onReceive(Context context, Intent intent, BluetoothDevice device)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
onReceive(Context context, Intent intent, BluetoothDevice device)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