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