Merge "Add spinner options to "All apps""
diff --git a/src/com/android/settings/spa/app/AllAppList.kt b/src/com/android/settings/spa/app/AllAppList.kt
index 05f77ed..08b06a6 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.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 @@
     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 @@
 ) : 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);
+}
diff --git a/src/com/android/settings/spa/development/UsageStatsListModel.kt b/src/com/android/settings/spa/development/UsageStatsListModel.kt
index caa30cc..61c24ac 100644
--- a/src/com/android/settings/spa/development/UsageStatsListModel.kt
+++ b/src/com/android/settings/spa/development/UsageStatsListModel.kt
@@ -26,6 +26,7 @@
 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 @@
             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(
         userIdFlow: Flow<Int>,
@@ -75,7 +80,8 @@
     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"
diff --git a/src/com/android/settings/spa/notification/AppNotificationsListModel.kt b/src/com/android/settings/spa/notification/AppNotificationsListModel.kt
index 29c8a2b..028b2f4 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.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 @@
         }
     }
 
-    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()) {
@@ -97,9 +99,13 @@
         }
     }
 
-    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) =
         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 b767912..b5dfddc 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 @@
     @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 @@
 
     @Test
     fun allAppListModel_getSummary() {
-        val listModel = AllAppListModel { stateOf(SUMMARY) }
+        val listModel = AllAppListModel(context) { stateOf(SUMMARY) }
 
         lateinit var summaryState: State<String>
         composeTestRule.setContent {
@@ -142,6 +142,23 @@
         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 {
@@ -157,7 +174,7 @@
     private fun setItemContent() {
         composeTestRule.setContent {
             fakeNavControllerWrapper.Wrapper {
-                with(AllAppListModel()) {
+                with(AllAppListModel(context)) {
                     AppListItemModel(
                         record = AppRecordWithSize(app = APP),
                         label = LABEL,