1 /* 2 * Copyright (C) 2023 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file 5 * except in compliance with the License. You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software distributed under the 10 * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 11 * KIND, either express or implied. See the License for the specific language governing 12 * permissions and limitations under the License. 13 */ 14 15 package com.android.systemui.statusbar.policy.bluetooth 16 17 import android.bluetooth.BluetoothAdapter 18 import android.bluetooth.BluetoothProfile 19 import com.android.settingslib.bluetooth.CachedBluetoothDevice 20 import com.android.settingslib.bluetooth.LocalBluetoothManager 21 import com.android.systemui.dagger.SysUISingleton 22 import com.android.systemui.dagger.qualifiers.Application 23 import com.android.systemui.dagger.qualifiers.Background 24 import javax.inject.Inject 25 import kotlinx.coroutines.CoroutineDispatcher 26 import kotlinx.coroutines.CoroutineScope 27 import kotlinx.coroutines.launch 28 import kotlinx.coroutines.withContext 29 30 /** 31 * Repository for information about bluetooth connections. 32 * 33 * Note: Right now, this class and [BluetoothController] co-exist. Any new code should go in this 34 * implementation, but external clients should query [BluetoothController] instead of this class for 35 * now. 36 */ 37 interface BluetoothRepository { 38 /** 39 * Fetches the connection statuses for the given [currentDevices] and invokes [callback] once 40 * those statuses have been fetched. The fetching occurs on a background thread because IPCs may 41 * be required to fetch the statuses (see b/271058380). 42 */ 43 fun fetchConnectionStatusInBackground( 44 currentDevices: Collection<CachedBluetoothDevice>, 45 callback: ConnectionStatusFetchedCallback, 46 ) 47 } 48 49 /** Implementation of [BluetoothRepository]. */ 50 @SysUISingleton 51 class BluetoothRepositoryImpl 52 @Inject 53 constructor( 54 @Application private val scope: CoroutineScope, 55 @Background private val bgDispatcher: CoroutineDispatcher, 56 private val localBluetoothManager: LocalBluetoothManager?, 57 ) : BluetoothRepository { 58 override fun fetchConnectionStatusInBackground( 59 currentDevices: Collection<CachedBluetoothDevice>, 60 callback: ConnectionStatusFetchedCallback, 61 ) { 62 scope.launch { 63 val result = fetchConnectionStatus(currentDevices) 64 callback.onConnectionStatusFetched(result) 65 } 66 } 67 68 private suspend fun fetchConnectionStatus( 69 currentDevices: Collection<CachedBluetoothDevice>, 70 ): ConnectionStatusModel { 71 return withContext(bgDispatcher) { 72 val minimumMaxConnectionState = 73 localBluetoothManager?.bluetoothAdapter?.connectionState 74 ?: BluetoothProfile.STATE_DISCONNECTED 75 var maxConnectionState = 76 if (currentDevices.isEmpty()) { 77 minimumMaxConnectionState 78 } else { 79 currentDevices 80 .maxOf { it.maxConnectionState } 81 .coerceAtLeast(minimumMaxConnectionState) 82 } 83 84 val connectedDevices = currentDevices.filter { it.isConnected } 85 86 if ( 87 connectedDevices.isEmpty() && maxConnectionState == BluetoothAdapter.STATE_CONNECTED 88 ) { 89 // If somehow we think we are connected, but have no connected devices, we aren't 90 // connected. 91 maxConnectionState = BluetoothAdapter.STATE_DISCONNECTED 92 } 93 94 ConnectionStatusModel(maxConnectionState, connectedDevices) 95 } 96 } 97 } 98 99 data class ConnectionStatusModel( 100 /** The maximum connection state out of all current devices. */ 101 val maxConnectionState: Int, 102 /** A list of devices that are currently connected. */ 103 val connectedDevices: List<CachedBluetoothDevice>, 104 ) 105 106 /** Callback notified when the new status has been fetched. */ 107 fun interface ConnectionStatusFetchedCallback { 108 fun onConnectionStatusFetched(status: ConnectionStatusModel) 109 } 110