1 /*
2  * Copyright (C) 2008 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.systemui.statusbar.policy;
18 
19 import android.annotation.Nullable;
20 import android.bluetooth.BluetoothAdapter;
21 import android.bluetooth.BluetoothProfile;
22 import android.content.Context;
23 import android.os.Handler;
24 import android.os.Looper;
25 import android.os.Message;
26 import android.os.UserHandle;
27 import android.os.UserManager;
28 
29 import androidx.annotation.NonNull;
30 
31 import com.android.internal.annotations.GuardedBy;
32 import com.android.settingslib.bluetooth.BluetoothCallback;
33 import com.android.settingslib.bluetooth.CachedBluetoothDevice;
34 import com.android.settingslib.bluetooth.LocalBluetoothManager;
35 import com.android.settingslib.bluetooth.LocalBluetoothProfile;
36 import com.android.settingslib.bluetooth.LocalBluetoothProfileManager;
37 import com.android.systemui.bluetooth.BluetoothLogger;
38 import com.android.systemui.dagger.SysUISingleton;
39 import com.android.systemui.dagger.qualifiers.Main;
40 import com.android.systemui.dump.DumpManager;
41 import com.android.systemui.flags.FeatureFlags;
42 import com.android.systemui.flags.Flags;
43 import com.android.systemui.settings.UserTracker;
44 import com.android.systemui.statusbar.policy.bluetooth.BluetoothRepository;
45 import com.android.systemui.statusbar.policy.bluetooth.ConnectionStatusModel;
46 
47 import java.io.PrintWriter;
48 import java.util.ArrayList;
49 import java.util.Collection;
50 import java.util.Collections;
51 import java.util.List;
52 import java.util.concurrent.Executor;
53 
54 import javax.inject.Inject;
55 
56 /**
57  * Controller for information about bluetooth connections.
58  *
59  * Note: Right now, this class and {@link BluetoothRepository} co-exist. Any new code should go in
60  * {@link BluetoothRepository}, but external clients should query this file for now.
61  */
62 @SysUISingleton
63 public class BluetoothControllerImpl implements BluetoothController, BluetoothCallback,
64         CachedBluetoothDevice.Callback, LocalBluetoothProfileManager.ServiceListener {
65     private static final String TAG = "BluetoothController";
66 
67     private final FeatureFlags mFeatureFlags;
68     private final DumpManager mDumpManager;
69     private final BluetoothLogger mLogger;
70     private final BluetoothRepository mBluetoothRepository;
71     private final LocalBluetoothManager mLocalBluetoothManager;
72     private final UserManager mUserManager;
73     private final int mCurrentUser;
74     @GuardedBy("mConnectedDevices")
75     private final List<CachedBluetoothDevice> mConnectedDevices = new ArrayList<>();
76 
77     private boolean mEnabled;
78     @ConnectionState
79     private int mConnectionState = BluetoothAdapter.STATE_DISCONNECTED;
80     private boolean mAudioProfileOnly;
81     private boolean mIsActive;
82 
83     private final H mHandler;
84     private int mState;
85 
86     private final BluetoothAdapter mAdapter;
87     /**
88      */
89     @Inject
BluetoothControllerImpl( Context context, FeatureFlags featureFlags, UserTracker userTracker, DumpManager dumpManager, BluetoothLogger logger, BluetoothRepository bluetoothRepository, @Main Looper mainLooper, @Nullable LocalBluetoothManager localBluetoothManager, @Nullable BluetoothAdapter bluetoothAdapter)90     public BluetoothControllerImpl(
91             Context context,
92             FeatureFlags featureFlags,
93             UserTracker userTracker,
94             DumpManager dumpManager,
95             BluetoothLogger logger,
96             BluetoothRepository bluetoothRepository,
97             @Main Looper mainLooper,
98             @Nullable LocalBluetoothManager localBluetoothManager,
99             @Nullable BluetoothAdapter bluetoothAdapter) {
100         mFeatureFlags = featureFlags;
101         mDumpManager = dumpManager;
102         mLogger = logger;
103         mBluetoothRepository = bluetoothRepository;
104         mLocalBluetoothManager = localBluetoothManager;
105         mHandler = new H(mainLooper);
106         if (mLocalBluetoothManager != null) {
107             mLocalBluetoothManager.getEventManager().registerCallback(this);
108             mLocalBluetoothManager.getProfileManager().addServiceListener(this);
109             onBluetoothStateChanged(
110                     mLocalBluetoothManager.getBluetoothAdapter().getBluetoothState());
111         }
112         mUserManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
113         mCurrentUser = userTracker.getUserId();
114         mDumpManager.registerDumpable(TAG, this);
115         mAdapter = bluetoothAdapter;
116     }
117 
118     @Override
canConfigBluetooth()119     public boolean canConfigBluetooth() {
120         return !mUserManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_BLUETOOTH,
121                 UserHandle.of(mCurrentUser))
122             && !mUserManager.hasUserRestriction(UserManager.DISALLOW_BLUETOOTH,
123                 UserHandle.of(mCurrentUser));
124     }
125 
dump(PrintWriter pw, String[] args)126     public void dump(PrintWriter pw, String[] args) {
127         pw.println("BluetoothController state:");
128         pw.print("  mLocalBluetoothManager="); pw.println(mLocalBluetoothManager);
129         if (mLocalBluetoothManager == null) {
130             return;
131         }
132         pw.print("  mEnabled="); pw.println(mEnabled);
133         pw.print("  mConnectionState="); pw.println(connectionStateToString(mConnectionState));
134         pw.print("  mAudioProfileOnly="); pw.println(mAudioProfileOnly);
135         pw.print("  mIsActive="); pw.println(mIsActive);
136         pw.print("  mConnectedDevices="); pw.println(getConnectedDevices());
137         pw.print("  mCallbacks.size="); pw.println(mHandler.mCallbacks.size());
138         pw.println("  Bluetooth Devices:");
139         for (CachedBluetoothDevice device : getDevices()) {
140             pw.println("    " + getDeviceString(device));
141         }
142     }
143 
connectionStateToString(@onnectionState int state)144     private static String connectionStateToString(@ConnectionState int state) {
145         switch (state) {
146             case BluetoothAdapter.STATE_CONNECTED:
147                 return "CONNECTED";
148             case BluetoothAdapter.STATE_CONNECTING:
149                 return "CONNECTING";
150             case BluetoothAdapter.STATE_DISCONNECTED:
151                 return "DISCONNECTED";
152             case BluetoothAdapter.STATE_DISCONNECTING:
153                 return "DISCONNECTING";
154         }
155         return "UNKNOWN(" + state + ")";
156     }
157 
getDeviceString(CachedBluetoothDevice device)158     private String getDeviceString(CachedBluetoothDevice device) {
159         return device.getName()
160                 + " profiles=" + getDeviceProfilesString(device)
161                 + " connected=" + device.isConnected()
162                 + " active[A2DP]=" + device.isActiveDevice(BluetoothProfile.A2DP)
163                 + " active[HEADSET]=" + device.isActiveDevice(BluetoothProfile.HEADSET)
164                 + " active[HEARING_AID]=" + device.isActiveDevice(BluetoothProfile.HEARING_AID)
165                 + " active[LE_AUDIO]=" + device.isActiveDevice(BluetoothProfile.LE_AUDIO);
166     }
167 
getDeviceProfilesString(CachedBluetoothDevice device)168     private String getDeviceProfilesString(CachedBluetoothDevice device) {
169         List<String> profileIds = new ArrayList<>();
170         for (LocalBluetoothProfile profile : device.getProfiles()) {
171             profileIds.add(String.valueOf(profile.getProfileId()));
172         }
173         return "[" + String.join(",", profileIds) + "]";
174     }
175 
176     @Override
getConnectedDevices()177     public List<CachedBluetoothDevice> getConnectedDevices() {
178         List<CachedBluetoothDevice> out;
179         synchronized (mConnectedDevices) {
180             out = new ArrayList<>(mConnectedDevices);
181         }
182         return out;
183     }
184 
185     @Override
addCallback(@onNull Callback cb)186     public void addCallback(@NonNull Callback cb) {
187         mHandler.obtainMessage(H.MSG_ADD_CALLBACK, cb).sendToTarget();
188         mHandler.sendEmptyMessage(H.MSG_STATE_CHANGED);
189     }
190 
191     @Override
removeCallback(@onNull Callback cb)192     public void removeCallback(@NonNull Callback cb) {
193         mHandler.obtainMessage(H.MSG_REMOVE_CALLBACK, cb).sendToTarget();
194     }
195 
196     @Override
isBluetoothEnabled()197     public boolean isBluetoothEnabled() {
198         return mEnabled;
199     }
200 
201     @Override
getBluetoothState()202     public int getBluetoothState() {
203         return mState;
204     }
205 
206     @Override
isBluetoothConnected()207     public boolean isBluetoothConnected() {
208         return mConnectionState == BluetoothAdapter.STATE_CONNECTED;
209     }
210 
211     @Override
isBluetoothConnecting()212     public boolean isBluetoothConnecting() {
213         return mConnectionState == BluetoothAdapter.STATE_CONNECTING;
214     }
215 
216     @Override
isBluetoothAudioProfileOnly()217     public boolean isBluetoothAudioProfileOnly() {
218         return mAudioProfileOnly;
219     }
220 
221     @Override
isBluetoothAudioActive()222     public boolean isBluetoothAudioActive() {
223         return mIsActive;
224     }
225 
226     @Override
setBluetoothEnabled(boolean enabled)227     public void setBluetoothEnabled(boolean enabled) {
228         if (mLocalBluetoothManager != null) {
229             mLocalBluetoothManager.getBluetoothAdapter().setBluetoothEnabled(enabled);
230         }
231     }
232 
233     @Override
isBluetoothSupported()234     public boolean isBluetoothSupported() {
235         return mLocalBluetoothManager != null;
236     }
237 
238     @Override
getConnectedDeviceName()239     public String getConnectedDeviceName() {
240         synchronized (mConnectedDevices) {
241             if (mConnectedDevices.size() == 1) {
242                 return mConnectedDevices.get(0).getName();
243             }
244         }
245         return null;
246     }
247 
getDevices()248     private Collection<CachedBluetoothDevice> getDevices() {
249         return mLocalBluetoothManager != null
250                 ? mLocalBluetoothManager.getCachedDeviceManager().getCachedDevicesCopy()
251                 : Collections.emptyList();
252     }
253 
updateConnected()254     private void updateConnected() {
255         if (mFeatureFlags.isEnabled(Flags.NEW_BLUETOOTH_REPOSITORY)) {
256             mBluetoothRepository.fetchConnectionStatusInBackground(
257                     getDevices(), this::onConnectionStatusFetched);
258         } else {
259             updateConnectedOld();
260         }
261     }
262 
263     /** Used only if {@link Flags.NEW_BLUETOOTH_REPOSITORY} is *not* enabled. */
updateConnectedOld()264     private void updateConnectedOld() {
265         // Make sure our connection state is up to date.
266         int state = mLocalBluetoothManager.getBluetoothAdapter().getConnectionState();
267         List<CachedBluetoothDevice> newList = new ArrayList<>();
268         // If any of the devices are in a higher state than the adapter, move the adapter into
269         // that state.
270         for (CachedBluetoothDevice device : getDevices()) {
271             int maxDeviceState = device.getMaxConnectionState();
272             if (maxDeviceState > state) {
273                 state = maxDeviceState;
274             }
275             if (device.isConnected()) {
276                 newList.add(device);
277             }
278         }
279 
280         if (newList.isEmpty() && state == BluetoothAdapter.STATE_CONNECTED) {
281             // If somehow we think we are connected, but have no connected devices, we aren't
282             // connected.
283             state = BluetoothAdapter.STATE_DISCONNECTED;
284         }
285         onConnectionStatusFetched(new ConnectionStatusModel(state, newList));
286     }
287 
onConnectionStatusFetched(ConnectionStatusModel status)288     private void onConnectionStatusFetched(ConnectionStatusModel status) {
289         List<CachedBluetoothDevice> newList = status.getConnectedDevices();
290         int state = status.getMaxConnectionState();
291         synchronized (mConnectedDevices) {
292             mConnectedDevices.clear();
293             mConnectedDevices.addAll(newList);
294         }
295         if (state != mConnectionState) {
296             mConnectionState = state;
297             mHandler.sendEmptyMessage(H.MSG_STATE_CHANGED);
298         }
299         updateAudioProfile();
300     }
301 
updateActive()302     private void updateActive() {
303         boolean isActive = false;
304 
305         for (CachedBluetoothDevice device : getDevices()) {
306             isActive |= device.isActiveDevice(BluetoothProfile.HEADSET)
307                     || device.isActiveDevice(BluetoothProfile.A2DP)
308                     || device.isActiveDevice(BluetoothProfile.HEARING_AID)
309                     || device.isActiveDevice(BluetoothProfile.LE_AUDIO);
310         }
311 
312         if (mIsActive != isActive) {
313             mIsActive = isActive;
314             mHandler.sendEmptyMessage(H.MSG_STATE_CHANGED);
315         }
316     }
317 
updateAudioProfile()318     private void updateAudioProfile() {
319         boolean audioProfileConnected = false;
320         boolean otherProfileConnected = false;
321 
322         for (CachedBluetoothDevice device : getDevices()) {
323             for (LocalBluetoothProfile profile : device.getProfiles()) {
324                 int profileId = profile.getProfileId();
325                 boolean isConnected = device.isConnectedProfile(profile);
326                 if (profileId == BluetoothProfile.HEADSET
327                         || profileId == BluetoothProfile.A2DP
328                         || profileId == BluetoothProfile.HEARING_AID
329                         || profileId == BluetoothProfile.LE_AUDIO) {
330                     audioProfileConnected |= isConnected;
331                 } else {
332                     otherProfileConnected |= isConnected;
333                 }
334             }
335         }
336 
337         boolean audioProfileOnly = (audioProfileConnected && !otherProfileConnected);
338         if (audioProfileOnly != mAudioProfileOnly) {
339             mAudioProfileOnly = audioProfileOnly;
340             mHandler.sendEmptyMessage(H.MSG_STATE_CHANGED);
341         }
342 
343     }
344 
345     @Override
onBluetoothStateChanged(@dapterState int bluetoothState)346     public void onBluetoothStateChanged(@AdapterState int bluetoothState) {
347         mLogger.logStateChange(BluetoothAdapter.nameForState(bluetoothState));
348         mEnabled = bluetoothState == BluetoothAdapter.STATE_ON
349                 || bluetoothState == BluetoothAdapter.STATE_TURNING_ON;
350         mState = bluetoothState;
351         updateConnected();
352         mHandler.sendEmptyMessage(H.MSG_STATE_CHANGED);
353     }
354 
355     @Override
onDeviceAdded(@onNull CachedBluetoothDevice cachedDevice)356     public void onDeviceAdded(@NonNull CachedBluetoothDevice cachedDevice) {
357         mLogger.logDeviceAdded(cachedDevice.getAddress());
358         cachedDevice.registerCallback(this);
359         updateConnected();
360         mHandler.sendEmptyMessage(H.MSG_PAIRED_DEVICES_CHANGED);
361     }
362 
363     @Override
onDeviceDeleted(@onNull CachedBluetoothDevice cachedDevice)364     public void onDeviceDeleted(@NonNull CachedBluetoothDevice cachedDevice) {
365         mLogger.logDeviceDeleted(cachedDevice.getAddress());
366         updateConnected();
367         mHandler.sendEmptyMessage(H.MSG_PAIRED_DEVICES_CHANGED);
368     }
369 
370     @Override
onDeviceBondStateChanged( @onNull CachedBluetoothDevice cachedDevice, int bondState)371     public void onDeviceBondStateChanged(
372             @NonNull CachedBluetoothDevice cachedDevice, int bondState) {
373         mLogger.logBondStateChange(cachedDevice.getAddress(), bondState);
374         updateConnected();
375         mHandler.sendEmptyMessage(H.MSG_PAIRED_DEVICES_CHANGED);
376     }
377 
378     @Override
onDeviceAttributesChanged()379     public void onDeviceAttributesChanged() {
380         mLogger.logDeviceAttributesChanged();
381         updateConnected();
382         mHandler.sendEmptyMessage(H.MSG_PAIRED_DEVICES_CHANGED);
383     }
384 
385     @Override
onConnectionStateChanged( @ullable CachedBluetoothDevice cachedDevice, @ConnectionState int state)386     public void onConnectionStateChanged(
387             @Nullable CachedBluetoothDevice cachedDevice,
388             @ConnectionState int state) {
389         mLogger.logDeviceConnectionStateChanged(
390                 getAddressOrNull(cachedDevice), connectionStateToString(state));
391         updateConnected();
392         mHandler.sendEmptyMessage(H.MSG_STATE_CHANGED);
393     }
394 
395     @Override
onProfileConnectionStateChanged( @onNull CachedBluetoothDevice cachedDevice, @ConnectionState int state, int bluetoothProfile)396     public void onProfileConnectionStateChanged(
397             @NonNull CachedBluetoothDevice cachedDevice,
398             @ConnectionState int state,
399             int bluetoothProfile) {
400         mLogger.logProfileConnectionStateChanged(
401                 cachedDevice.getAddress(), connectionStateToString(state), bluetoothProfile);
402         updateConnected();
403         mHandler.sendEmptyMessage(H.MSG_STATE_CHANGED);
404     }
405 
406     @Override
onActiveDeviceChanged( @ullable CachedBluetoothDevice activeDevice, int bluetoothProfile)407     public void onActiveDeviceChanged(
408             @Nullable CachedBluetoothDevice activeDevice, int bluetoothProfile) {
409         mLogger.logActiveDeviceChanged(getAddressOrNull(activeDevice), bluetoothProfile);
410         updateActive();
411         mHandler.sendEmptyMessage(H.MSG_STATE_CHANGED);
412     }
413 
414     @Override
onAclConnectionStateChanged( @onNull CachedBluetoothDevice cachedDevice, int state)415     public void onAclConnectionStateChanged(
416             @NonNull CachedBluetoothDevice cachedDevice, int state) {
417         mLogger.logAclConnectionStateChanged(
418                 cachedDevice.getAddress(), connectionStateToString(state));
419         updateConnected();
420         mHandler.sendEmptyMessage(H.MSG_STATE_CHANGED);
421     }
422 
addOnMetadataChangedListener( @onNull CachedBluetoothDevice cachedDevice, Executor executor, BluetoothAdapter.OnMetadataChangedListener listener )423     public void addOnMetadataChangedListener(
424             @NonNull CachedBluetoothDevice cachedDevice,
425             Executor executor,
426             BluetoothAdapter.OnMetadataChangedListener listener
427     ) {
428         if (mAdapter == null) return;
429         mAdapter.addOnMetadataChangedListener(
430                 cachedDevice.getDevice(),
431                 executor,
432                 listener
433         );
434     }
435 
removeOnMetadataChangedListener( @onNull CachedBluetoothDevice cachedDevice, BluetoothAdapter.OnMetadataChangedListener listener )436     public void removeOnMetadataChangedListener(
437             @NonNull CachedBluetoothDevice cachedDevice,
438             BluetoothAdapter.OnMetadataChangedListener listener
439     ) {
440         if (mAdapter == null) return;
441         mAdapter.removeOnMetadataChangedListener(
442                 cachedDevice.getDevice(),
443                 listener
444         );
445     }
446 
447     @Nullable
getAddressOrNull(@ullable CachedBluetoothDevice device)448     private String getAddressOrNull(@Nullable CachedBluetoothDevice device) {
449         return device == null ? null : device.getAddress();
450     }
451 
452     @Override
onServiceConnected()453     public void onServiceConnected() {
454         updateConnected();
455         mHandler.sendEmptyMessage(H.MSG_PAIRED_DEVICES_CHANGED);
456     }
457 
458     @Override
onServiceDisconnected()459     public void onServiceDisconnected() {}
460 
461     private final class H extends Handler {
462         private final ArrayList<BluetoothController.Callback> mCallbacks = new ArrayList<>();
463 
464         private static final int MSG_PAIRED_DEVICES_CHANGED = 1;
465         private static final int MSG_STATE_CHANGED = 2;
466         private static final int MSG_ADD_CALLBACK = 3;
467         private static final int MSG_REMOVE_CALLBACK = 4;
468 
H(Looper looper)469         public H(Looper looper) {
470             super(looper);
471         }
472 
473         @Override
handleMessage(Message msg)474         public void handleMessage(Message msg) {
475             switch (msg.what) {
476                 case MSG_PAIRED_DEVICES_CHANGED:
477                     firePairedDevicesChanged();
478                     break;
479                 case MSG_STATE_CHANGED:
480                     fireStateChange();
481                     break;
482                 case MSG_ADD_CALLBACK:
483                     mCallbacks.add((BluetoothController.Callback) msg.obj);
484                     break;
485                 case MSG_REMOVE_CALLBACK:
486                     mCallbacks.remove((BluetoothController.Callback) msg.obj);
487                     break;
488             }
489         }
490 
firePairedDevicesChanged()491         private void firePairedDevicesChanged() {
492             for (BluetoothController.Callback cb : mCallbacks) {
493                 cb.onBluetoothDevicesChanged();
494             }
495         }
496 
fireStateChange()497         private void fireStateChange() {
498             for (BluetoothController.Callback cb : mCallbacks) {
499                 fireStateChange(cb);
500             }
501         }
502 
fireStateChange(BluetoothController.Callback cb)503         private void fireStateChange(BluetoothController.Callback cb) {
504             cb.onBluetoothStateChange(mEnabled);
505         }
506     }
507 }
508