• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
<lambda>null2  * 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.backgroundinstall
18 
19 import android.content.Context
20 import android.content.pm.ApplicationInfo
21 import android.content.pm.IBackgroundInstallControlService
22 import android.content.pm.PackageInfo
23 import android.content.pm.PackageManager
24 import android.content.pm.ParceledListSlice
25 import android.os.Bundle
26 import android.os.ServiceManager
27 import android.provider.DeviceConfig
28 import android.util.Log
29 import androidx.compose.foundation.layout.Box
30 import androidx.compose.foundation.layout.padding
31 import androidx.compose.material.icons.Icons
32 import androidx.compose.material.icons.outlined.Delete
33 import androidx.compose.runtime.Composable
34 import androidx.compose.runtime.State
35 import androidx.compose.runtime.produceState
36 import androidx.compose.ui.Modifier
37 import androidx.compose.ui.platform.LocalContext
38 import androidx.compose.ui.res.stringResource
39 import com.android.settings.R
40 import com.android.settings.spa.app.appinfo.AppInfoSettingsProvider
41 import com.android.settings.spa.app.startUninstallActivity
42 import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
43 import com.android.settingslib.spa.framework.common.SettingsPageProvider
44 import com.android.settingslib.spa.framework.common.createSettingsPage
45 import com.android.settingslib.spa.framework.compose.navigator
46 import com.android.settingslib.spa.framework.compose.rememberContext
47 import com.android.settingslib.spa.framework.theme.SettingsDimension
48 import com.android.settingslib.spa.framework.util.asyncMap
49 import com.android.settingslib.spa.framework.util.formatString
50 import com.android.settingslib.spa.widget.preference.Preference
51 import com.android.settingslib.spa.widget.preference.PreferenceModel
52 import com.android.settingslib.spa.widget.ui.SettingsBody
53 import com.android.settingslib.spaprivileged.model.app.AppEntry
54 import com.android.settingslib.spaprivileged.model.app.AppListModel
55 import com.android.settingslib.spaprivileged.model.app.AppRecord
56 import com.android.settingslib.spaprivileged.model.app.userHandle
57 import com.android.settingslib.spaprivileged.template.app.AppList
58 import com.android.settingslib.spaprivileged.template.app.AppListButtonItem
59 import com.android.settingslib.spaprivileged.template.app.AppListInput
60 import com.android.settingslib.spaprivileged.template.app.AppListItemModel
61 import com.android.settingslib.spaprivileged.template.app.AppListPage
62 import com.google.common.annotations.VisibleForTesting
63 import kotlinx.coroutines.Dispatchers
64 import kotlinx.coroutines.flow.Flow
65 import kotlinx.coroutines.flow.combine
66 import kotlinx.coroutines.flow.flowOf
67 import kotlinx.coroutines.withContext
68 
69 private const val KEY_GROUPING_MONTH = "key_grouping_by_month"
70 const val DEFAULT_GROUPING_MONTH_VALUE = 6
71 const val MONTH_IN_MILLIS = 2629800000L
72 const val KEY_BIC_UI_ENABLED = "key_bic_ui_enabled"
73 const val BACKGROUND_INSTALL_CONTROL_FLAG = PackageManager.MATCH_ALL.toLong()
74 
75 object BackgroundInstalledAppsPageProvider : SettingsPageProvider {
76     override val name = "BackgroundInstalledAppsPage"
77     private val owner = createSettingsPage()
78     private var backgroundInstallService = IBackgroundInstallControlService.Stub.asInterface(
79         ServiceManager.getService(Context.BACKGROUND_INSTALL_CONTROL_SERVICE))
80     private var featureIsDisabled = featureIsDisabled()
81 
82     @Composable
83     override fun Page(arguments: Bundle?) {
84         if(featureIsDisabled) return
85         BackgroundInstalledAppList()
86     }
87 
88     @Composable
89     fun EntryItem() {
90         if(featureIsDisabled) return
91         Preference(object : PreferenceModel {
92             override val title = stringResource(R.string.background_install_title)
93             override val summary = generatePreferenceSummary()
94             override val onClick = navigator(name)
95         })
96     }
97 
98     fun buildInjectEntry() = SettingsEntryBuilder
99         .createInject(owner)
100         .setSearchDataFn { null }
101         .setUiLayoutFn { EntryItem() }
102 
103     private fun featureIsDisabled() = !DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SETTINGS_UI,
104         KEY_BIC_UI_ENABLED, false)
105 
106     @Composable
107     private fun generatePreferenceSummary(): State<String> {
108         val context = LocalContext.current
109         return produceState(initialValue = stringResource(R.string.summary_placeholder)) {
110             withContext(Dispatchers.IO) {
111                 val backgroundInstalledApps =
112                     backgroundInstallService.getBackgroundInstalledPackages(
113                         BACKGROUND_INSTALL_CONTROL_FLAG, context.user.identifier
114                     ).list.size
115                 value = context.formatString(
116                     R.string.background_install_preference_summary,
117                     "count" to backgroundInstalledApps
118                 )
119             }
120         }
121     }
122 
123     @VisibleForTesting
124     fun setDisableFeature(disableFeature : Boolean): BackgroundInstalledAppsPageProvider {
125         featureIsDisabled = disableFeature
126         return this
127     }
128 
129     @VisibleForTesting
130     fun setBackgroundInstallControlService(bic: IBackgroundInstallControlService):
131         BackgroundInstalledAppsPageProvider {
132         backgroundInstallService = bic
133         return this
134     }
135 }
136 
137 @Composable
BackgroundInstalledAppListnull138 fun BackgroundInstalledAppList(
139     appList: @Composable AppListInput<BackgroundInstalledAppListWithGroupingAppRecord>.() -> Unit
140     = { AppList() },
141 ) {
142     AppListPage(
143             title = stringResource(R.string.background_install_title),
144             listModel = rememberContext(::BackgroundInstalledAppsWithGroupingListModel),
145             noItemMessage = stringResource(R.string.background_install_feature_list_no_entry),
146             appList = appList,
<lambda>null147             header = {
148                 Box(Modifier.padding(SettingsDimension.itemPadding)) {
149                     SettingsBody(stringResource(R.string.background_install_summary))
150                 }
151             }
152     )
153 }
154 
155 data class BackgroundInstalledAppListWithGroupingAppRecord(
156     override val app: ApplicationInfo,
157     val dateOfInstall: Long,
158 ) : AppRecord
159 
160 class BackgroundInstalledAppsWithGroupingListModel(private val context: Context)
161     : AppListModel<BackgroundInstalledAppListWithGroupingAppRecord> {
162 
163     companion object {
164         private const val tag = "AppListModel<BackgroundInstalledAppListWithGroupingAppRecord>"
165     }
166 
167     private var backgroundInstallService = IBackgroundInstallControlService.Stub.asInterface(
168         ServiceManager.getService(Context.BACKGROUND_INSTALL_CONTROL_SERVICE))
169 
170     @VisibleForTesting
setBackgroundInstallControlServicenull171     fun setBackgroundInstallControlService(bic: IBackgroundInstallControlService) {
172         backgroundInstallService = bic
173     }
174     @Composable
AppItemnull175     override fun AppListItemModel<BackgroundInstalledAppListWithGroupingAppRecord>.AppItem() {
176         val context = LocalContext.current
177         val app = record.app
178         AppListButtonItem(
179             onClick = AppInfoSettingsProvider.navigator(app = app),
180             onButtonClick = { context.startUninstallActivity(app.packageName, app.userHandle) },
181             buttonIcon = Icons.Outlined.Delete,
182             buttonIconDescription = stringResource(
183                 R.string.background_install_uninstall_button_description))
184     }
185 
transformnull186     override fun transform(userIdFlow: Flow<Int>, appListFlow: Flow<List<ApplicationInfo>>) =
187         userIdFlow.combine(appListFlow) { userId, appList ->
188             appList.asyncMap { app ->
189                 BackgroundInstalledAppListWithGroupingAppRecord(
190                         app = app,
191                         dateOfInstall = context.packageManager.getPackageInfoAsUser(app.packageName,
192                                 PackageManager.PackageInfoFlags.of(0), userId).firstInstallTime
193                 )
194             }
195         }
196 
filternull197     override fun filter(
198             userIdFlow: Flow<Int>,
199             option: Int,
200             recordListFlow: Flow<List<BackgroundInstalledAppListWithGroupingAppRecord>>
201     ): Flow<List<BackgroundInstalledAppListWithGroupingAppRecord>> {
202         if(backgroundInstallService == null) {
203             Log.e(tag, "Failed to retrieve Background Install Control Service")
204             return flowOf()
205         }
206         return userIdFlow.combine(recordListFlow) { userId, recordList ->
207             @Suppress("UNCHECKED_CAST")
208             val appList = (backgroundInstallService.getBackgroundInstalledPackages(
209                 PackageManager.MATCH_ALL.toLong(), userId) as ParceledListSlice<PackageInfo>).list
210             val appNameList = appList.map { it.packageName }
211             recordList.filter { record -> record.app.packageName in appNameList }
212         }
213     }
214 
getComparatornull215     override fun getComparator(
216             option: Int,
217     ): Comparator<AppEntry<BackgroundInstalledAppListWithGroupingAppRecord>> =
218             compareByDescending { it.record.dateOfInstall }
219 
getGroupTitlenull220     override fun getGroupTitle(option: Int, record: BackgroundInstalledAppListWithGroupingAppRecord)
221     : String {
222         val groupByMonth = getGroupSeparationByMonth()
223         return when (record.dateOfInstall > System.currentTimeMillis()
224             - (groupByMonth * MONTH_IN_MILLIS)) {
225             true -> context.formatString(R.string.background_install_before, "count" to groupByMonth)
226             else -> context.formatString(R.string.background_install_after, "count" to groupByMonth)
227         }
228     }
229 }
230 
getGroupSeparationByMonthnull231 private fun getGroupSeparationByMonth(): Int {
232     val month = DeviceConfig.getProperty(DeviceConfig.NAMESPACE_SETTINGS_UI, KEY_GROUPING_MONTH)
233     return try {
234         if (month.isNullOrBlank()) {
235             DEFAULT_GROUPING_MONTH_VALUE
236         } else {
237             month.toInt()
238         }
239     } catch (e: Exception) {
240         Log.d(
241             BackgroundInstalledAppsPageProvider.name, "Error parsing list grouping value: " +
242             "${e.message} falling back to default value: $DEFAULT_GROUPING_MONTH_VALUE")
243         DEFAULT_GROUPING_MONTH_VALUE
244     }
245 }
246