• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
<lambda>null2  * Copyright (C) 2025 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.statusbar.pipeline.battery.data.repository
18 
19 import android.content.Context
20 import android.provider.Settings
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 com.android.systemui.shared.settings.data.repository.SystemSettingsRepository
25 import com.android.systemui.statusbar.policy.BatteryController
26 import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
27 import javax.inject.Inject
28 import kotlin.coroutines.resume
29 import kotlin.time.Duration.Companion.minutes
30 import kotlinx.coroutines.CoroutineDispatcher
31 import kotlinx.coroutines.CoroutineScope
32 import kotlinx.coroutines.channels.awaitClose
33 import kotlinx.coroutines.delay
34 import kotlinx.coroutines.flow.Flow
35 import kotlinx.coroutines.flow.SharingStarted
36 import kotlinx.coroutines.flow.StateFlow
37 import kotlinx.coroutines.flow.flow
38 import kotlinx.coroutines.flow.flowOn
39 import kotlinx.coroutines.flow.map
40 import kotlinx.coroutines.flow.scan
41 import kotlinx.coroutines.flow.stateIn
42 import kotlinx.coroutines.suspendCancellableCoroutine
43 
44 /**
45  * Repository-style state for battery information. Currently we just use the [BatteryController] as
46  * our source of truth, but we could (should?) migrate away from that eventually.
47  */
48 @SysUISingleton
49 class BatteryRepository
50 @Inject
51 constructor(
52     @Application context: Context,
53     @Background scope: CoroutineScope,
54     @Background bgDispatcher: CoroutineDispatcher,
55     private val controller: BatteryController,
56     settingsRepository: SystemSettingsRepository,
57 ) {
58     private val batteryState: StateFlow<BatteryCallbackState> =
59         conflatedCallbackFlow<(BatteryCallbackState) -> BatteryCallbackState> {
60                 val callback =
61                     object : BatteryController.BatteryStateChangeCallback {
62                         override fun onBatteryLevelChanged(
63                             level: Int,
64                             pluggedIn: Boolean,
65                             charging: Boolean,
66                         ) {
67                             trySend { prev -> prev.copy(level = level, isPluggedIn = pluggedIn) }
68                         }
69 
70                         override fun onPowerSaveChanged(isPowerSave: Boolean) {
71                             trySend { prev -> prev.copy(isPowerSaveEnabled = isPowerSave) }
72                         }
73 
74                         override fun onIsBatteryDefenderChanged(isBatteryDefender: Boolean) {
75                             trySend { prev ->
76                                 prev.copy(isBatteryDefenderEnabled = isBatteryDefender)
77                             }
78                         }
79 
80                         override fun onBatteryUnknownStateChanged(isUnknown: Boolean) {
81                             // If the state is unknown, then all other fields are invalid
82                             trySend { prev ->
83                                 if (isUnknown) {
84                                     // Forget everything before now
85                                     BatteryCallbackState(isStateUnknown = true)
86                                 } else {
87                                     prev.copy(isStateUnknown = false)
88                                 }
89                             }
90                         }
91                     }
92 
93                 controller.addCallback(callback)
94                 awaitClose { controller.removeCallback(callback) }
95             }
96             .scan(initial = BatteryCallbackState()) { state, eventF -> eventF(state) }
97             .flowOn(bgDispatcher)
98             .stateIn(scope, SharingStarted.Lazily, BatteryCallbackState())
99 
100     /**
101      * True if the phone is plugged in. Note that this does not always mean the device is charging
102      */
103     val isPluggedIn = batteryState.map { it.isPluggedIn }
104 
105     /** Is power saver enabled */
106     val isPowerSaveEnabled = batteryState.map { it.isPowerSaveEnabled }
107 
108     /** Battery defender means the device is plugged in but not charging to protect the battery */
109     val isBatteryDefenderEnabled = batteryState.map { it.isBatteryDefenderEnabled }
110 
111     /** The current level [0-100] */
112     val level = batteryState.map { it.level }
113 
114     /** State unknown means that we can't detect a battery */
115     val isStateUnknown = batteryState.map { it.isStateUnknown }
116 
117     /**
118      * [Settings.System.SHOW_BATTERY_PERCENT]. A user setting to indicate whether we should show the
119      * battery percentage in the home screen status bar
120      */
121     val isShowBatteryPercentSettingEnabled = run {
122         val default =
123             context.resources.getBoolean(
124                 com.android.internal.R.bool.config_defaultBatteryPercentageSetting
125             )
126         settingsRepository
127             .boolSetting(name = Settings.System.SHOW_BATTERY_PERCENT, defaultValue = default)
128             .flowOn(bgDispatcher)
129             .stateIn(scope, SharingStarted.Lazily, default)
130     }
131 
132     /** Get and re-fetch the estimate every 2 minutes while active */
133     private val estimate: Flow<String?> = flow {
134         while (true) {
135             val estimate = fetchEstimate()
136             emit(estimate)
137             delay(2.minutes)
138         }
139     }
140 
141     /**
142      * If available, this flow yields a string that describes the approximate time remaining for the
143      * current battery charge and usage information. While subscribed, the estimate is updated every
144      * 2 minutes.
145      */
146     val batteryTimeRemainingEstimate: Flow<String?> = estimate.flowOn(bgDispatcher)
147 
148     private suspend fun fetchEstimate() = suspendCancellableCoroutine { continuation ->
149         val callback =
150             BatteryController.EstimateFetchCompletion { estimate -> continuation.resume(estimate) }
151 
152         controller.getEstimatedTimeRemainingString(callback)
153     }
154 }
155 
156 /** Data object to track the current battery callback state */
157 private data class BatteryCallbackState(
158     val level: Int? = null,
159     val isPluggedIn: Boolean = false,
160     val isPowerSaveEnabled: Boolean = false,
161     val isBatteryDefenderEnabled: Boolean = false,
162     val isStateUnknown: Boolean = false,
163 )
164