• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2024 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.settingslib.notification.data.repository
18 
19 import android.annotation.SuppressLint
20 import android.app.NotificationManager
21 import android.app.NotificationManager.EXTRA_NOTIFICATION_POLICY
22 import android.content.BroadcastReceiver
23 import android.content.ContentResolver
24 import android.content.Context
25 import android.content.Intent
26 import android.content.IntentFilter
27 import android.database.ContentObserver
28 import android.os.Handler
29 import android.provider.Settings
30 import com.android.settingslib.notification.modes.ZenMode
31 import com.android.settingslib.notification.modes.ZenModesBackend
32 import java.time.Duration
33 import kotlin.coroutines.CoroutineContext
34 import kotlinx.coroutines.CoroutineScope
35 import kotlinx.coroutines.channels.awaitClose
36 import kotlinx.coroutines.flow.Flow
37 import kotlinx.coroutines.flow.MutableStateFlow
38 import kotlinx.coroutines.flow.SharingStarted
39 import kotlinx.coroutines.flow.StateFlow
40 import kotlinx.coroutines.flow.callbackFlow
41 import kotlinx.coroutines.flow.distinctUntilChanged
42 import kotlinx.coroutines.flow.filter
43 import kotlinx.coroutines.flow.flowOf
44 import kotlinx.coroutines.flow.flowOn
45 import kotlinx.coroutines.flow.map
46 import kotlinx.coroutines.flow.onStart
47 import kotlinx.coroutines.flow.shareIn
48 import kotlinx.coroutines.flow.stateIn
49 import kotlinx.coroutines.launch
50 
51 /** Provides state of volume policy and restrictions imposed by notifications. */
52 interface ZenModeRepository {
53     /** @see NotificationManager.getConsolidatedNotificationPolicy */
54     val consolidatedNotificationPolicy: StateFlow<NotificationManager.Policy?>
55 
56     /** @see NotificationManager.getZenMode */
57     val globalZenMode: StateFlow<Int?>
58 
59     /** A list of all existing priority modes. */
60     val modes: Flow<List<ZenMode>>
61 
getModesnull62     fun getModes(): List<ZenMode>
63 
64     fun activateMode(zenMode: ZenMode, duration: Duration? = null)
65 
66     fun deactivateMode(zenMode: ZenMode)
67 }
68 
69 @SuppressLint("SharedFlowCreation")
70 class ZenModeRepositoryImpl(
71     private val context: Context,
72     private val notificationManager: NotificationManager,
73     private val backend: ZenModesBackend,
74     private val contentResolver: ContentResolver,
75     val applicationScope: CoroutineScope,
76     val backgroundCoroutineContext: CoroutineContext,
77     // This is nullable just to simplify testing, since SettingsLib doesn't have a good way
78     // to create a fake handler.
79     val backgroundHandler: Handler?,
80 ) : ZenModeRepository {
81 
82     private val notificationBroadcasts by lazy {
83         callbackFlow {
84                 val receiver =
85                     object : BroadcastReceiver() {
86                         override fun onReceive(context: Context?, intent: Intent?) {
87                             intent?.let { launch { send(it) } }
88                         }
89                     }
90 
91                 context.registerReceiver(
92                     receiver,
93                     IntentFilter().apply {
94                         addAction(NotificationManager.ACTION_INTERRUPTION_FILTER_CHANGED)
95                         addAction(NotificationManager.ACTION_NOTIFICATION_POLICY_CHANGED)
96                         addAction(
97                             NotificationManager.ACTION_CONSOLIDATED_NOTIFICATION_POLICY_CHANGED
98                         )
99                     },
100                     /* broadcastPermission = */ null,
101                     /* scheduler = */ backgroundHandler,
102                 )
103 
104                 awaitClose { context.unregisterReceiver(receiver) }
105             }
106             .flowOn(backgroundCoroutineContext)
107             .shareIn(started = SharingStarted.WhileSubscribed(), scope = applicationScope)
108     }
109 
110     override val consolidatedNotificationPolicy: StateFlow<NotificationManager.Policy?> by lazy {
111         flowFromBroadcast(NotificationManager.ACTION_CONSOLIDATED_NOTIFICATION_POLICY_CHANGED) {
112             // If available, get the value from extras to avoid a potential binder call.
113             it?.extras?.getParcelable(
114                 EXTRA_NOTIFICATION_POLICY,
115                 NotificationManager.Policy::class.java
116             ) ?: notificationManager.consolidatedNotificationPolicy
117         }
118     }
119 
120     override val globalZenMode: StateFlow<Int?> by lazy {
121         flowFromBroadcast(NotificationManager.ACTION_INTERRUPTION_FILTER_CHANGED) {
122             notificationManager.zenMode
123         }
124     }
125 
126     private fun <T> flowFromBroadcast(intentAction: String, mapper: (Intent?) -> T) =
127         notificationBroadcasts
128             .filter { intentAction == it.action }
129             .map { mapper(it) }
130             .onStart { emit(mapper(null)) }
131             .flowOn(backgroundCoroutineContext)
132             .stateIn(applicationScope, SharingStarted.WhileSubscribed(), null)
133 
134     private val zenConfigChanged by lazy {
135         if (android.app.Flags.modesUi()) {
136             callbackFlow {
137                     val observer =
138                         object : ContentObserver(backgroundHandler) {
139                             override fun onChange(selfChange: Boolean) {
140                                 trySend(Unit)
141                             }
142                         }
143 
144                     contentResolver.registerContentObserver(
145                         Settings.Global.getUriFor(Settings.Global.ZEN_MODE),
146                         /* notifyForDescendants= */ false,
147                         observer,
148                     )
149                     contentResolver.registerContentObserver(
150                         Settings.Global.getUriFor(Settings.Global.ZEN_MODE_CONFIG_ETAG),
151                         /* notifyForDescendants= */ false,
152                         observer,
153                     )
154 
155                     awaitClose { contentResolver.unregisterContentObserver(observer) }
156                 }
157                 .flowOn(backgroundCoroutineContext)
158         } else {
159             flowOf(Unit)
160         }
161     }
162 
163     override val modes: StateFlow<List<ZenMode>> =
164         if (android.app.Flags.modesUi())
165             zenConfigChanged
166                 .map { backend.modes }
167                 .distinctUntilChanged()
168                 .flowOn(backgroundCoroutineContext)
169                 .stateIn(
170                     scope = applicationScope,
171                     started = SharingStarted.Eagerly,
172                     initialValue = backend.modes,
173                 )
174         else MutableStateFlow<List<ZenMode>>(emptyList())
175 
176     /**
177      * Gets the current list of [ZenMode] instances according to the backend.
178      *
179      * This is necessary, and cannot be supplanted by making [modes] a StateFlow, because it will be
180      * called whenever we know or suspect that [modes] may not have caught up to the latest data
181      * (such as right after a user switch).
182      */
183     override fun getModes(): List<ZenMode> = backend.modes
184 
185     override fun activateMode(zenMode: ZenMode, duration: Duration?) {
186         backend.activateMode(zenMode, duration)
187     }
188 
189     override fun deactivateMode(zenMode: ZenMode) {
190         backend.deactivateMode(zenMode)
191     }
192 }
193