1 /*
<lambda>null2  * Copyright (C) 2022 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.settings.spa.app.appinfo
18 
19 import android.content.Context
20 import android.content.pm.ApplicationInfo
21 import android.util.Log
22 import androidx.compose.runtime.Composable
23 import androidx.compose.runtime.LaunchedEffect
24 import androidx.compose.runtime.getValue
25 import androidx.compose.runtime.mutableStateOf
26 import androidx.compose.runtime.remember
27 import androidx.compose.runtime.setValue
28 import androidx.compose.ui.platform.LocalContext
29 import androidx.compose.ui.platform.LocalLifecycleOwner
30 import androidx.compose.ui.res.stringResource
31 import androidx.core.os.bundleOf
32 import androidx.lifecycle.Lifecycle
33 import androidx.lifecycle.repeatOnLifecycle
34 import com.android.settings.R
35 import com.android.settings.Utils
36 import com.android.settings.core.SubSettingLauncher
37 import com.android.settings.fuelgauge.AdvancedPowerUsageDetail
38 import com.android.settings.fuelgauge.batteryusage.BatteryChartPreferenceController
39 import com.android.settings.fuelgauge.batteryusage.BatteryDiffEntry
40 import com.android.settingslib.spa.widget.preference.Preference
41 import com.android.settingslib.spa.widget.preference.PreferenceModel
42 import com.android.settingslib.spaprivileged.model.app.installed
43 import com.android.settingslib.spaprivileged.model.app.userHandle
44 import com.android.settingslib.spaprivileged.model.app.userId
45 import kotlinx.coroutines.Dispatchers
46 import kotlinx.coroutines.launch
47 import kotlinx.coroutines.withContext
48 
49 @Composable
50 fun AppBatteryPreference(app: ApplicationInfo) {
51     val context = LocalContext.current
52     val presenter = remember(app) { AppBatteryPresenter(context, app) }
53     if (!presenter.isAvailable()) return
54 
55     Preference(object : PreferenceModel {
56         override val title = stringResource(R.string.battery_details_title)
57         override val summary = presenter.summary
58         override val enabled = presenter.enabled
59         override val onClick = presenter::startActivity
60     })
61 
62     presenter.Updater()
63 }
64 
65 private class AppBatteryPresenter(private val context: Context, private val app: ApplicationInfo) {
66     private var batteryDiffEntryState: LoadingState<BatteryDiffEntry?>
67         by mutableStateOf(LoadingState.Loading)
68 
69     @Composable
<lambda>null70     fun isAvailable() = remember {
71         context.resources.getBoolean(R.bool.config_show_app_info_settings_battery)
72     }
73 
74     @Composable
Updaternull75     fun Updater() {
76         if (!app.installed) return
77         val current = LocalLifecycleOwner.current
78         LaunchedEffect(app) {
79             current.repeatOnLifecycle(Lifecycle.State.STARTED) {
80                 launch { batteryDiffEntryState = LoadingState.Done(getBatteryDiffEntry()) }
81             }
82         }
83     }
84 
<lambda>null85     private suspend fun getBatteryDiffEntry(): BatteryDiffEntry? = withContext(Dispatchers.IO) {
86         BatteryChartPreferenceController.getAppBatteryUsageData(
87             context, app.packageName, app.userId
88         ).also {
89             Log.d(TAG, "loadBatteryDiffEntries():\n$it")
90         }
91     }
92 
<lambda>null93     val enabled = { batteryDiffEntryState is LoadingState.Done }
94 
<lambda>null95     val summary = {
96         if (app.installed) {
97             batteryDiffEntryState.let { batteryDiffEntryState ->
98                 when (batteryDiffEntryState) {
99                     is LoadingState.Loading -> context.getString(R.string.summary_placeholder)
100                     is LoadingState.Done -> batteryDiffEntryState.result.getSummary()
101                 }
102             }
103         } else ""
104     }
105 
BatteryDiffEntrynull106     private fun BatteryDiffEntry?.getSummary(): String =
107         this?.takeIf { mConsumePower > 0 }?.let {
108             context.getString(
109                 R.string.battery_summary, Utils.formatPercentage(percentage, true)
110             )
111         } ?: context.getString(R.string.no_battery_summary)
112 
startActivitynull113     fun startActivity() {
114         batteryDiffEntryState.resultOrNull?.run {
115             startBatteryDetailPage()
116             return
117         }
118 
119         fallbackStartBatteryDetailPage()
120     }
121 
startBatteryDetailPagenull122     private fun BatteryDiffEntry.startBatteryDetailPage() {
123         Log.i(TAG, "handlePreferenceTreeClick():\n$this")
124         AdvancedPowerUsageDetail.startBatteryDetailPage(
125             context,
126             AppInfoSettingsProvider.METRICS_CATEGORY,
127             this,
128             Utils.formatPercentage(percentage, true),
129             /*slotInformation=*/ null,
130             /*showTimeInformation=*/ false,
131             /*anomalyHintPrefKey=*/ null,
132             /*anomalyHintText=*/ null
133         )
134     }
135 
fallbackStartBatteryDetailPagenull136     private fun fallbackStartBatteryDetailPage() {
137         Log.i(TAG, "Launch : ${app.packageName} with package name")
138         val args = bundleOf(
139             AdvancedPowerUsageDetail.EXTRA_PACKAGE_NAME to app.packageName,
140             AdvancedPowerUsageDetail.EXTRA_POWER_USAGE_PERCENT to Utils.formatPercentage(0),
141             AdvancedPowerUsageDetail.EXTRA_UID to app.uid,
142         )
143         SubSettingLauncher(context)
144             .setDestination(AdvancedPowerUsageDetail::class.java.name)
145             .setTitleRes(R.string.battery_details_title)
146             .setArguments(args)
147             .setUserHandle(app.userHandle)
148             .setSourceMetricsCategory(AppInfoSettingsProvider.METRICS_CATEGORY)
149             .launch()
150     }
151 
152     companion object {
153         private const val TAG = "AppBatteryPresenter"
154     }
155 }
156 
157 private sealed class LoadingState<out T> {
158     data object Loading : LoadingState<Nothing>()
159 
160     data class Done<T>(val result: T) : LoadingState<T>()
161 
162     val resultOrNull: T? get() = if (this is Done) result else null
163 }
164