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.notification 18 19 import android.app.settings.SettingsEnums 20 import android.content.Context 21 import android.content.pm.ApplicationInfo 22 import android.icu.text.RelativeDateTimeFormatter 23 import androidx.compose.runtime.Composable 24 import androidx.compose.runtime.livedata.observeAsState 25 import androidx.compose.runtime.produceState 26 import com.android.settings.R 27 import com.android.settings.applications.AppInfoBase 28 import com.android.settings.notification.app.AppNotificationSettings 29 import com.android.settings.spa.notification.SpinnerItem.Companion.toSpinnerItem 30 import com.android.settingslib.spa.framework.compose.stateOf 31 import com.android.settingslib.spa.framework.util.asyncFilter 32 import com.android.settingslib.spa.framework.util.asyncForEach 33 import com.android.settingslib.spa.widget.ui.SpinnerOption 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.AppListItemModel 38 import com.android.settingslib.spaprivileged.template.app.AppListSwitchItem 39 import com.android.settingslib.utils.StringUtil 40 import kotlinx.coroutines.flow.Flow 41 import kotlinx.coroutines.flow.combine 42 import kotlinx.coroutines.flow.map 43 44 data class AppNotificationsRecord( 45 override val app: ApplicationInfo, 46 val sentState: NotificationSentState?, 47 val controller: AppNotificationController, 48 ) : AppRecord 49 50 class AppNotificationsListModel( 51 private val context: Context, 52 ) : AppListModel<AppNotificationsRecord> { 53 private val repository = AppNotificationRepository(context) 54 private val now = System.currentTimeMillis() 55 56 override fun transform( 57 userIdFlow: Flow<Int>, appListFlow: Flow<List<ApplicationInfo>>, 58 ) = repository.getAggregatedUsageEvents(userIdFlow) 59 .combine(appListFlow) { usageEvents, appList -> 60 appList.map { app -> 61 AppNotificationsRecord( 62 app = app, 63 sentState = usageEvents[app.packageName], 64 controller = AppNotificationController(repository, app), 65 ) 66 } 67 } 68 69 override fun filter( 70 userIdFlow: Flow<Int>, option: Int, recordListFlow: Flow<List<AppNotificationsRecord>>, 71 ) = recordListFlow.map { recordList -> 72 recordList.asyncFilter { record -> 73 when (option.toSpinnerItem()) { 74 SpinnerItem.MostRecent -> record.sentState != null 75 SpinnerItem.MostFrequent -> record.sentState != null 76 SpinnerItem.TurnedOff -> !record.controller.getEnabled() 77 else -> true 78 } 79 } 80 } 81 82 override suspend fun onFirstLoaded(recordList: List<AppNotificationsRecord>): Boolean { 83 recordList.asyncForEach { it.controller.getEnabled() } 84 return true 85 } 86 87 override fun getComparator(option: Int) = when (option.toSpinnerItem()) { 88 SpinnerItem.MostRecent -> compareByDescending { it.record.sentState?.lastSent } 89 SpinnerItem.MostFrequent -> compareByDescending { it.record.sentState?.sentCount } 90 else -> compareBy<AppEntry<AppNotificationsRecord>> { 0 } 91 }.then(super.getComparator(option)) 92 93 @Composable 94 override fun getSummary(option: Int, record: AppNotificationsRecord) = record.sentState?.let { 95 when (option.toSpinnerItem()) { 96 SpinnerItem.MostRecent -> stateOf(formatLastSent(it.lastSent)) 97 SpinnerItem.MostFrequent -> stateOf(repository.calculateFrequencySummary(it.sentCount)) 98 else -> null 99 } 100 } 101 102 override fun getSpinnerOptions(recordList: List<AppNotificationsRecord>): List<SpinnerOption> = 103 SpinnerItem.values().map { 104 SpinnerOption( 105 id = it.ordinal, 106 text = context.getString(it.stringResId), 107 ) 108 } 109 110 private fun formatLastSent(lastSent: Long) = 111 StringUtil.formatRelativeTime( 112 context, 113 (now - lastSent).toDouble(), 114 true, 115 RelativeDateTimeFormatter.Style.LONG, 116 ).toString() 117 118 @Composable 119 override fun AppListItemModel<AppNotificationsRecord>.AppItem() { 120 AppListSwitchItem( 121 onClick = { navigateToAppNotificationSettings(app = record.app) }, 122 checked = record.controller.isEnabled.observeAsState(), 123 changeable = produceState(initialValue = false) { 124 value = repository.isChangeable(record.app) 125 }, 126 onCheckedChange = record.controller::setEnabled, 127 ) 128 } 129 130 private fun navigateToAppNotificationSettings(app: ApplicationInfo) { 131 AppInfoBase.startAppInfoFragment( 132 AppNotificationSettings::class.java, 133 context.getString(R.string.notifications_title), 134 app, 135 context, 136 SettingsEnums.MANAGE_APPLICATIONS_NOTIFICATIONS, 137 ) 138 } 139 } 140 141 private enum class SpinnerItem(val stringResId: Int) { 142 MostRecent(R.string.sort_order_recent_notification), 143 MostFrequent(R.string.sort_order_frequent_notification), 144 AllApps(R.string.filter_all_apps), 145 TurnedOff(R.string.filter_notif_blocked_apps); 146 147 companion object { toSpinnerItemnull148 fun Int.toSpinnerItem(): SpinnerItem = values()[this] 149 } 150 } 151