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.getValue 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.util.asyncFilter 31 import com.android.settingslib.spa.framework.util.asyncForEach 32 import com.android.settingslib.spa.livedata.observeAsCallback 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.model.app.userId 38 import com.android.settingslib.spaprivileged.template.app.AppListItemModel 39 import com.android.settingslib.spaprivileged.template.app.AppListSwitchItem 40 import com.android.settingslib.spaprivileged.template.app.AppListTwoTargetSwitchItem 41 import com.android.settingslib.utils.StringUtil 42 import kotlinx.coroutines.Dispatchers 43 import kotlinx.coroutines.flow.Flow 44 import kotlinx.coroutines.flow.combine 45 import kotlinx.coroutines.flow.map 46 import kotlinx.coroutines.withContext 47 48 data class AppNotificationsRecord( 49 override val app: ApplicationInfo, 50 val sentState: NotificationSentState?, 51 val controller: AppNotificationController, 52 ) : AppRecord 53 54 class AppNotificationsListModel( 55 private val context: Context, 56 private val listType: ListType 57 ) : AppListModel<AppNotificationsRecord> { 58 private val repository = AppNotificationRepository(context) 59 private val now = System.currentTimeMillis() 60 61 override fun transform( 62 userIdFlow: Flow<Int>, appListFlow: Flow<List<ApplicationInfo>>, 63 ) = repository.getAggregatedUsageEvents(userIdFlow) 64 .combine(appListFlow) { usageEvents, appList -> 65 appList.map { app -> 66 AppNotificationsRecord( 67 app = app, 68 sentState = usageEvents[app.packageName], 69 controller = AppNotificationController(repository, app, listType), 70 ) 71 } 72 } 73 74 override fun filter( 75 userIdFlow: Flow<Int>, option: Int, recordListFlow: Flow<List<AppNotificationsRecord>>, 76 ) = recordListFlow.map { recordList -> 77 recordList.asyncFilter { record -> 78 when (option.toSpinnerItem()) { 79 SpinnerItem.MostRecent -> record.sentState != null 80 SpinnerItem.MostFrequent -> record.sentState != null 81 SpinnerItem.TurnedOff -> !record.controller.getEnabled() 82 else -> true 83 } 84 } 85 } 86 87 override suspend fun onFirstLoaded(recordList: List<AppNotificationsRecord>): Boolean { 88 recordList.asyncForEach { it.controller.getEnabled() } 89 return true 90 } 91 92 override fun getComparator(option: Int) = when (option.toSpinnerItem()) { 93 SpinnerItem.MostRecent -> compareByDescending { it.record.sentState?.lastSent } 94 SpinnerItem.MostFrequent -> compareByDescending { it.record.sentState?.sentCount } 95 else -> compareBy<AppEntry<AppNotificationsRecord>> { 0 } 96 }.then(super.getComparator(option)) 97 98 @Composable 99 override fun getSummary(option: Int, record: AppNotificationsRecord): (() -> String)? = 100 record.sentState?.let { 101 when (option.toSpinnerItem()) { 102 SpinnerItem.MostRecent -> ({ formatLastSent(it.lastSent) }) 103 SpinnerItem.MostFrequent -> ({ repository.calculateFrequencySummary(it.sentCount) }) 104 else -> null 105 } 106 } 107 108 override fun getSpinnerOptions(recordList: List<AppNotificationsRecord>): List<SpinnerOption> { 109 val options = mutableListOf(SpinnerItem.AllApps, SpinnerItem.TurnedOff) 110 if (recordList.isNotEmpty() && repository.isUserUnlocked(recordList[0].app.userId)) { 111 options.add(0, SpinnerItem.MostRecent) 112 options.add(1, SpinnerItem.MostFrequent) 113 } 114 115 return options.map { 116 SpinnerOption( 117 id = it.ordinal, 118 text = context.getString(it.stringResId), 119 ) 120 } 121 } 122 123 124 private fun formatLastSent(lastSent: Long) = 125 StringUtil.formatRelativeTime( 126 context, 127 (now - lastSent).toDouble(), 128 true, 129 RelativeDateTimeFormatter.Style.LONG, 130 ).toString() 131 132 @Composable 133 override fun AppListItemModel<AppNotificationsRecord>.AppItem() { 134 when (listType) { 135 ListType.ExcludeSummarization -> { 136 AppListSwitchItem( 137 checked = record.controller.isAllowed.observeAsCallback(), 138 changeable = { true }, 139 onCheckedChange = record.controller::setAllowed, 140 ) 141 } 142 ListType.ExcludeClassification -> { 143 AppListSwitchItem( 144 checked = record.controller.isAllowed.observeAsCallback(), 145 changeable = { true }, 146 onCheckedChange = record.controller::setAllowed, 147 ) 148 } 149 else -> { 150 val changeable by produceState(initialValue = false) { 151 withContext(Dispatchers.Default) { 152 value = repository.isChangeable(record.app) 153 } 154 } 155 AppListTwoTargetSwitchItem( 156 onClick = { navigateToAppNotificationSettings(app = record.app) }, 157 checked = record.controller.isEnabled.observeAsCallback(), 158 changeable = { changeable }, 159 onCheckedChange = record.controller::setEnabled, 160 ) 161 } 162 } 163 } 164 165 private fun navigateToAppNotificationSettings(app: ApplicationInfo) { 166 AppInfoBase.startAppInfoFragment( 167 AppNotificationSettings::class.java, 168 context.getString(R.string.notifications_title), 169 app, 170 context, 171 SettingsEnums.MANAGE_APPLICATIONS_NOTIFICATIONS, 172 ) 173 } 174 } 175 176 private enum class SpinnerItem(val stringResId: Int) { 177 MostRecent(R.string.sort_order_recent_notification), 178 MostFrequent(R.string.sort_order_frequent_notification), 179 AllApps(R.string.filter_all_apps), 180 TurnedOff(R.string.filter_notif_blocked_apps); 181 182 companion object { toSpinnerItemnull183 fun Int.toSpinnerItem(): SpinnerItem = entries[this] 184 } 185 } 186