From 74b3bfc066e6314783c13504a7e39c84f0fd329d Mon Sep 17 00:00:00 2001 From: Chaohui Wang Date: Tue, 3 Jan 2023 17:57:12 +0800 Subject: [PATCH] Add spinner options to "All apps" Spinner options only shows when there is any disabled or instant apps. Fix: 264228237 Test: Manually with Settings Test: Unit test Change-Id: I9f916c1076b2d754b8c5f30244eac5d5c0bc78f4 --- .../android/settings/spa/app/AllAppList.kt | 65 ++++++++++++++++++- .../spa/development/UsageStatsListModel.kt | 14 ++-- .../notification/AppNotificationsListModel.kt | 14 ++-- .../settings/spa/app/AllAppListTest.kt | 23 ++++++- 4 files changed, 102 insertions(+), 14 deletions(-) diff --git a/src/com/android/settings/spa/app/AllAppList.kt b/src/com/android/settings/spa/app/AllAppList.kt index 05f77ed05f7..08b06a6f5e9 100644 --- a/src/com/android/settings/spa/app/AllAppList.kt +++ b/src/com/android/settings/spa/app/AllAppList.kt @@ -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 @@ -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 @@ -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, @@ -79,17 +84,71 @@ data class AppRecordWithSize( ) : AppRecord class AllAppListModel( - private val getSummary: @Composable ApplicationInfo.() -> State = { getStorageSize() }, + private val context: Context, + private val getStorageSummary: @Composable ApplicationInfo.() -> State = { + getStorageSize() + }, ) : AppListModel { + override fun getSpinnerOptions(recordList: List): List { + 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, appListFlow: Flow>) = appListFlow.mapItem(::AppRecordWithSize) + override fun filter( + userIdFlow: Flow, + option: Int, + recordListFlow: Flow>, + ): Flow> = 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 { + 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.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); +} diff --git a/src/com/android/settings/spa/development/UsageStatsListModel.kt b/src/com/android/settings/spa/development/UsageStatsListModel.kt index caa30cc2535..61c24aca853 100644 --- a/src/com/android/settings/spa/development/UsageStatsListModel.kt +++ b/src/com/android/settings/spa/development/UsageStatsListModel.kt @@ -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 @@ -53,9 +54,13 @@ class UsageStatsListModel(private val context: Context) : AppListModel UsageStatsAppRecord(app, usageStatsMap[app.packageName]) } } - override fun getSpinnerOptions() = SpinnerItem.values().map { - context.getString(it.stringResId) - } + override fun getSpinnerOptions(recordList: List): List = + SpinnerItem.values().map { + SpinnerOption( + id = it.ordinal, + text = context.getString(it.stringResId), + ) + } override fun filter( userIdFlow: Flow, @@ -75,7 +80,8 @@ class UsageStatsListModel(private val context: Context) : AppListModel? { 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" diff --git a/src/com/android/settings/spa/notification/AppNotificationsListModel.kt b/src/com/android/settings/spa/notification/AppNotificationsListModel.kt index 29c8a2bc849..028b2f4aaf9 100644 --- a/src/com/android/settings/spa/notification/AppNotificationsListModel.kt +++ b/src/com/android/settings/spa/notification/AppNotificationsListModel.kt @@ -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 @@ -78,8 +79,9 @@ class AppNotificationsListModel( } } - override suspend fun onFirstLoaded(recordList: List) { + override suspend fun onFirstLoaded(recordList: List): Boolean { recordList.asyncForEach { it.controller.getEnabled() } + return true } override fun getComparator(option: Int) = when (option.toSpinnerItem()) { @@ -97,9 +99,13 @@ class AppNotificationsListModel( } } - override fun getSpinnerOptions() = SpinnerItem.values().map { - context.getString(it.stringResId) - } + override fun getSpinnerOptions(recordList: List): List = + SpinnerItem.values().map { + SpinnerOption( + id = it.ordinal, + text = context.getString(it.stringResId), + ) + } private fun formatLastSent(lastSent: Long) = StringUtil.formatRelativeTime( diff --git a/tests/spa_unit/src/com/android/settings/spa/app/AllAppListTest.kt b/tests/spa_unit/src/com/android/settings/spa/app/AllAppListTest.kt index b76791298fd..b5dfddcb5bd 100644 --- a/tests/spa_unit/src/com/android/settings/spa/app/AllAppListTest.kt +++ b/tests/spa_unit/src/com/android/settings/spa/app/AllAppListTest.kt @@ -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))) @@ -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 composeTestRule.setContent { @@ -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 + composeTestRule.setContent { + summaryState = + listModel.getSummary(option = 0, record = AppRecordWithSize(app = disabledApp)) + } + + assertThat(summaryState.value).isEqualTo("$SUMMARY${System.lineSeparator()}Disabled") + } + private fun getAppListInput(): AppListInput { lateinit var input: AppListInput composeTestRule.setContent { @@ -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,