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