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.view.View 25 import androidx.annotation.VisibleForTesting 26 import com.android.internal.jank.InteractionJankMonitor 27 import com.android.internal.logging.MetricsLogger 28 import com.android.systemui.R 29 import com.android.systemui.animation.ActivityLaunchAnimator 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.logging.QSLogger 44 import com.android.systemui.qs.tileimpl.QSTileImpl 45 import java.util.concurrent.atomic.AtomicBoolean 46 import javax.inject.Inject 47 48 class DeviceControlsTile @Inject constructor( 49 host: QSHost, 50 @Background backgroundLooper: Looper, 51 @Main mainHandler: Handler, 52 falsingManager: FalsingManager, 53 metricsLogger: MetricsLogger, 54 statusBarStateController: StatusBarStateController, 55 activityStarter: ActivityStarter, 56 qsLogger: QSLogger, 57 private val controlsComponent: ControlsComponent 58 ) : QSTileImpl<QSTile.State>( 59 host, 60 backgroundLooper, 61 mainHandler, 62 falsingManager, 63 metricsLogger, 64 statusBarStateController, 65 activityStarter, 66 qsLogger 67 ) { 68 69 private var hasControlsApps = AtomicBoolean(false) 70 71 @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) 72 val icon: QSTile.Icon 73 get() = ResourceIcon.get(controlsComponent.getTileImageId()) 74 75 private val listingCallback = object : ControlsListingController.ControlsListingCallback { onServicesUpdatednull76 override fun onServicesUpdated(serviceInfos: List<ControlsServiceInfo>) { 77 if (hasControlsApps.compareAndSet(serviceInfos.isEmpty(), serviceInfos.isNotEmpty())) { 78 refreshState() 79 } 80 } 81 } 82 83 init { <lambda>null84 controlsComponent.getControlsListingController().ifPresent { 85 it.observe(this, listingCallback) 86 } 87 } 88 isAvailablenull89 override fun isAvailable(): Boolean { 90 return controlsComponent.getControlsController().isPresent 91 } 92 newTileStatenull93 override fun newTileState(): QSTile.State { 94 return QSTile.State().also { 95 it.state = Tile.STATE_UNAVAILABLE // Start unavailable matching `hasControlsApps` 96 it.handlesLongClick = false 97 } 98 } 99 handleClicknull100 override fun handleClick(view: View?) { 101 if (state.state == Tile.STATE_UNAVAILABLE) { 102 return 103 } 104 105 val intent = Intent().apply { 106 component = ComponentName(mContext, controlsComponent.getControlsUiController().get() 107 .resolveActivity()) 108 addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK) 109 putExtra(ControlsUiController.EXTRA_ANIMATE, true) 110 } 111 val animationController = view?.let { 112 ActivityLaunchAnimator.Controller.fromView( 113 it, InteractionJankMonitor.CUJ_SHADE_APP_LAUNCH_FROM_QS_TILE) 114 } 115 116 mUiHandler.post { 117 val showOverLockscreenWhenLocked = state.state == Tile.STATE_ACTIVE 118 mActivityStarter.startActivity( 119 intent, true /* dismissShade */, animationController, showOverLockscreenWhenLocked) 120 } 121 } 122 handleUpdateStatenull123 override fun handleUpdateState(state: QSTile.State, arg: Any?) { 124 state.label = tileLabel 125 state.contentDescription = state.label 126 state.icon = icon 127 if (controlsComponent.isEnabled() && hasControlsApps.get()) { 128 if (controlsComponent.getVisibility() == AVAILABLE) { 129 val selection = controlsComponent 130 .getControlsController().get().getPreferredSelection() 131 state.state = if (selection is SelectedItem.StructureItem && 132 selection.structure.controls.isEmpty()) { 133 Tile.STATE_INACTIVE 134 } else { 135 Tile.STATE_ACTIVE 136 } 137 val label = selection.name 138 state.secondaryLabel = if (label == tileLabel) null else label 139 } else { 140 state.state = Tile.STATE_INACTIVE 141 state.secondaryLabel = mContext.getText(R.string.controls_tile_locked) 142 } 143 state.stateDescription = state.secondaryLabel 144 } else { 145 state.state = Tile.STATE_UNAVAILABLE 146 } 147 } 148 getMetricsCategorynull149 override fun getMetricsCategory(): Int { 150 return 0 151 } 152 getLongClickIntentnull153 override fun getLongClickIntent(): Intent? { 154 return null 155 } 156 handleLongClicknull157 override fun handleLongClick(view: View?) {} 158 getTileLabelnull159 override fun getTileLabel(): CharSequence { 160 return mContext.getText(controlsComponent.getTileTitleId()) 161 } 162 163 companion object { 164 const val TILE_SPEC = "controls" 165 } 166 } 167