• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
<lambda>null2  * 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.systemui.statusbar.policy.ui.dialog.viewmodel
18 
19 import android.content.Context
20 import android.content.Intent
21 import android.provider.Settings.ACTION_AUTOMATIC_ZEN_RULE_SETTINGS
22 import android.provider.Settings.EXTRA_AUTOMATIC_ZEN_RULE_ID
23 import com.android.settingslib.notification.modes.ZenMode
24 import com.android.settingslib.notification.modes.ZenModeDescriptions
25 import com.android.systemui.common.shared.model.asIcon
26 import com.android.systemui.dagger.SysUISingleton
27 import com.android.systemui.dagger.qualifiers.Background
28 import com.android.systemui.res.R
29 import com.android.systemui.statusbar.policy.domain.interactor.ZenModeInteractor
30 import com.android.systemui.statusbar.policy.ui.dialog.ModesDialogDelegate
31 import com.android.systemui.statusbar.policy.ui.dialog.ModesDialogEventLogger
32 import javax.inject.Inject
33 import kotlinx.coroutines.CoroutineDispatcher
34 import kotlinx.coroutines.flow.Flow
35 import kotlinx.coroutines.flow.flowOn
36 import kotlinx.coroutines.flow.map
37 import kotlinx.coroutines.flow.scan
38 
39 /**
40  * Viewmodel for the priority ("zen") modes dialog that can be opened from quick settings. It allows
41  * the user to quickly toggle modes.
42  */
43 @SysUISingleton
44 class ModesDialogViewModel
45 @Inject
46 constructor(
47     val context: Context,
48     zenModeInteractor: ZenModeInteractor,
49     @Background val bgDispatcher: CoroutineDispatcher,
50     private val dialogDelegate: ModesDialogDelegate,
51     private val dialogEventLogger: ModesDialogEventLogger,
52 ) {
53     private val zenModeDescriptions = ZenModeDescriptions(context)
54 
55     // Modes that should be displayed in the dialog
56     private val visibleModes: Flow<List<ZenMode>> =
57         zenModeInteractor.modes
58             // While this is being collected (or in other words, while the dialog is open), we don't
59             // want a mode to disappear from the list if, for instance, the user deactivates it,
60             // since that can be confusing (similar to how we have visual stability for
61             // notifications while the shade is open).
62             // This ensures new modes are added to the list, and updates to modes already in the
63             // list are registered correctly.
64             .scan(listOf()) { prev, modes ->
65                 val prevIds = prev.map { it.id }.toSet()
66 
67                 modes.filter { mode ->
68                     when {
69                         // Mode appeared previously -> keep it even if otherwise we may have
70                         // filtered it
71                         mode.id in prevIds -> true
72                         // Mode is enabled -> show if active (so user can toggle off), or if it
73                         // can be manually toggled on
74                         mode.isEnabled -> mode.isActive || mode.isManualInvocationAllowed
75                         // Mode was created as disabled, or disabled by the app that owns it ->
76                         // will be shown with a "Not set" text
77                         !mode.isEnabled -> mode.status == ZenMode.Status.DISABLED_BY_OTHER
78                         else -> false
79                     }
80                 }
81             }
82 
83     val tiles: Flow<List<ModeTileViewModel>> =
84         visibleModes
85             .map { modesList ->
86                 modesList.map { mode ->
87                     ModeTileViewModel(
88                         id = mode.id,
89                         icon = zenModeInteractor.getModeIcon(mode).drawable().asIcon(),
90                         text = mode.name,
91                         subtext = getTileSubtext(mode),
92                         subtextDescription =
93                             getModeDescription(mode, forAccessibility = true) ?: "",
94                         enabled = mode.isActive,
95                         stateDescription =
96                             context.getString(
97                                 if (mode.isActive) R.string.zen_mode_on else R.string.zen_mode_off
98                             ),
99                         onClick = {
100                             if (!mode.isEnabled) {
101                                 openSettings(mode)
102                             } else if (mode.isActive) {
103                                 dialogEventLogger.logModeOff(mode)
104                                 zenModeInteractor.deactivateMode(mode)
105                             } else {
106                                 if (mode.isManualInvocationAllowed) {
107                                     if (zenModeInteractor.shouldAskForZenDuration(mode)) {
108                                         dialogEventLogger.logOpenDurationDialog(mode)
109                                         // NOTE: The dialog handles turning on the mode itself.
110                                         val dialog = dialogDelegate.makeDndDurationDialog()
111                                         dialog.show()
112                                     } else {
113                                         dialogEventLogger.logModeOn(mode)
114                                         zenModeInteractor.activateMode(mode)
115                                     }
116                                 }
117                             }
118                         },
119                         onLongClick = { openSettings(mode) },
120                         onLongClickLabel =
121                             context.resources.getString(R.string.accessibility_long_click_tile),
122                     )
123                 }
124             }
125             .flowOn(bgDispatcher)
126 
127     private fun openSettings(mode: ZenMode) {
128         dialogEventLogger.logModeSettings(mode)
129         val intent: Intent =
130             Intent(ACTION_AUTOMATIC_ZEN_RULE_SETTINGS)
131                 .putExtra(EXTRA_AUTOMATIC_ZEN_RULE_ID, mode.id)
132 
133         dialogDelegate.launchFromDialog(intent)
134     }
135 
136     /**
137      * Returns a description of the mode, which is:
138      * * a prompt to set up the mode if it is not enabled
139      * * if it cannot be manually activated, text that says so
140      * * otherwise, the trigger description of the mode if it exists...
141      * * ...or null if it doesn't
142      *
143      * This description is used directly for the content description of a mode tile for screen
144      * readers, and for the tile subtext will be augmented with the current status of the mode.
145      */
146     private fun getModeDescription(mode: ZenMode, forAccessibility: Boolean): String? {
147         if (!mode.isEnabled) {
148             return context.resources.getString(R.string.zen_mode_set_up)
149         }
150         if (!mode.isManualInvocationAllowed && !mode.isActive) {
151             return context.resources.getString(R.string.zen_mode_no_manual_invocation)
152         }
153         return if (forAccessibility)
154             zenModeDescriptions.getTriggerDescriptionForAccessibility(mode)
155                 ?: zenModeDescriptions.getTriggerDescription(mode)
156         else zenModeDescriptions.getTriggerDescription(mode)
157     }
158 
159     private fun getTileSubtext(mode: ZenMode): String {
160         val modeDescription = getModeDescription(mode, forAccessibility = false)
161         return if (mode.isActive) {
162             if (modeDescription != null) {
163                 context.getString(R.string.zen_mode_on_with_details, modeDescription)
164             } else {
165                 context.getString(R.string.zen_mode_on)
166             }
167         } else {
168             modeDescription ?: context.getString(R.string.zen_mode_off)
169         }
170     }
171 }
172