1 /* 2 * Copyright (C) 2025 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.qs.tiles 18 19 import android.content.Intent 20 import android.os.Handler 21 import android.os.Looper 22 import androidx.annotation.DrawableRes 23 import androidx.annotation.VisibleForTesting 24 import androidx.lifecycle.Lifecycle 25 import androidx.lifecycle.coroutineScope 26 import androidx.lifecycle.repeatOnLifecycle 27 import com.android.app.tracing.coroutines.launchTraced as launch 28 import com.android.internal.logging.MetricsLogger 29 import com.android.systemui.animation.Expandable 30 import com.android.systemui.dagger.qualifiers.Background 31 import com.android.systemui.dagger.qualifiers.Main 32 import com.android.systemui.modes.shared.ModesUi 33 import com.android.systemui.plugins.ActivityStarter 34 import com.android.systemui.plugins.FalsingManager 35 import com.android.systemui.plugins.qs.QSTile.BooleanState 36 import com.android.systemui.plugins.statusbar.StatusBarStateController 37 import com.android.systemui.qs.QSHost 38 import com.android.systemui.qs.QsEventLogger 39 import com.android.systemui.qs.asQSTileIcon 40 import com.android.systemui.qs.flags.QsInCompose 41 import com.android.systemui.qs.logging.QSLogger 42 import com.android.systemui.qs.tileimpl.QSTileImpl 43 import com.android.systemui.qs.tiles.base.shared.model.QSTileConfigProvider 44 import com.android.systemui.qs.tiles.base.shared.model.QSTileState 45 import com.android.systemui.qs.tiles.impl.modes.domain.interactor.ModesDndTileDataInteractor 46 import com.android.systemui.qs.tiles.impl.modes.domain.interactor.ModesDndTileUserActionInteractor 47 import com.android.systemui.qs.tiles.impl.modes.domain.model.ModesDndTileModel 48 import com.android.systemui.qs.tiles.impl.modes.ui.ModesDndTileMapper 49 import com.android.systemui.res.R 50 import javax.inject.Inject 51 import kotlinx.coroutines.runBlocking 52 53 /** 54 * Standalone tile used to control the DND Mode. Contrast to [ModesTile] (the tile that opens a 55 * dialog showing the list of all modes) and [DndTile] (the tile used to toggle interruption 56 * filtering in the pre-MODES_UI world). 57 */ 58 class ModesDndTile 59 @Inject 60 constructor( 61 host: QSHost, 62 uiEventLogger: QsEventLogger, 63 @Background backgroundLooper: Looper, 64 @Main mainHandler: Handler, 65 falsingManager: FalsingManager, 66 metricsLogger: MetricsLogger, 67 statusBarStateController: StatusBarStateController, 68 activityStarter: ActivityStarter, 69 qsLogger: QSLogger, 70 qsTileConfigProvider: QSTileConfigProvider, 71 private val dataInteractor: ModesDndTileDataInteractor, 72 private val tileMapper: ModesDndTileMapper, 73 private val userActionInteractor: ModesDndTileUserActionInteractor, 74 ) : 75 QSTileImpl<BooleanState>( 76 host, 77 uiEventLogger, 78 backgroundLooper, 79 mainHandler, 80 falsingManager, 81 metricsLogger, 82 statusBarStateController, 83 activityStarter, 84 qsLogger, 85 ) { 86 87 private lateinit var tileState: QSTileState 88 private val config = qsTileConfigProvider.getConfig(TILE_SPEC) 89 90 init { <lambda>null91 lifecycle.coroutineScope.launch { 92 lifecycle.repeatOnLifecycle( 93 // TODO: b/403434908 - Workaround for "not listening to tile updates". Can be reset 94 // to RESUMED if either b/403434908 is fixed or QsInCompose is inlined. 95 if (QsInCompose.isEnabled) Lifecycle.State.RESUMED else Lifecycle.State.CREATED 96 ) { 97 dataInteractor.tileData().collect { refreshState(it) } 98 } 99 } 100 } 101 isAvailablenull102 override fun isAvailable(): Boolean = ModesUi.isEnabled && android.app.Flags.modesUiDndTile() 103 104 override fun getTileLabel(): CharSequence = 105 mContext.getString(R.string.quick_settings_dnd_label) 106 107 override fun newTileState(): BooleanState = BooleanState() 108 109 override fun handleClick(expandable: Expandable?) = runBlocking { 110 userActionInteractor.handleClick() 111 } 112 getLongClickIntentnull113 override fun getLongClickIntent(): Intent? = userActionInteractor.getSettingsIntent() 114 115 @VisibleForTesting 116 public override fun handleUpdateState(state: BooleanState?, arg: Any?) { 117 val model = arg as? ModesDndTileModel ?: dataInteractor.getCurrentTileModel() 118 119 tileState = tileMapper.map(config, model) 120 state?.apply { 121 value = model.isActivated 122 this.state = tileState.activationState.legacyState 123 icon = 124 tileState.icon?.asQSTileIcon() 125 ?: maybeLoadResourceIcon(iconResId(model.isActivated)) 126 label = tileLabel 127 secondaryLabel = tileState.secondaryLabel 128 contentDescription = tileState.contentDescription 129 stateDescription = tileState.stateDescription 130 expandedAccessibilityClassName = tileState.expandedAccessibilityClassName 131 } 132 } 133 134 @DrawableRes iconResIdnull135 private fun iconResId(activated: Boolean): Int = 136 if (activated) R.drawable.qs_dnd_icon_on else R.drawable.qs_dnd_icon_off 137 138 companion object { 139 const val TILE_SPEC = "modes_dnd" 140 } 141 } 142