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.settings.spa.app.battery
18
19 import android.app.AppOpsManager
20 import android.content.Context
21 import android.content.pm.ApplicationInfo
22 import android.os.Bundle
23 import androidx.compose.runtime.Composable
24 import androidx.compose.ui.res.stringResource
25 import androidx.core.os.bundleOf
26 import com.android.settings.R
27 import com.android.settings.Utils
28 import com.android.settings.core.SubSettingLauncher
29 import com.android.settings.fuelgauge.AdvancedPowerUsageDetail
30 import com.android.settings.fuelgauge.BatteryOptimizeUtils
31 import com.android.settings.spa.app.AppRecordWithSize
32 import com.android.settings.spa.app.appinfo.AppInfoSettingsProvider
33 import com.android.settings.spa.app.rememberResetAppDialogPresenter
34 import com.android.settingslib.fuelgauge.PowerAllowlistBackend
35 import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
36 import com.android.settingslib.spa.framework.common.SettingsPageProvider
37 import com.android.settingslib.spa.framework.common.createSettingsPage
38 import com.android.settingslib.spa.framework.compose.navigator
39 import com.android.settingslib.spa.framework.compose.rememberContext
40 import com.android.settingslib.spa.framework.util.filterItem
41 import com.android.settingslib.spa.framework.util.mapItem
42 import com.android.settingslib.spa.widget.preference.Preference
43 import com.android.settingslib.spa.widget.preference.PreferenceModel
44 import com.android.settingslib.spa.widget.ui.SpinnerOption
45 import com.android.settingslib.spaprivileged.framework.compose.getPlaceholder
46 import com.android.settingslib.spaprivileged.model.app.AppListModel
47 import com.android.settingslib.spaprivileged.model.app.installed
48 import com.android.settingslib.spaprivileged.model.app.userHandle
49 import com.android.settingslib.spaprivileged.template.app.AppList
50 import com.android.settingslib.spaprivileged.template.app.AppListInput
51 import com.android.settingslib.spaprivileged.template.app.AppListItem
52 import com.android.settingslib.spaprivileged.template.app.AppListItemModel
53 import com.android.settingslib.spaprivileged.template.app.AppListPage
54 import kotlinx.coroutines.flow.Flow
55
56 object BatteryOptimizationModeAppListPageProvider : SettingsPageProvider {
57 override val name = "BatteryOptimizationModeAppList"
58 private val owner = createSettingsPage()
59
60 @Composable
Pagenull61 override fun Page(arguments: Bundle?) {
62 BatteryOptimizationModeAppList()
63 }
64
buildInjectEntrynull65 fun buildInjectEntry() = SettingsEntryBuilder
66 .createInject(owner)
67 .setSearchDataFn { null }
<lambda>null68 .setUiLayoutFn {
69 Preference(object : PreferenceModel {
70 override val title = stringResource(R.string.app_battery_usage_title)
71 override val onClick = navigator(name)
72 })
73 }
74 }
75
76 @Composable
BatteryOptimizationModeAppListnull77 fun BatteryOptimizationModeAppList(
78 appList: @Composable AppListInput<AppRecordWithSize>.() -> Unit = { AppList() },
79 ) {
80 AppListPage(
81 title = stringResource(R.string.app_battery_usage_title),
82 listModel = rememberContext(::BatteryOptimizationModeAppListModel),
83 appList = appList,
84 )
85 }
86
87 class BatteryOptimizationModeAppListModel(
88 private val context: Context,
89 ) : AppListModel<AppRecordWithSize> {
90
getSpinnerOptionsnull91 override fun getSpinnerOptions(recordList: List<AppRecordWithSize>): List<SpinnerOption> =
92 OptimizationModeSpinnerItem.entries.map {
93 SpinnerOption(
94 id = it.ordinal,
95 text = context.getString(it.stringResId),
96 )
97 }
98
transformnull99 override fun transform(userIdFlow: Flow<Int>, appListFlow: Flow<List<ApplicationInfo>>) =
100 appListFlow.mapItem(::AppRecordWithSize)
101
102 override fun filter(
103 userIdFlow: Flow<Int>,
104 option: Int,
105 recordListFlow: Flow<List<AppRecordWithSize>>,
106 ): Flow<List<AppRecordWithSize>> {
107 PowerAllowlistBackend.getInstance(context).refreshList()
108 return recordListFlow.filterItem {
109 val appOptimizationMode = BatteryOptimizeUtils(context, it.app.uid, it.app.packageName)
110 .getAppOptimizationMode(/* refreshList */ false);
111 when (OptimizationModeSpinnerItem.entries.getOrNull(option)) {
112 OptimizationModeSpinnerItem.Restricted ->
113 appOptimizationMode == BatteryOptimizeUtils.MODE_RESTRICTED
114 OptimizationModeSpinnerItem.Optimized ->
115 appOptimizationMode == BatteryOptimizeUtils.MODE_OPTIMIZED
116 OptimizationModeSpinnerItem.Unrestricted ->
117 appOptimizationMode == BatteryOptimizeUtils.MODE_UNRESTRICTED
118 else -> (true)
119 }
120 }
121 }
122
123 @Composable
<lambda>null124 override fun getSummary(option: Int, record: AppRecordWithSize): () -> String = {
125 var summary = String()
126 val app = record.app
127 when {
128 !app.installed && !app.isArchived -> {
129 summary += context.getString(R.string.not_installed)
130 }
131
132 !app.enabled -> {
133 summary += context.getString(com.android.settingslib.R.string.disabled)
134 }
135 }
136 summary
137 }
138
139 @Composable
AppItemnull140 override fun AppListItemModel<AppRecordWithSize>.AppItem() {
141 AppListItem(onClick = {
142 val args = bundleOf(
143 AdvancedPowerUsageDetail.EXTRA_PACKAGE_NAME to record.app.packageName,
144 AdvancedPowerUsageDetail.EXTRA_POWER_USAGE_PERCENT to Utils.formatPercentage(0),
145 AdvancedPowerUsageDetail.EXTRA_UID to record.app.uid,
146 )
147 SubSettingLauncher(context)
148 .setDestination(AdvancedPowerUsageDetail::class.java.name)
149 .setTitleRes(R.string.battery_details_title)
150 .setArguments(args)
151 .setUserHandle(record.app.userHandle)
152 .setSourceMetricsCategory(AppInfoSettingsProvider.METRICS_CATEGORY)
153 .launch()
154 })
155 }
156 }
157
158 private enum class OptimizationModeSpinnerItem(val stringResId: Int) {
159 All(R.string.filter_all_apps),
160 Restricted(R.string.filter_battery_restricted_title),
161 Optimized(R.string.filter_battery_optimized_title),
162 Unrestricted(R.string.filter_battery_unrestricted_title);
163 }
164