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