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 }