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