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.systemui.qs.tiles 18 19 import android.content.Intent 20 import android.os.Handler 21 import android.os.Looper 22 import android.service.quicksettings.Tile 23 import androidx.annotation.DrawableRes 24 import androidx.annotation.VisibleForTesting 25 import androidx.lifecycle.Lifecycle 26 import androidx.lifecycle.coroutineScope 27 import androidx.lifecycle.repeatOnLifecycle 28 import com.android.app.tracing.coroutines.launchTraced as launch 29 import com.android.internal.logging.MetricsLogger 30 import com.android.systemui.animation.Expandable 31 import com.android.systemui.dagger.qualifiers.Background 32 import com.android.systemui.dagger.qualifiers.Main 33 import com.android.systemui.modes.shared.ModesUi 34 import com.android.systemui.plugins.ActivityStarter 35 import com.android.systemui.plugins.FalsingManager 36 import com.android.systemui.plugins.qs.QSTile 37 import com.android.systemui.plugins.qs.TileDetailsViewModel 38 import com.android.systemui.plugins.statusbar.StatusBarStateController 39 import com.android.systemui.qs.QSHost 40 import com.android.systemui.qs.QsEventLogger 41 import com.android.systemui.qs.asQSTileIcon 42 import com.android.systemui.qs.flags.QsInCompose 43 import com.android.systemui.qs.logging.QSLogger 44 import com.android.systemui.qs.tileimpl.QSTileImpl 45 import com.android.systemui.qs.tiles.base.shared.model.QSTileConfigProvider 46 import com.android.systemui.qs.tiles.base.shared.model.QSTileState 47 import com.android.systemui.qs.tiles.dialog.ModesDetailsViewModel 48 import com.android.systemui.qs.tiles.impl.modes.domain.interactor.ModesTileDataInteractor 49 import com.android.systemui.qs.tiles.impl.modes.domain.interactor.ModesTileUserActionInteractor 50 import com.android.systemui.qs.tiles.impl.modes.domain.model.ModesTileModel 51 import com.android.systemui.qs.tiles.impl.modes.ui.mapper.ModesTileMapper 52 import com.android.systemui.res.R 53 import com.android.systemui.statusbar.policy.ui.dialog.viewmodel.ModesDialogViewModel 54 import javax.inject.Inject 55 import kotlinx.coroutines.runBlocking 56 57 class ModesTile 58 @Inject 59 constructor( 60 host: QSHost, 61 uiEventLogger: QsEventLogger, 62 @Background backgroundLooper: Looper, 63 @Main mainHandler: Handler, 64 falsingManager: FalsingManager, 65 metricsLogger: MetricsLogger, 66 statusBarStateController: StatusBarStateController, 67 activityStarter: ActivityStarter, 68 qsLogger: QSLogger, 69 qsTileConfigProvider: QSTileConfigProvider, 70 private val dataInteractor: ModesTileDataInteractor, 71 private val tileMapper: ModesTileMapper, 72 private val userActionInteractor: ModesTileUserActionInteractor, 73 private val modesDialogViewModel: ModesDialogViewModel, 74 ) : 75 QSTileImpl<QSTile.State>( 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 103 104 override fun getTileLabel(): CharSequence = tileState.label 105 106 override fun newTileState(): QSTile.State { 107 return QSTile.State().apply { 108 label = mContext.getString(R.string.quick_settings_modes_label) 109 icon = ResourceIcon.get(ICON_RES_ID) 110 state = Tile.STATE_INACTIVE 111 } 112 } 113 <lambda>null114 override fun handleClick(expandable: Expandable?) = runBlocking { 115 userActionInteractor.handleClick(expandable) 116 } 117 <lambda>null118 override fun handleSecondaryClick(expandable: Expandable?) = runBlocking { 119 val model = dataInteractor.getCurrentTileModel() 120 userActionInteractor.handleToggleClick(model) 121 } 122 getDetailsViewModelnull123 override fun getDetailsViewModel(): TileDetailsViewModel { 124 return ModesDetailsViewModel( 125 onSettingsClick = { userActionInteractor.handleLongClick(null) }, 126 viewModel = modesDialogViewModel, 127 ) 128 } 129 getLongClickIntentnull130 override fun getLongClickIntent(): Intent = userActionInteractor.longClickIntent 131 132 @VisibleForTesting 133 public override fun handleUpdateState(state: QSTile.State?, arg: Any?) { 134 // This runBlocking() will block @Background. Due to caches, it's expected to be fast. 135 val model = 136 if (arg is ModesTileModel) arg else runBlocking { dataInteractor.getCurrentTileModel() } 137 138 tileState = tileMapper.map(config, model) 139 state?.apply { 140 this.state = tileState.activationState.legacyState 141 icon = tileState.icon?.asQSTileIcon() ?: maybeLoadResourceIcon(ICON_RES_ID) 142 label = tileLabel 143 secondaryLabel = tileState.secondaryLabel 144 contentDescription = tileState.contentDescription 145 expandedAccessibilityClassName = tileState.expandedAccessibilityClassName 146 handlesSecondaryClick = true 147 } 148 } 149 150 companion object { 151 const val TILE_SPEC = "dnd" 152 @DrawableRes val ICON_RES_ID = com.android.internal.R.drawable.ic_zen_priority_modes 153 } 154 } 155