1 2 /* 3 * Copyright (C) 2021 The Android Open Source Project 4 * 5 * Licensed under the Apache License, Version 2.0 (the "License"); 6 * you may not use this file except in compliance with the License. 7 * You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18 package com.android.systemui.qs.tiles 19 20 import android.content.ComponentName 21 import android.content.Intent 22 import android.os.Handler 23 import android.os.Looper 24 import android.service.quicksettings.Tile 25 import androidx.annotation.VisibleForTesting 26 import com.android.internal.jank.InteractionJankMonitor 27 import com.android.internal.logging.MetricsLogger 28 import com.android.systemui.res.R 29 import com.android.systemui.animation.Expandable 30 import com.android.systemui.controls.ControlsServiceInfo 31 import com.android.systemui.controls.dagger.ControlsComponent 32 import com.android.systemui.controls.dagger.ControlsComponent.Visibility.AVAILABLE 33 import com.android.systemui.controls.management.ControlsListingController 34 import com.android.systemui.controls.ui.ControlsUiController 35 import com.android.systemui.controls.ui.SelectedItem 36 import com.android.systemui.dagger.qualifiers.Background 37 import com.android.systemui.dagger.qualifiers.Main 38 import com.android.systemui.plugins.ActivityStarter 39 import com.android.systemui.plugins.FalsingManager 40 import com.android.systemui.plugins.qs.QSTile 41 import com.android.systemui.plugins.statusbar.StatusBarStateController 42 import com.android.systemui.qs.QSHost 43 import com.android.systemui.qs.QsEventLogger 44 import com.android.systemui.qs.logging.QSLogger 45 import com.android.systemui.qs.tileimpl.QSTileImpl 46 import java.util.concurrent.atomic.AtomicBoolean 47 import javax.inject.Inject 48 49 class DeviceControlsTile @Inject constructor( 50 host: QSHost, 51 uiEventLogger: QsEventLogger, 52 @Background backgroundLooper: Looper, 53 @Main mainHandler: Handler, 54 falsingManager: FalsingManager, 55 metricsLogger: MetricsLogger, 56 statusBarStateController: StatusBarStateController, 57 activityStarter: ActivityStarter, 58 qsLogger: QSLogger, 59 private val controlsComponent: ControlsComponent 60 ) : QSTileImpl<QSTile.State>( 61 host, 62 uiEventLogger, 63 backgroundLooper, 64 mainHandler, 65 falsingManager, 66 metricsLogger, 67 statusBarStateController, 68 activityStarter, 69 qsLogger 70 ) { 71 72 private var hasControlsApps = AtomicBoolean(false) 73 74 @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) 75 val icon: QSTile.Icon 76 get() = ResourceIcon.get(controlsComponent.getTileImageId()) 77 78 private val listingCallback = object : ControlsListingController.ControlsListingCallback { onServicesUpdatednull79 override fun onServicesUpdated(serviceInfos: List<ControlsServiceInfo>) { 80 if (hasControlsApps.compareAndSet(serviceInfos.isEmpty(), serviceInfos.isNotEmpty())) { 81 refreshState() 82 } 83 } 84 } 85 86 init { <lambda>null87 controlsComponent.getControlsListingController().ifPresent { 88 it.observe(this, listingCallback) 89 } 90 } 91 isAvailablenull92 override fun isAvailable(): Boolean { 93 return controlsComponent.getControlsController().isPresent 94 } 95 newTileStatenull96 override fun newTileState(): QSTile.State { 97 return QSTile.State().also { 98 it.state = Tile.STATE_UNAVAILABLE // Start unavailable matching `hasControlsApps` 99 it.handlesLongClick = false 100 } 101 } 102 handleClicknull103 override fun handleClick(expandable: Expandable?) { 104 if (state.state == Tile.STATE_UNAVAILABLE) { 105 return 106 } 107 108 val intent = Intent().apply { 109 component = ComponentName(mContext, controlsComponent.getControlsUiController().get() 110 .resolveActivity()) 111 addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK) 112 putExtra(ControlsUiController.EXTRA_ANIMATE, true) 113 } 114 val animationController = 115 expandable?.activityTransitionController( 116 InteractionJankMonitor.CUJ_SHADE_APP_LAUNCH_FROM_QS_TILE 117 ) 118 119 mUiHandler.post { 120 val showOverLockscreenWhenLocked = state.state == Tile.STATE_ACTIVE 121 mActivityStarter.startActivity( 122 intent, 123 true /* dismissShade */, 124 animationController, 125 showOverLockscreenWhenLocked, 126 ) 127 } 128 } 129 handleUpdateStatenull130 override fun handleUpdateState(state: QSTile.State, arg: Any?) { 131 state.label = tileLabel 132 state.contentDescription = state.label 133 state.icon = icon 134 if (controlsComponent.isEnabled() && hasControlsApps.get()) { 135 if (controlsComponent.getVisibility() == AVAILABLE) { 136 val selection = controlsComponent 137 .getControlsController().get().getPreferredSelection() 138 state.state = if (selection is SelectedItem.StructureItem && 139 selection.structure.controls.isEmpty()) { 140 Tile.STATE_INACTIVE 141 } else { 142 Tile.STATE_ACTIVE 143 } 144 val label = selection.name 145 state.secondaryLabel = if (label == tileLabel) null else label 146 } else { 147 state.state = Tile.STATE_INACTIVE 148 state.secondaryLabel = mContext.getText(R.string.controls_tile_locked) 149 } 150 state.stateDescription = state.secondaryLabel 151 } else { 152 state.state = Tile.STATE_UNAVAILABLE 153 } 154 } 155 getMetricsCategorynull156 override fun getMetricsCategory(): Int { 157 return 0 158 } 159 getLongClickIntentnull160 override fun getLongClickIntent(): Intent? { 161 return null 162 } 163 handleLongClicknull164 override fun handleLongClick(expandable: Expandable?) {} 165 getTileLabelnull166 override fun getTileLabel(): CharSequence { 167 return mContext.getText(controlsComponent.getTileTitleId()) 168 } 169 170 companion object { 171 const val TILE_SPEC = "controls" 172 } 173 }