Loading src/com/android/settings/spa/app/AllAppList.kt +62 −3 Original line number Diff line number Diff line Loading @@ -16,10 +16,12 @@ package com.android.settings.spa.app import android.content.Context import android.content.pm.ApplicationInfo import android.os.Bundle import androidx.compose.runtime.Composable import androidx.compose.runtime.State import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.remember import androidx.compose.ui.res.stringResource import com.android.settings.R Loading @@ -28,9 +30,12 @@ import com.android.settingslib.spa.framework.common.SettingsEntryBuilder import com.android.settingslib.spa.framework.common.SettingsPage import com.android.settingslib.spa.framework.common.SettingsPageProvider import com.android.settingslib.spa.framework.compose.navigator import com.android.settingslib.spa.framework.compose.rememberContext import com.android.settingslib.spa.framework.util.filterItem import com.android.settingslib.spa.framework.util.mapItem import com.android.settingslib.spa.widget.preference.Preference import com.android.settingslib.spa.widget.preference.PreferenceModel import com.android.settingslib.spa.widget.ui.SpinnerOption import com.android.settingslib.spaprivileged.model.app.AppListModel import com.android.settingslib.spaprivileged.model.app.AppRecord import com.android.settingslib.spaprivileged.template.app.AppList Loading Loading @@ -67,7 +72,7 @@ fun AllAppListPage( val resetAppDialogPresenter = rememberResetAppDialogPresenter() AppListPage( title = stringResource(R.string.all_apps), listModel = remember { AllAppListModel() }, listModel = rememberContext(::AllAppListModel), showInstantApps = true, moreOptions = { ResetAppPreferences(resetAppDialogPresenter::open) }, appList = appList, Loading @@ -79,17 +84,71 @@ data class AppRecordWithSize( ) : AppRecord class AllAppListModel( private val getSummary: @Composable ApplicationInfo.() -> State<String> = { getStorageSize() }, private val context: Context, private val getStorageSummary: @Composable ApplicationInfo.() -> State<String> = { getStorageSize() }, ) : AppListModel<AppRecordWithSize> { override fun getSpinnerOptions(recordList: List<AppRecordWithSize>): List<SpinnerOption> { val hasDisabled = recordList.any(isDisabled) val hasInstant = recordList.any(isInstant) if (!hasDisabled && !hasInstant) return emptyList() val options = mutableListOf(SpinnerItem.All, SpinnerItem.Enabled) if (hasDisabled) options += SpinnerItem.Disabled if (hasInstant) options += SpinnerItem.Instant return options.map { SpinnerOption( id = it.ordinal, text = context.getString(it.stringResId), ) } } override fun transform(userIdFlow: Flow<Int>, appListFlow: Flow<List<ApplicationInfo>>) = appListFlow.mapItem(::AppRecordWithSize) override fun filter( userIdFlow: Flow<Int>, option: Int, recordListFlow: Flow<List<AppRecordWithSize>>, ): Flow<List<AppRecordWithSize>> = recordListFlow.filterItem( when (SpinnerItem.values().getOrNull(option)) { SpinnerItem.Enabled -> ({ it.app.enabled && !it.app.isInstantApp }) SpinnerItem.Disabled -> isDisabled SpinnerItem.Instant -> isInstant else -> ({ true }) } ) private val isDisabled: (AppRecordWithSize) -> Boolean = { !it.app.enabled && !it.app.isInstantApp } private val isInstant: (AppRecordWithSize) -> Boolean = { it.app.isInstantApp } @Composable override fun getSummary(option: Int, record: AppRecordWithSize) = record.app.getSummary() override fun getSummary(option: Int, record: AppRecordWithSize): State<String> { val storageSummary = record.app.getStorageSummary() return remember { derivedStateOf { storageSummary.value + when (isDisabled(record)) { true -> System.lineSeparator() + context.getString(R.string.disabled) else -> "" } } } } @Composable override fun AppListItemModel<AppRecordWithSize>.AppItem() { AppListItem(onClick = AppInfoSettingsProvider.navigator(app = record.app)) } } private enum class SpinnerItem(val stringResId: Int) { All(R.string.filter_all_apps), Enabled(R.string.filter_enabled_apps), Disabled(R.string.filter_apps_disabled), Instant(R.string.filter_instant_apps); } src/com/android/settings/spa/development/UsageStatsListModel.kt +10 −4 Original line number Diff line number Diff line Loading @@ -26,6 +26,7 @@ import androidx.compose.runtime.State import com.android.settings.R import com.android.settings.spa.development.UsageStatsListModel.SpinnerItem.Companion.toSpinnerItem import com.android.settingslib.spa.framework.compose.stateOf import com.android.settingslib.spa.widget.ui.SpinnerOption import com.android.settingslib.spaprivileged.model.app.AppEntry import com.android.settingslib.spaprivileged.model.app.AppListModel import com.android.settingslib.spaprivileged.model.app.AppRecord Loading Loading @@ -53,8 +54,12 @@ class UsageStatsListModel(private val context: Context) : AppListModel<UsageStat appList.map { app -> UsageStatsAppRecord(app, usageStatsMap[app.packageName]) } } override fun getSpinnerOptions() = SpinnerItem.values().map { context.getString(it.stringResId) override fun getSpinnerOptions(recordList: List<UsageStatsAppRecord>): List<SpinnerOption> = SpinnerItem.values().map { SpinnerOption( id = it.ordinal, text = context.getString(it.stringResId), ) } override fun filter( Loading @@ -75,7 +80,8 @@ class UsageStatsListModel(private val context: Context) : AppListModel<UsageStat override fun getSummary(option: Int, record: UsageStatsAppRecord): State<String>? { val usageStats = record.usageStats ?: return null val lastTimeUsed = DateUtils.formatSameDayTime( usageStats.lastTimeUsed, now, DateFormat.MEDIUM, DateFormat.MEDIUM) usageStats.lastTimeUsed, now, DateFormat.MEDIUM, DateFormat.MEDIUM ) val lastTimeUsedLine = "${context.getString(R.string.last_time_used_label)}: $lastTimeUsed" val usageTime = DateUtils.formatElapsedTime(usageStats.totalTimeInForeground / 1000) val usageTimeLine = "${context.getString(R.string.usage_time_label)}: $usageTime" Loading src/com/android/settings/spa/notification/AppNotificationsListModel.kt +10 −4 Original line number Diff line number Diff line Loading @@ -30,6 +30,7 @@ import com.android.settings.spa.notification.SpinnerItem.Companion.toSpinnerItem import com.android.settingslib.spa.framework.compose.stateOf import com.android.settingslib.spa.framework.util.asyncFilter import com.android.settingslib.spa.framework.util.asyncForEach import com.android.settingslib.spa.widget.ui.SpinnerOption import com.android.settingslib.spaprivileged.model.app.AppEntry import com.android.settingslib.spaprivileged.model.app.AppListModel import com.android.settingslib.spaprivileged.model.app.AppRecord Loading Loading @@ -78,8 +79,9 @@ class AppNotificationsListModel( } } override suspend fun onFirstLoaded(recordList: List<AppNotificationsRecord>) { override suspend fun onFirstLoaded(recordList: List<AppNotificationsRecord>): Boolean { recordList.asyncForEach { it.controller.getEnabled() } return true } override fun getComparator(option: Int) = when (option.toSpinnerItem()) { Loading @@ -97,8 +99,12 @@ class AppNotificationsListModel( } } override fun getSpinnerOptions() = SpinnerItem.values().map { context.getString(it.stringResId) override fun getSpinnerOptions(recordList: List<AppNotificationsRecord>): List<SpinnerOption> = SpinnerItem.values().map { SpinnerOption( id = it.ordinal, text = context.getString(it.stringResId), ) } private fun formatLastSent(lastSent: Long) = Loading tests/spa_unit/src/com/android/settings/spa/app/AllAppListTest.kt +20 −3 Original line number Diff line number Diff line Loading @@ -121,7 +121,7 @@ class AllAppListTest { @OptIn(ExperimentalCoroutinesApi::class) @Test fun allAppListModel_transform() = runTest { val listModel = AllAppListModel { stateOf(SUMMARY) } val listModel = AllAppListModel(context) { stateOf(SUMMARY) } val recordListFlow = listModel.transform(flowOf(USER_ID), flowOf(listOf(APP))) Loading @@ -132,7 +132,7 @@ class AllAppListTest { @Test fun allAppListModel_getSummary() { val listModel = AllAppListModel { stateOf(SUMMARY) } val listModel = AllAppListModel(context) { stateOf(SUMMARY) } lateinit var summaryState: State<String> composeTestRule.setContent { Loading @@ -142,6 +142,23 @@ class AllAppListTest { assertThat(summaryState.value).isEqualTo(SUMMARY) } @Test fun allAppListModel_getSummaryWhenDisabled() { val listModel = AllAppListModel(context) { stateOf(SUMMARY) } val disabledApp = ApplicationInfo().apply { packageName = PACKAGE_NAME enabled = false } lateinit var summaryState: State<String> composeTestRule.setContent { summaryState = listModel.getSummary(option = 0, record = AppRecordWithSize(app = disabledApp)) } assertThat(summaryState.value).isEqualTo("$SUMMARY${System.lineSeparator()}Disabled") } private fun getAppListInput(): AppListInput<AppRecordWithSize> { lateinit var input: AppListInput<AppRecordWithSize> composeTestRule.setContent { Loading @@ -157,7 +174,7 @@ class AllAppListTest { private fun setItemContent() { composeTestRule.setContent { fakeNavControllerWrapper.Wrapper { with(AllAppListModel()) { with(AllAppListModel(context)) { AppListItemModel( record = AppRecordWithSize(app = APP), label = LABEL, Loading Loading
src/com/android/settings/spa/app/AllAppList.kt +62 −3 Original line number Diff line number Diff line Loading @@ -16,10 +16,12 @@ package com.android.settings.spa.app import android.content.Context import android.content.pm.ApplicationInfo import android.os.Bundle import androidx.compose.runtime.Composable import androidx.compose.runtime.State import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.remember import androidx.compose.ui.res.stringResource import com.android.settings.R Loading @@ -28,9 +30,12 @@ import com.android.settingslib.spa.framework.common.SettingsEntryBuilder import com.android.settingslib.spa.framework.common.SettingsPage import com.android.settingslib.spa.framework.common.SettingsPageProvider import com.android.settingslib.spa.framework.compose.navigator import com.android.settingslib.spa.framework.compose.rememberContext import com.android.settingslib.spa.framework.util.filterItem import com.android.settingslib.spa.framework.util.mapItem import com.android.settingslib.spa.widget.preference.Preference import com.android.settingslib.spa.widget.preference.PreferenceModel import com.android.settingslib.spa.widget.ui.SpinnerOption import com.android.settingslib.spaprivileged.model.app.AppListModel import com.android.settingslib.spaprivileged.model.app.AppRecord import com.android.settingslib.spaprivileged.template.app.AppList Loading Loading @@ -67,7 +72,7 @@ fun AllAppListPage( val resetAppDialogPresenter = rememberResetAppDialogPresenter() AppListPage( title = stringResource(R.string.all_apps), listModel = remember { AllAppListModel() }, listModel = rememberContext(::AllAppListModel), showInstantApps = true, moreOptions = { ResetAppPreferences(resetAppDialogPresenter::open) }, appList = appList, Loading @@ -79,17 +84,71 @@ data class AppRecordWithSize( ) : AppRecord class AllAppListModel( private val getSummary: @Composable ApplicationInfo.() -> State<String> = { getStorageSize() }, private val context: Context, private val getStorageSummary: @Composable ApplicationInfo.() -> State<String> = { getStorageSize() }, ) : AppListModel<AppRecordWithSize> { override fun getSpinnerOptions(recordList: List<AppRecordWithSize>): List<SpinnerOption> { val hasDisabled = recordList.any(isDisabled) val hasInstant = recordList.any(isInstant) if (!hasDisabled && !hasInstant) return emptyList() val options = mutableListOf(SpinnerItem.All, SpinnerItem.Enabled) if (hasDisabled) options += SpinnerItem.Disabled if (hasInstant) options += SpinnerItem.Instant return options.map { SpinnerOption( id = it.ordinal, text = context.getString(it.stringResId), ) } } override fun transform(userIdFlow: Flow<Int>, appListFlow: Flow<List<ApplicationInfo>>) = appListFlow.mapItem(::AppRecordWithSize) override fun filter( userIdFlow: Flow<Int>, option: Int, recordListFlow: Flow<List<AppRecordWithSize>>, ): Flow<List<AppRecordWithSize>> = recordListFlow.filterItem( when (SpinnerItem.values().getOrNull(option)) { SpinnerItem.Enabled -> ({ it.app.enabled && !it.app.isInstantApp }) SpinnerItem.Disabled -> isDisabled SpinnerItem.Instant -> isInstant else -> ({ true }) } ) private val isDisabled: (AppRecordWithSize) -> Boolean = { !it.app.enabled && !it.app.isInstantApp } private val isInstant: (AppRecordWithSize) -> Boolean = { it.app.isInstantApp } @Composable override fun getSummary(option: Int, record: AppRecordWithSize) = record.app.getSummary() override fun getSummary(option: Int, record: AppRecordWithSize): State<String> { val storageSummary = record.app.getStorageSummary() return remember { derivedStateOf { storageSummary.value + when (isDisabled(record)) { true -> System.lineSeparator() + context.getString(R.string.disabled) else -> "" } } } } @Composable override fun AppListItemModel<AppRecordWithSize>.AppItem() { AppListItem(onClick = AppInfoSettingsProvider.navigator(app = record.app)) } } private enum class SpinnerItem(val stringResId: Int) { All(R.string.filter_all_apps), Enabled(R.string.filter_enabled_apps), Disabled(R.string.filter_apps_disabled), Instant(R.string.filter_instant_apps); }
src/com/android/settings/spa/development/UsageStatsListModel.kt +10 −4 Original line number Diff line number Diff line Loading @@ -26,6 +26,7 @@ import androidx.compose.runtime.State import com.android.settings.R import com.android.settings.spa.development.UsageStatsListModel.SpinnerItem.Companion.toSpinnerItem import com.android.settingslib.spa.framework.compose.stateOf import com.android.settingslib.spa.widget.ui.SpinnerOption import com.android.settingslib.spaprivileged.model.app.AppEntry import com.android.settingslib.spaprivileged.model.app.AppListModel import com.android.settingslib.spaprivileged.model.app.AppRecord Loading Loading @@ -53,8 +54,12 @@ class UsageStatsListModel(private val context: Context) : AppListModel<UsageStat appList.map { app -> UsageStatsAppRecord(app, usageStatsMap[app.packageName]) } } override fun getSpinnerOptions() = SpinnerItem.values().map { context.getString(it.stringResId) override fun getSpinnerOptions(recordList: List<UsageStatsAppRecord>): List<SpinnerOption> = SpinnerItem.values().map { SpinnerOption( id = it.ordinal, text = context.getString(it.stringResId), ) } override fun filter( Loading @@ -75,7 +80,8 @@ class UsageStatsListModel(private val context: Context) : AppListModel<UsageStat override fun getSummary(option: Int, record: UsageStatsAppRecord): State<String>? { val usageStats = record.usageStats ?: return null val lastTimeUsed = DateUtils.formatSameDayTime( usageStats.lastTimeUsed, now, DateFormat.MEDIUM, DateFormat.MEDIUM) usageStats.lastTimeUsed, now, DateFormat.MEDIUM, DateFormat.MEDIUM ) val lastTimeUsedLine = "${context.getString(R.string.last_time_used_label)}: $lastTimeUsed" val usageTime = DateUtils.formatElapsedTime(usageStats.totalTimeInForeground / 1000) val usageTimeLine = "${context.getString(R.string.usage_time_label)}: $usageTime" Loading
src/com/android/settings/spa/notification/AppNotificationsListModel.kt +10 −4 Original line number Diff line number Diff line Loading @@ -30,6 +30,7 @@ import com.android.settings.spa.notification.SpinnerItem.Companion.toSpinnerItem import com.android.settingslib.spa.framework.compose.stateOf import com.android.settingslib.spa.framework.util.asyncFilter import com.android.settingslib.spa.framework.util.asyncForEach import com.android.settingslib.spa.widget.ui.SpinnerOption import com.android.settingslib.spaprivileged.model.app.AppEntry import com.android.settingslib.spaprivileged.model.app.AppListModel import com.android.settingslib.spaprivileged.model.app.AppRecord Loading Loading @@ -78,8 +79,9 @@ class AppNotificationsListModel( } } override suspend fun onFirstLoaded(recordList: List<AppNotificationsRecord>) { override suspend fun onFirstLoaded(recordList: List<AppNotificationsRecord>): Boolean { recordList.asyncForEach { it.controller.getEnabled() } return true } override fun getComparator(option: Int) = when (option.toSpinnerItem()) { Loading @@ -97,8 +99,12 @@ class AppNotificationsListModel( } } override fun getSpinnerOptions() = SpinnerItem.values().map { context.getString(it.stringResId) override fun getSpinnerOptions(recordList: List<AppNotificationsRecord>): List<SpinnerOption> = SpinnerItem.values().map { SpinnerOption( id = it.ordinal, text = context.getString(it.stringResId), ) } private fun formatLastSent(lastSent: Long) = Loading
tests/spa_unit/src/com/android/settings/spa/app/AllAppListTest.kt +20 −3 Original line number Diff line number Diff line Loading @@ -121,7 +121,7 @@ class AllAppListTest { @OptIn(ExperimentalCoroutinesApi::class) @Test fun allAppListModel_transform() = runTest { val listModel = AllAppListModel { stateOf(SUMMARY) } val listModel = AllAppListModel(context) { stateOf(SUMMARY) } val recordListFlow = listModel.transform(flowOf(USER_ID), flowOf(listOf(APP))) Loading @@ -132,7 +132,7 @@ class AllAppListTest { @Test fun allAppListModel_getSummary() { val listModel = AllAppListModel { stateOf(SUMMARY) } val listModel = AllAppListModel(context) { stateOf(SUMMARY) } lateinit var summaryState: State<String> composeTestRule.setContent { Loading @@ -142,6 +142,23 @@ class AllAppListTest { assertThat(summaryState.value).isEqualTo(SUMMARY) } @Test fun allAppListModel_getSummaryWhenDisabled() { val listModel = AllAppListModel(context) { stateOf(SUMMARY) } val disabledApp = ApplicationInfo().apply { packageName = PACKAGE_NAME enabled = false } lateinit var summaryState: State<String> composeTestRule.setContent { summaryState = listModel.getSummary(option = 0, record = AppRecordWithSize(app = disabledApp)) } assertThat(summaryState.value).isEqualTo("$SUMMARY${System.lineSeparator()}Disabled") } private fun getAppListInput(): AppListInput<AppRecordWithSize> { lateinit var input: AppListInput<AppRecordWithSize> composeTestRule.setContent { Loading @@ -157,7 +174,7 @@ class AllAppListTest { private fun setItemContent() { composeTestRule.setContent { fakeNavControllerWrapper.Wrapper { with(AllAppListModel()) { with(AllAppListModel(context)) { AppListItemModel( record = AppRecordWithSize(app = APP), label = LABEL, Loading