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