1 /* 2 * Copyright (C) 2021 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.ComponentName 20 import android.content.Intent 21 import android.os.Handler 22 import android.os.Looper 23 import android.service.quicksettings.Tile 24 import android.widget.Button 25 import com.android.internal.jank.InteractionJankMonitor 26 import com.android.internal.logging.MetricsLogger 27 import com.android.systemui.animation.Expandable 28 import com.android.systemui.controls.ControlsServiceInfo 29 import com.android.systemui.controls.dagger.ControlsComponent 30 import com.android.systemui.controls.dagger.ControlsComponent.Visibility.AVAILABLE 31 import com.android.systemui.controls.management.ControlsListingController 32 import com.android.systemui.controls.ui.ControlsUiController 33 import com.android.systemui.controls.ui.SelectedItem 34 import com.android.systemui.dagger.qualifiers.Background 35 import com.android.systemui.dagger.qualifiers.Main 36 import com.android.systemui.plugins.ActivityStarter 37 import com.android.systemui.plugins.FalsingManager 38 import com.android.systemui.plugins.qs.QSTile 39 import com.android.systemui.plugins.statusbar.StatusBarStateController 40 import com.android.systemui.qs.QSHost 41 import com.android.systemui.qs.QsEventLogger 42 import com.android.systemui.qs.logging.QSLogger 43 import com.android.systemui.qs.tileimpl.QSTileImpl 44 import com.android.systemui.res.R 45 import java.util.concurrent.atomic.AtomicBoolean 46 import javax.inject.Inject 47 48 class DeviceControlsTile 49 @Inject 50 constructor( 51 host: QSHost, 52 uiEventLogger: QsEventLogger, 53 @Background backgroundLooper: Looper, 54 @Main mainHandler: Handler, 55 falsingManager: FalsingManager, 56 metricsLogger: MetricsLogger, 57 statusBarStateController: StatusBarStateController, 58 activityStarter: ActivityStarter, 59 qsLogger: QSLogger, 60 private val controlsComponent: ControlsComponent, 61 ) : 62 QSTileImpl<QSTile.State>( 63 host, 64 uiEventLogger, 65 backgroundLooper, 66 mainHandler, 67 falsingManager, 68 metricsLogger, 69 statusBarStateController, 70 activityStarter, 71 qsLogger, 72 ) { 73 74 private var hasControlsApps = AtomicBoolean(false) 75 76 private var icon: QSTile.Icon? = null 77 78 private val listingCallback = 79 object : ControlsListingController.ControlsListingCallback { onServicesUpdatednull80 override fun onServicesUpdated(serviceInfos: List<ControlsServiceInfo>) { 81 if ( 82 hasControlsApps.compareAndSet(serviceInfos.isEmpty(), serviceInfos.isNotEmpty()) 83 ) { 84 refreshState() 85 } 86 } 87 } 88 89 init { <lambda>null90 controlsComponent.getControlsListingController().ifPresent { 91 it.observe(this, listingCallback) 92 } 93 } 94 isAvailablenull95 override fun isAvailable(): Boolean { 96 return controlsComponent.getControlsController().isPresent 97 } 98 newTileStatenull99 override fun newTileState(): QSTile.State { 100 return QSTile.State().also { 101 it.state = Tile.STATE_UNAVAILABLE // Start unavailable matching `hasControlsApps` 102 it.handlesLongClick = false 103 } 104 } 105 handleClicknull106 override fun handleClick(expandable: Expandable?) { 107 if (state.state == Tile.STATE_UNAVAILABLE) { 108 return 109 } 110 111 val intent = 112 Intent().apply { 113 component = 114 ComponentName( 115 mContext, 116 controlsComponent.getControlsUiController().get().resolveActivity(), 117 ) 118 addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK) 119 putExtra(ControlsUiController.EXTRA_ANIMATE, true) 120 } 121 val animationController = 122 expandable?.activityTransitionController( 123 InteractionJankMonitor.CUJ_SHADE_APP_LAUNCH_FROM_QS_TILE 124 ) 125 126 mUiHandler.post { 127 val showOverLockscreenWhenLocked = state.state == Tile.STATE_ACTIVE 128 mActivityStarter.startActivity( 129 intent, 130 true /* dismissShade */, 131 animationController, 132 showOverLockscreenWhenLocked, 133 ) 134 } 135 } 136 handleUpdateStatenull137 override fun handleUpdateState(state: QSTile.State, arg: Any?) { 138 state.label = tileLabel 139 state.contentDescription = state.label 140 if (icon == null) { 141 icon = maybeLoadResourceIcon(controlsComponent.getTileImageId()) 142 } 143 state.icon = icon 144 if (controlsComponent.isEnabled() && hasControlsApps.get()) { 145 if (controlsComponent.getVisibility() == AVAILABLE) { 146 val selection = 147 controlsComponent.getControlsController().get().getPreferredSelection() 148 state.state = 149 if ( 150 selection is SelectedItem.StructureItem && 151 selection.structure.controls.isEmpty() 152 ) { 153 Tile.STATE_INACTIVE 154 } else { 155 Tile.STATE_ACTIVE 156 } 157 val label = selection.name 158 state.secondaryLabel = if (label == tileLabel) null else label 159 } else { 160 state.state = Tile.STATE_INACTIVE 161 state.secondaryLabel = mContext.getText(R.string.controls_tile_locked) 162 } 163 state.stateDescription = state.secondaryLabel 164 } else { 165 state.state = Tile.STATE_UNAVAILABLE 166 } 167 state.expandedAccessibilityClassName = Button::class.java.name 168 } 169 getMetricsCategorynull170 override fun getMetricsCategory(): Int { 171 return 0 172 } 173 getLongClickIntentnull174 override fun getLongClickIntent(): Intent? { 175 return null 176 } 177 handleLongClicknull178 override fun handleLongClick(expandable: Expandable?) {} 179 getTileLabelnull180 override fun getTileLabel(): CharSequence { 181 return mContext.getText(controlsComponent.getTileTitleId()) 182 } 183 184 companion object { 185 const val TILE_SPEC = "controls" 186 } 187 } 188