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