1 /*
2 * 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
18
19 import android.content.Context
20 import android.content.pm.ApplicationInfo
21 import android.os.Bundle
22 import androidx.compose.runtime.Composable
23 import androidx.compose.runtime.State
24 import androidx.compose.runtime.derivedStateOf
25 import androidx.compose.runtime.remember
26 import androidx.compose.ui.res.stringResource
27 import com.android.settings.R
28 import com.android.settings.spa.app.appinfo.AppInfoSettingsProvider
29 import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
30 import com.android.settingslib.spa.framework.common.SettingsPageProvider
31 import com.android.settingslib.spa.framework.common.createSettingsPage
32 import com.android.settingslib.spa.framework.compose.navigator
33 import com.android.settingslib.spa.framework.compose.rememberContext
34 import com.android.settingslib.spa.framework.util.filterItem
35 import com.android.settingslib.spa.framework.util.mapItem
36 import com.android.settingslib.spa.widget.preference.Preference
37 import com.android.settingslib.spa.widget.preference.PreferenceModel
38 import com.android.settingslib.spa.widget.ui.SpinnerOption
39 import com.android.settingslib.spaprivileged.model.app.AppListModel
40 import com.android.settingslib.spaprivileged.model.app.AppRecord
41 import com.android.settingslib.spaprivileged.model.app.installed
42 import com.android.settingslib.spaprivileged.template.app.AppList
43 import com.android.settingslib.spaprivileged.template.app.AppListInput
44 import com.android.settingslib.spaprivileged.template.app.AppListItem
45 import com.android.settingslib.spaprivileged.template.app.AppListItemModel
46 import com.android.settingslib.spaprivileged.template.app.AppListPage
47 import com.android.settingslib.spaprivileged.template.app.getStorageSize
48 import kotlinx.coroutines.flow.Flow
49
50 object AllAppListPageProvider : SettingsPageProvider {
51 override val name = "AllAppList"
52 private val owner = createSettingsPage()
53
54 @Composable
Pagenull55 override fun Page(arguments: Bundle?) {
56 AllAppListPage()
57 }
58
buildInjectEntrynull59 fun buildInjectEntry() = SettingsEntryBuilder
60 .createInject(owner)
61 .setSearchDataFn { null }
<lambda>null62 .setUiLayoutFn {
63 Preference(object : PreferenceModel {
64 override val title = stringResource(R.string.all_apps)
65 override val onClick = navigator(name)
66 })
67 }
68 }
69
70 @Composable
AllAppListPagenull71 fun AllAppListPage(
72 appList: @Composable AppListInput<AppRecordWithSize>.() -> Unit = { AppList() },
73 ) {
74 val resetAppDialogPresenter = rememberResetAppDialogPresenter()
75 AppListPage(
76 title = stringResource(R.string.all_apps),
77 listModel = rememberContext(::AllAppListModel),
78 showInstantApps = true,
79 matchAnyUserForAdmin = true,
<lambda>null80 moreOptions = { ResetAppPreferences(resetAppDialogPresenter::open) },
81 appList = appList,
82 )
83 }
84
85 data class AppRecordWithSize(
86 override val app: ApplicationInfo,
87 ) : AppRecord
88
89 class AllAppListModel(
90 private val context: Context,
<lambda>null91 private val getStorageSummary: @Composable ApplicationInfo.() -> State<String> = {
92 getStorageSize()
93 },
94 ) : AppListModel<AppRecordWithSize> {
95
getSpinnerOptionsnull96 override fun getSpinnerOptions(recordList: List<AppRecordWithSize>): List<SpinnerOption> {
97 val hasDisabled = recordList.any(isDisabled)
98 val hasInstant = recordList.any(isInstant)
99 if (!hasDisabled && !hasInstant) return emptyList()
100 val options = mutableListOf(SpinnerItem.All, SpinnerItem.Enabled)
101 if (hasDisabled) options += SpinnerItem.Disabled
102 if (hasInstant) options += SpinnerItem.Instant
103 return options.map {
104 SpinnerOption(
105 id = it.ordinal,
106 text = context.getString(it.stringResId),
107 )
108 }
109 }
110
transformnull111 override fun transform(userIdFlow: Flow<Int>, appListFlow: Flow<List<ApplicationInfo>>) =
112 appListFlow.mapItem(::AppRecordWithSize)
113
114 override fun filter(
115 userIdFlow: Flow<Int>,
116 option: Int,
117 recordListFlow: Flow<List<AppRecordWithSize>>,
118 ): Flow<List<AppRecordWithSize>> = recordListFlow.filterItem(
119 when (SpinnerItem.values().getOrNull(option)) {
120 SpinnerItem.Enabled -> ({ it.app.enabled && !it.app.isInstantApp })
121 SpinnerItem.Disabled -> isDisabled
122 SpinnerItem.Instant -> isInstant
123 else -> ({ true })
124 }
125 )
126
127 private val isDisabled: (AppRecordWithSize) -> Boolean =
<lambda>null128 { !it.app.enabled && !it.app.isInstantApp }
129
<lambda>null130 private val isInstant: (AppRecordWithSize) -> Boolean = { it.app.isInstantApp }
131
132 @Composable
getSummarynull133 override fun getSummary(option: Int, record: AppRecordWithSize): State<String> {
134 val storageSummary = record.app.getStorageSummary()
135 return remember {
136 derivedStateOf {
137 storageSummary.value +
138 when {
139 !record.app.installed -> {
140 System.lineSeparator() + context.getString(R.string.not_installed)
141 }
142 isDisabled(record) -> {
143 System.lineSeparator() + context.getString(R.string.disabled)
144 }
145 else -> ""
146 }
147 }
148 }
149 }
150
151 @Composable
AppItemnull152 override fun AppListItemModel<AppRecordWithSize>.AppItem() {
153 AppListItem(onClick = AppInfoSettingsProvider.navigator(app = record.app))
154 }
155 }
156
157 private enum class SpinnerItem(val stringResId: Int) {
158 All(R.string.filter_all_apps),
159 Enabled(R.string.filter_enabled_apps),
160 Disabled(R.string.filter_apps_disabled),
161 Instant(R.string.filter_instant_apps);
162 }
163