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.systemui.keyguard.data.quickaffordance
18 
19 import android.content.Context
20 import android.content.Intent
21 import android.net.Uri
22 import android.provider.Settings
23 import android.provider.Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS
24 import android.provider.Settings.Global.ZEN_MODE_OFF
25 import android.provider.Settings.Secure.ZEN_DURATION_FOREVER
26 import android.provider.Settings.Secure.ZEN_DURATION_PROMPT
27 import android.service.notification.ZenModeConfig
28 import com.android.settingslib.notification.EnableZenModeDialog
29 import com.android.settingslib.notification.ZenModeDialogMetricsLogger
30 import com.android.systemui.res.R
31 import com.android.systemui.animation.Expandable
32 import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
33 import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
34 import com.android.systemui.common.shared.model.ContentDescription
35 import com.android.systemui.common.shared.model.Icon
36 import com.android.systemui.dagger.SysUISingleton
37 import com.android.systemui.dagger.qualifiers.Background
38 import com.android.systemui.keyguard.shared.quickaffordance.ActivationState
39 import com.android.systemui.settings.UserTracker
40 import com.android.systemui.statusbar.policy.ZenModeController
41 import com.android.systemui.util.settings.SecureSettings
42 import com.android.systemui.util.settings.SettingsProxyExt.observerFlow
43 import javax.inject.Inject
44 import kotlinx.coroutines.CoroutineDispatcher
45 import kotlinx.coroutines.channels.awaitClose
46 import kotlinx.coroutines.flow.Flow
47 import kotlinx.coroutines.flow.combine
48 import kotlinx.coroutines.flow.distinctUntilChanged
49 import kotlinx.coroutines.flow.flowOn
50 import kotlinx.coroutines.flow.map
51 import kotlinx.coroutines.flow.onEach
52 import kotlinx.coroutines.flow.onStart
53 
54 @SysUISingleton
55 class DoNotDisturbQuickAffordanceConfig
56 constructor(
57     private val context: Context,
58     private val controller: ZenModeController,
59     private val secureSettings: SecureSettings,
60     private val userTracker: UserTracker,
61     @Background private val backgroundDispatcher: CoroutineDispatcher,
62     private val testConditionId: Uri?,
63     testDialog: EnableZenModeDialog?,
64 ) : KeyguardQuickAffordanceConfig {
65 
66     @Inject
67     constructor(
68         context: Context,
69         controller: ZenModeController,
70         secureSettings: SecureSettings,
71         userTracker: UserTracker,
72         @Background backgroundDispatcher: CoroutineDispatcher,
73     ) : this(context, controller, secureSettings, userTracker, backgroundDispatcher, null, null)
74 
75     private var dndMode: Int = 0
76     private var isAvailable = false
77     private var settingsValue: Int = 0
78 
79     private val conditionUri: Uri
80         get() =
81             testConditionId
82                 ?: ZenModeConfig.toTimeCondition(
83                         context,
84                         settingsValue,
85                         userTracker.userId,
86                         true, /* shortVersion */
87                     )
88                     .id
89 
90     private val dialog: EnableZenModeDialog by lazy {
91         testDialog
92             ?: EnableZenModeDialog(
93                 context,
94                 R.style.Theme_SystemUI_Dialog,
95                 true, /* cancelIsNeutral */
96                 ZenModeDialogMetricsLogger(context),
97             )
98     }
99 
100     override val key: String = BuiltInKeyguardQuickAffordanceKeys.DO_NOT_DISTURB
101 
102     override fun pickerName(): String = context.getString(R.string.quick_settings_dnd_label)
103 
104     override val pickerIconResourceId: Int = R.drawable.ic_do_not_disturb
105 
106     override val lockScreenState: Flow<KeyguardQuickAffordanceConfig.LockScreenState> =
107         combine(
108             conflatedCallbackFlow {
109                 val callback =
110                     object : ZenModeController.Callback {
111                         override fun onZenChanged(zen: Int) {
112                             dndMode = zen
113                             trySendWithFailureLogging(updateState(), TAG)
114                         }
115 
116                         override fun onZenAvailableChanged(available: Boolean) {
117                             isAvailable = available
118                             trySendWithFailureLogging(updateState(), TAG)
119                         }
120                     }
121 
122                 dndMode = controller.zen
123                 isAvailable = controller.isZenAvailable
124                 trySendWithFailureLogging(updateState(), TAG)
125 
126                 controller.addCallback(callback)
127 
128                 awaitClose { controller.removeCallback(callback) }
129             },
130             secureSettings
131                 .observerFlow(userTracker.userId, Settings.Secure.ZEN_DURATION)
132                 .onStart { emit(Unit) }
133                 .map { secureSettings.getInt(Settings.Secure.ZEN_DURATION, ZEN_MODE_OFF) }
134                 .flowOn(backgroundDispatcher)
135                 .distinctUntilChanged()
136                 .onEach { settingsValue = it }
137         ) { callbackFlowValue, _ ->
138             callbackFlowValue
139         }
140 
141     override suspend fun getPickerScreenState(): KeyguardQuickAffordanceConfig.PickerScreenState {
142         return if (controller.isZenAvailable) {
143             KeyguardQuickAffordanceConfig.PickerScreenState.Default(
144                 configureIntent = Intent(Settings.ACTION_ZEN_MODE_SETTINGS)
145             )
146         } else {
147             KeyguardQuickAffordanceConfig.PickerScreenState.UnavailableOnDevice
148         }
149     }
150 
151     override fun onTriggered(
152         expandable: Expandable?
153     ): KeyguardQuickAffordanceConfig.OnTriggeredResult {
154         return when {
155             !isAvailable -> KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled
156             dndMode != ZEN_MODE_OFF -> {
157                 controller.setZen(ZEN_MODE_OFF, null, TAG)
158                 KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled
159             }
160             settingsValue == ZEN_DURATION_PROMPT ->
161                 KeyguardQuickAffordanceConfig.OnTriggeredResult.ShowDialog(
162                     dialog.createDialog(),
163                     expandable
164                 )
165             settingsValue == ZEN_DURATION_FOREVER -> {
166                 controller.setZen(ZEN_MODE_IMPORTANT_INTERRUPTIONS, null, TAG)
167                 KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled
168             }
169             else -> {
170                 controller.setZen(ZEN_MODE_IMPORTANT_INTERRUPTIONS, conditionUri, TAG)
171                 KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled
172             }
173         }
174     }
175 
176     private fun updateState(): KeyguardQuickAffordanceConfig.LockScreenState {
177         return if (!isAvailable) {
178             KeyguardQuickAffordanceConfig.LockScreenState.Hidden
179         } else if (dndMode == ZEN_MODE_OFF) {
180             KeyguardQuickAffordanceConfig.LockScreenState.Visible(
181                 Icon.Resource(
182                     R.drawable.qs_dnd_icon_off,
183                     ContentDescription.Resource(R.string.dnd_is_off),
184                 ),
185                 ActivationState.Inactive,
186             )
187         } else {
188             KeyguardQuickAffordanceConfig.LockScreenState.Visible(
189                 Icon.Resource(
190                     R.drawable.qs_dnd_icon_on,
191                     ContentDescription.Resource(R.string.dnd_is_on),
192                 ),
193                 ActivationState.Active,
194             )
195         }
196     }
197 
198     companion object {
199         const val TAG = "DoNotDisturbQuickAffordanceConfig"
200     }
201 }
202