1 /*
2  * Copyright (C) 2023 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
18 
19 import android.content.ComponentName
20 import android.content.Context
21 import androidx.annotation.GuardedBy
22 import com.android.systemui.dagger.SysUISingleton
23 import com.android.systemui.dagger.qualifiers.Application
24 import com.android.systemui.dump.DumpManager
25 import com.android.systemui.flags.FeatureFlags
26 import com.android.systemui.flags.Flags
27 import com.android.systemui.plugins.qs.QSTile
28 import com.android.systemui.plugins.qs.QSTileView
29 import com.android.systemui.qs.external.TileServiceRequestController
30 import com.android.systemui.qs.pipeline.data.repository.TileSpecRepository.Companion.POSITION_AT_END
31 import com.android.systemui.qs.pipeline.domain.interactor.CurrentTilesInteractor
32 import com.android.systemui.qs.pipeline.shared.TileSpec
33 import javax.inject.Inject
34 import kotlinx.coroutines.CoroutineScope
35 import kotlinx.coroutines.Job
36 import kotlinx.coroutines.launch
37 
38 /**
39  * Adapter to determine what real class to use for classes that depend on [QSHost].
40  * * When [Flags.QS_PIPELINE_NEW_HOST] is off, all calls will be routed to [QSTileHost].
41  * * When [Flags.QS_PIPELINE_NEW_HOST] is on, calls regarding the current set of tiles will be
42  *   routed to [CurrentTilesInteractor]. Other calls (like [createTileView]) will still be routed to
43  *   [QSTileHost].
44  *
45  * This routing also includes dumps.
46  */
47 @SysUISingleton
48 class QSHostAdapter
49 @Inject
50 constructor(
51     private val qsTileHost: QSTileHost,
52     private val interactor: CurrentTilesInteractor,
53     private val context: Context,
54     private val tileServiceRequestControllerBuilder: TileServiceRequestController.Builder,
55     @Application private val scope: CoroutineScope,
56     private val featureFlags: FeatureFlags,
57     dumpManager: DumpManager,
58 ) : QSHost {
59 
60     companion object {
61         private const val TAG = "QSTileHost"
62     }
63 
64     private val useNewHost = featureFlags.isEnabled(Flags.QS_PIPELINE_NEW_HOST)
65 
66     @GuardedBy("callbacksMap") private val callbacksMap = mutableMapOf<QSHost.Callback, Job>()
67 
68     init {
69         scope.launch { tileServiceRequestControllerBuilder.create(this@QSHostAdapter).init() }
70         // Redirect dump to the correct host (needed for CTS tests)
71         dumpManager.registerCriticalDumpable(TAG, if (useNewHost) interactor else qsTileHost)
72     }
73 
74     override fun getTiles(): Collection<QSTile> {
75         return if (useNewHost) {
76             interactor.currentQSTiles
77         } else {
78             qsTileHost.getTiles()
79         }
80     }
81 
82     override fun getSpecs(): List<String> {
83         return if (useNewHost) {
84             interactor.currentTilesSpecs.map { it.spec }
85         } else {
86             qsTileHost.getSpecs()
87         }
88     }
89 
90     override fun removeTile(spec: String) {
91         if (useNewHost) {
92             interactor.removeTiles(listOf(TileSpec.create(spec)))
93         } else {
94             qsTileHost.removeTile(spec)
95         }
96     }
97 
98     override fun addCallback(callback: QSHost.Callback) {
99         if (useNewHost) {
100             val job = scope.launch { interactor.currentTiles.collect { callback.onTilesChanged() } }
101             synchronized(callbacksMap) { callbacksMap.put(callback, job) }
102         } else {
103             qsTileHost.addCallback(callback)
104         }
105     }
106 
107     override fun removeCallback(callback: QSHost.Callback) {
108         if (useNewHost) {
109             synchronized(callbacksMap) { callbacksMap.get(callback)?.cancel() }
110         } else {
111             qsTileHost.removeCallback(callback)
112         }
113     }
114 
115     override fun removeTiles(specs: Collection<String>) {
116         if (useNewHost) {
117             interactor.removeTiles(specs.map(TileSpec::create))
118         } else {
119             qsTileHost.removeTiles(specs)
120         }
121     }
122 
123     override fun removeTileByUser(component: ComponentName) {
124         if (useNewHost) {
125             interactor.removeTiles(listOf(TileSpec.create(component)))
126         } else {
127             qsTileHost.removeTileByUser(component)
128         }
129     }
130 
131     override fun addTile(spec: String, position: Int) {
132         if (useNewHost) {
133             interactor.addTile(TileSpec.create(spec), position)
134         } else {
135             qsTileHost.addTile(spec, position)
136         }
137     }
138 
139     override fun addTile(component: ComponentName, end: Boolean) {
140         if (useNewHost) {
141             interactor.addTile(TileSpec.create(component), if (end) POSITION_AT_END else 0)
142         } else {
143             qsTileHost.addTile(component, end)
144         }
145     }
146 
147     override fun changeTilesByUser(previousTiles: List<String>, newTiles: List<String>) {
148         if (useNewHost) {
149             interactor.setTiles(newTiles.map(TileSpec::create))
150         } else {
151             qsTileHost.changeTilesByUser(previousTiles, newTiles)
152         }
153     }
154 
155     override fun getContext(): Context {
156         return if (useNewHost) {
157             context
158         } else {
159             qsTileHost.context
160         }
161     }
162 
163     override fun getUserContext(): Context {
164         return if (useNewHost) {
165             interactor.userContext.value
166         } else {
167             qsTileHost.userContext
168         }
169     }
170 
171     override fun getUserId(): Int {
172         return if (useNewHost) {
173             interactor.userId.value
174         } else {
175             qsTileHost.userId
176         }
177     }
178 
179     override fun createTileView(
180         themedContext: Context?,
181         tile: QSTile?,
182         collapsedView: Boolean
183     ): QSTileView {
184         return qsTileHost.createTileView(themedContext, tile, collapsedView)
185     }
186 
187     override fun createTile(tileSpec: String): QSTile? {
188         return qsTileHost.createTile(tileSpec)
189     }
190 
191     override fun addTile(spec: String) {
192         return addTile(spec, QSHost.POSITION_AT_END)
193     }
194 
195     override fun addTile(tile: ComponentName) {
196         return addTile(tile, false)
197     }
198 
199     override fun indexOf(tileSpec: String): Int {
200         return specs.indexOf(tileSpec)
201     }
202 }
203