1 /*
2  * Copyright (C) 2023 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.storage
18 
19 import android.content.Context
20 import android.content.pm.ApplicationInfo
21 import android.os.Bundle
22 import androidx.annotation.StringRes
23 import androidx.compose.runtime.Composable
24 import androidx.compose.runtime.State
25 import androidx.compose.runtime.getValue
26 import androidx.compose.runtime.remember
27 import androidx.compose.ui.platform.LocalContext
28 import androidx.compose.ui.res.stringResource
29 import com.android.settings.R
30 import com.android.settings.spa.app.appinfo.AppInfoSettingsProvider
31 import com.android.settingslib.spa.framework.common.SettingsPageProvider
32 import com.android.settingslib.spa.framework.util.filterItem
33 import com.android.settingslib.spa.framework.util.mapItem
34 import com.android.settingslib.spaprivileged.model.app.AppEntry
35 import com.android.settingslib.spaprivileged.model.app.AppListModel
36 import com.android.settingslib.spaprivileged.model.app.AppRecord
37 import com.android.settingslib.spaprivileged.template.app.AppList
38 import com.android.settingslib.spaprivileged.template.app.AppListInput
39 import com.android.settingslib.spaprivileged.template.app.AppListItem
40 import com.android.settingslib.spaprivileged.template.app.AppListItemModel
41 import com.android.settingslib.spaprivileged.template.app.AppListPage
42 import com.android.settingslib.spaprivileged.template.app.calculateSizeBytes
43 import com.android.settingslib.spaprivileged.template.app.getStorageSize
44 import kotlinx.coroutines.flow.Flow
45 
46 sealed class StorageAppListPageProvider(private val type: StorageType) : SettingsPageProvider {
47     @Composable
Pagenull48     override fun Page(arguments: Bundle?) {
49         StorageAppListPage(type)
50     }
51 
52     object Apps : StorageAppListPageProvider(StorageType.Apps) {
53         override val name = "StorageAppList"
54     }
55 
56     object Games : StorageAppListPageProvider(StorageType.Games) {
57         override val name = "GameStorageAppList"
58     }
59 }
60 
61 sealed class StorageType(
62     @StringRes val titleResource: Int,
63     val filter: (AppRecordWithSize) -> Boolean
64 ) {
65     object Apps : StorageType(
66         titleResource = R.string.apps_storage,
<lambda>null67         filter = {
68             (it.app.flags and ApplicationInfo.FLAG_IS_GAME) == 0 &&
69             it.app.category != ApplicationInfo.CATEGORY_GAME
70         }
71     )
72     object Games : StorageType(
73         titleResource = R.string.game_storage_settings,
<lambda>null74         filter = {
75             (it.app.flags and ApplicationInfo.FLAG_IS_GAME) != 0 ||
76                 it.app.category == ApplicationInfo.CATEGORY_GAME
77         }
78     )
79 }
80 
81 @Composable
StorageAppListPagenull82 fun StorageAppListPage(
83     type: StorageType,
84     appList: @Composable AppListInput<AppRecordWithSize>.() -> Unit = { AppList() }
85 ) {
86     val context = LocalContext.current
87     AppListPage(
88         title = stringResource(type.titleResource),
89         listModel = when (type) {
<lambda>null90             StorageType.Apps -> remember(context) { StorageAppListModel(context, type) }
<lambda>null91             StorageType.Games -> remember(context) { StorageAppListModel(context, type) }
92         },
93         showInstantApps = true,
94         matchAnyUserForAdmin = true,
95         appList = appList,
<lambda>null96         moreOptions = {  }, // TODO(b/292165031) Sorting in Options not yet supported
97     )
98 }
99 
100 data class AppRecordWithSize(
101     override val app: ApplicationInfo,
102     val size: Long
103 ) : AppRecord
104 
105 class StorageAppListModel(
106     private val context: Context,
107     private val type: StorageType,
<lambda>null108     private val getStorageSummary: @Composable ApplicationInfo.() -> State<String> = {
109         getStorageSize()
110     }
111 ) : AppListModel<AppRecordWithSize> {
transformnull112     override fun transform(userIdFlow: Flow<Int>, appListFlow: Flow<List<ApplicationInfo>>) =
113         appListFlow.mapItem {
114             AppRecordWithSize(it, it.calculateSizeBytes(context) ?: 0L)
115         }
116 
filternull117     override fun filter(
118         userIdFlow: Flow<Int>,
119         option: Int,
120         recordListFlow: Flow<List<AppRecordWithSize>>
121     ): Flow<List<AppRecordWithSize>> = recordListFlow.filterItem { type.filter(it) }
122 
123     @Composable
getSummarynull124     override fun getSummary(option: Int, record: AppRecordWithSize): () -> String {
125         val storageSummary by record.app.getStorageSummary()
126         return { storageSummary }
127     }
128 
129     @Composable
AppItemnull130     override fun AppListItemModel<AppRecordWithSize>.AppItem() {
131         AppListItem(onClick = AppInfoSettingsProvider.navigator(app = record.app))
132     }
133 
<lambda>null134     override fun getComparator(option: Int) = compareByDescending<AppEntry<AppRecordWithSize>> {
135         it.record.size
136     }.then(super.getComparator(option))
137 }
138