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