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