• 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.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