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