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). However, the callback will be invoked in
42      * the main thread.
43      */
fetchConnectionStatusInBackgroundnull44     fun fetchConnectionStatusInBackground(
45         currentDevices: Collection<CachedBluetoothDevice>,
46         callback: ConnectionStatusFetchedCallback,
47     )
48 }
49 
50 /** Implementation of [BluetoothRepository]. */
51 @SysUISingleton
52 class BluetoothRepositoryImpl
53 @Inject
54 constructor(
55     @Application private val scope: CoroutineScope,
56     @Background private val bgDispatcher: CoroutineDispatcher,
57     private val localBluetoothManager: LocalBluetoothManager?,
58 ) : BluetoothRepository {
59     override fun fetchConnectionStatusInBackground(
60         currentDevices: Collection<CachedBluetoothDevice>,
61         callback: ConnectionStatusFetchedCallback,
62     ) {
63         scope.launch {
64             val result = fetchConnectionStatus(currentDevices)
65             callback.onConnectionStatusFetched(result)
66         }
67     }
68 
69     private suspend fun fetchConnectionStatus(
70         currentDevices: Collection<CachedBluetoothDevice>,
71     ): ConnectionStatusModel {
72         return withContext(bgDispatcher) {
73             val minimumMaxConnectionState =
74                 localBluetoothManager?.bluetoothAdapter?.connectionState
75                     ?: BluetoothProfile.STATE_DISCONNECTED
76             var maxConnectionState =
77                 if (currentDevices.isEmpty()) {
78                     minimumMaxConnectionState
79                 } else {
80                     currentDevices
81                         .maxOf { it.maxConnectionState }
82                         .coerceAtLeast(minimumMaxConnectionState)
83                 }
84 
85             val connectedDevices = currentDevices.filter { it.isConnected }
86 
87             if (
88                 connectedDevices.isEmpty() && maxConnectionState == BluetoothAdapter.STATE_CONNECTED
89             ) {
90                 // If somehow we think we are connected, but have no connected devices, we aren't
91                 // connected.
92                 maxConnectionState = BluetoothAdapter.STATE_DISCONNECTED
93             }
94 
95             ConnectionStatusModel(maxConnectionState, connectedDevices)
96         }
97     }
98 }
99 
100 data class ConnectionStatusModel(
101     /** The maximum connection state out of all current devices. */
102     val maxConnectionState: Int,
103     /** A list of devices that are currently connected. */
104     val connectedDevices: List<CachedBluetoothDevice>,
105 )
106 
107 /** Callback notified when the new status has been fetched. */
interfacenull108 fun interface ConnectionStatusFetchedCallback {
109     fun onConnectionStatusFetched(status: ConnectionStatusModel)
110 }
111