• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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