1 /*
2  * Copyright (C) 2024 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.bluetooth.qsdialog
18 
19 import android.bluetooth.BluetoothAdapter
20 import android.bluetooth.BluetoothAdapter.STATE_OFF
21 import android.bluetooth.BluetoothAdapter.STATE_ON
22 import com.android.settingslib.bluetooth.BluetoothCallback
23 import com.android.settingslib.bluetooth.LocalBluetoothManager
24 import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
25 import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
26 import com.android.systemui.dagger.SysUISingleton
27 import com.android.systemui.dagger.qualifiers.Application
28 import com.android.systemui.dagger.qualifiers.Background
29 import javax.inject.Inject
30 import kotlinx.coroutines.CoroutineDispatcher
31 import kotlinx.coroutines.CoroutineScope
32 import kotlinx.coroutines.channels.awaitClose
33 import kotlinx.coroutines.flow.SharingStarted
34 import kotlinx.coroutines.flow.StateFlow
35 import kotlinx.coroutines.flow.flowOn
36 import kotlinx.coroutines.flow.onStart
37 import kotlinx.coroutines.flow.stateIn
38 import kotlinx.coroutines.withContext
39 
40 /** Holds business logic for the Bluetooth Dialog's bluetooth and device connection state */
41 @SysUISingleton
42 internal class BluetoothStateInteractor
43 @Inject
44 constructor(
45     private val localBluetoothManager: LocalBluetoothManager?,
46     private val logger: BluetoothTileDialogLogger,
47     @Application private val coroutineScope: CoroutineScope,
48     @Background private val backgroundDispatcher: CoroutineDispatcher,
49 ) {
50 
51     internal val bluetoothStateUpdate: StateFlow<Boolean> =
<lambda>null52         conflatedCallbackFlow {
53                 val listener =
54                     object : BluetoothCallback {
55                         override fun onBluetoothStateChanged(bluetoothState: Int) {
56                             if (bluetoothState == STATE_ON || bluetoothState == STATE_OFF) {
57                                 super.onBluetoothStateChanged(bluetoothState)
58                                 logger.logBluetoothState(
59                                     BluetoothStateStage.BLUETOOTH_STATE_CHANGE_RECEIVED,
60                                     BluetoothAdapter.nameForState(bluetoothState)
61                                 )
62                                 trySendWithFailureLogging(
63                                     bluetoothState == STATE_ON,
64                                     TAG,
65                                     "onBluetoothStateChanged"
66                                 )
67                             }
68                         }
69                     }
70                 localBluetoothManager?.eventManager?.registerCallback(listener)
71                 awaitClose { localBluetoothManager?.eventManager?.unregisterCallback(listener) }
72             }
<lambda>null73             .onStart { emit(isBluetoothEnabled()) }
74             .flowOn(backgroundDispatcher)
75             .stateIn(
76                 coroutineScope,
77                 SharingStarted.WhileSubscribed(replayExpirationMillis = 0),
78                 initialValue = false
79             )
80 
isBluetoothEnablednull81     suspend fun isBluetoothEnabled(): Boolean =
82         withContext(backgroundDispatcher) {
83             localBluetoothManager?.bluetoothAdapter?.isEnabled == true
84         }
85 
setBluetoothEnablednull86     suspend fun setBluetoothEnabled(value: Boolean) {
87         withContext(backgroundDispatcher) {
88             if (isBluetoothEnabled() != value) {
89                 localBluetoothManager?.bluetoothAdapter?.apply {
90                     if (value) enable() else disable()
91                     logger.logBluetoothState(
92                         BluetoothStateStage.BLUETOOTH_STATE_VALUE_SET,
93                         value.toString()
94                     )
95                 }
96             }
97         }
98     }
99 
100     companion object {
101         private const val TAG = "BtStateInteractor"
102     }
103 }
104