1 /*
<lambda>null2  * 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.util.Log
21 import com.android.settingslib.bluetooth.BluetoothCallback
22 import com.android.settingslib.bluetooth.LocalBluetoothManager
23 import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
24 import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
25 import com.android.systemui.dagger.SysUISingleton
26 import com.android.systemui.dagger.qualifiers.Application
27 import com.android.systemui.dagger.qualifiers.Background
28 import javax.inject.Inject
29 import kotlinx.coroutines.CoroutineDispatcher
30 import kotlinx.coroutines.CoroutineScope
31 import kotlinx.coroutines.channels.awaitClose
32 import kotlinx.coroutines.flow.Flow
33 import kotlinx.coroutines.flow.SharingStarted
34 import kotlinx.coroutines.flow.flowOf
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 /**
41  * Repository class responsible for managing the Bluetooth Auto-On feature settings for the current
42  * user.
43  */
44 @SysUISingleton
45 class BluetoothAutoOnRepository
46 @Inject
47 constructor(
48     localBluetoothManager: LocalBluetoothManager?,
49     private val bluetoothAdapter: BluetoothAdapter?,
50     @Application private val coroutineScope: CoroutineScope,
51     @Background private val backgroundDispatcher: CoroutineDispatcher,
52 ) {
53 
54     // Flow representing the auto on state for the current user
55     internal val isAutoOn: Flow<Boolean> =
56         localBluetoothManager?.eventManager?.let { eventManager ->
57             conflatedCallbackFlow {
58                     val listener =
59                         object : BluetoothCallback {
60                             override fun onAutoOnStateChanged(autoOnState: Int) {
61                                 super.onAutoOnStateChanged(autoOnState)
62                                 if (
63                                     autoOnState == BluetoothAdapter.AUTO_ON_STATE_ENABLED ||
64                                         autoOnState == BluetoothAdapter.AUTO_ON_STATE_DISABLED
65                                 ) {
66                                     trySendWithFailureLogging(
67                                         autoOnState == BluetoothAdapter.AUTO_ON_STATE_ENABLED,
68                                         TAG,
69                                         "onAutoOnStateChanged"
70                                     )
71                                 }
72                             }
73                         }
74                     eventManager.registerCallback(listener)
75                     awaitClose { eventManager.unregisterCallback(listener) }
76                 }
77                 .onStart { emit(isAutoOnEnabled()) }
78                 .flowOn(backgroundDispatcher)
79                 .stateIn(
80                     coroutineScope,
81                     SharingStarted.WhileSubscribed(replayExpirationMillis = 0),
82                     initialValue = false
83                 )
84         }
85             ?: flowOf(false)
86 
87     /**
88      * Checks if the auto on feature is supported for the current user.
89      *
90      * @throws Exception if an error occurs while checking auto-on support.
91      */
92     suspend fun isAutoOnSupported(): Boolean =
93         withContext(backgroundDispatcher) {
94             try {
95                 bluetoothAdapter?.isAutoOnSupported ?: false
96             } catch (e: Exception) {
97                 // Server could throw TimeoutException, InterruptedException or ExecutionException
98                 Log.e(TAG, "Error calling isAutoOnSupported", e)
99                 false
100             } catch (e: NoSuchMethodError) {
101                 // TODO(b/346716614): Remove this when the flag is cleaned up.
102                 Log.e(TAG, "Non-existed api isAutoOnSupported", e)
103                 false
104             }
105         }
106 
107     /** Sets the Bluetooth Auto-On for the current user. */
108     suspend fun setAutoOn(value: Boolean) {
109         withContext(backgroundDispatcher) {
110             try {
111                 bluetoothAdapter?.setAutoOnEnabled(value)
112             } catch (e: Exception) {
113                 // Server could throw IllegalStateException, TimeoutException, InterruptedException
114                 // or ExecutionException
115                 Log.e(TAG, "Error calling setAutoOnEnabled", e)
116             } catch (e: NoSuchMethodError) {
117                 // TODO(b/346716614): Remove this when the flag is cleaned up.
118                 Log.e(TAG, "Non-existed api setAutoOn", e)
119             }
120         }
121     }
122 
123     private suspend fun isAutoOnEnabled() =
124         withContext(backgroundDispatcher) {
125             try {
126                 bluetoothAdapter?.isAutoOnEnabled ?: false
127             } catch (e: Exception) {
128                 // Server could throw IllegalStateException, TimeoutException, InterruptedException
129                 // or ExecutionException
130                 Log.e(TAG, "Error calling isAutoOnEnabled", e)
131                 false
132             } catch (e: NoSuchMethodError) {
133                 // TODO(b/346716614): Remove this when the flag is cleaned up.
134                 Log.e(TAG, "Non-existed api isAutoOnEnabled", e)
135                 false
136             }
137         }
138 
139     private companion object {
140         const val TAG = "BluetoothAutoOnRepository"
141     }
142 }
143