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.pipeline.domain.interactor
18 
19 import com.android.systemui.Dumpable
20 import com.android.systemui.dagger.SysUISingleton
21 import com.android.systemui.dagger.qualifiers.Application
22 import com.android.systemui.dump.DumpManager
23 import com.android.systemui.qs.pipeline.data.repository.AutoAddRepository
24 import com.android.systemui.qs.pipeline.domain.model.AutoAddSignal
25 import com.android.systemui.qs.pipeline.domain.model.AutoAddTracking
26 import com.android.systemui.qs.pipeline.domain.model.AutoAddable
27 import com.android.systemui.qs.pipeline.shared.logging.QSPipelineLogger
28 import com.android.systemui.util.asIndenting
29 import com.android.systemui.util.indentIfPossible
30 import java.io.PrintWriter
31 import java.util.concurrent.atomic.AtomicBoolean
32 import javax.inject.Inject
33 import kotlinx.coroutines.CoroutineScope
34 import kotlinx.coroutines.coroutineScope
35 import kotlinx.coroutines.flow.collectLatest
36 import kotlinx.coroutines.flow.emptyFlow
37 import kotlinx.coroutines.flow.filterIsInstance
38 import kotlinx.coroutines.flow.merge
39 import kotlinx.coroutines.flow.stateIn
40 import kotlinx.coroutines.flow.take
41 import kotlinx.coroutines.launch
42 
43 /**
44  * Collects the signals coming from all registered [AutoAddable] and adds/removes tiles accordingly.
45  */
46 @SysUISingleton
47 class AutoAddInteractor
48 @Inject
49 constructor(
50     private val autoAddables: Set<@JvmSuppressWildcards AutoAddable>,
51     private val repository: AutoAddRepository,
52     private val dumpManager: DumpManager,
53     private val qsPipelineLogger: QSPipelineLogger,
54     @Application private val scope: CoroutineScope,
55 ) : Dumpable {
56 
57     private val initialized = AtomicBoolean(false)
58 
59     /** Start collection of signals following the user from [currentTilesInteractor]. */
60     fun init(currentTilesInteractor: CurrentTilesInteractor) {
61         if (!initialized.compareAndSet(false, true)) {
62             return
63         }
64 
65         dumpManager.registerNormalDumpable(TAG, this)
66 
67         scope.launch {
68             currentTilesInteractor.userId.collectLatest { userId ->
69                 coroutineScope {
70                     val previouslyAdded = repository.autoAddedTiles(userId).stateIn(this)
71 
72                     autoAddables
73                         .map { addable ->
74                             val autoAddSignal = addable.autoAddSignal(userId)
75                             when (val lifecycle = addable.autoAddTracking) {
76                                 is AutoAddTracking.Always -> autoAddSignal
77                                 is AutoAddTracking.Disabled -> emptyFlow()
78                                 is AutoAddTracking.IfNotAdded -> {
79                                     if (lifecycle.spec !in previouslyAdded.value) {
80                                         autoAddSignal.filterIsInstance<AutoAddSignal.Add>().take(1)
81                                     } else {
82                                         emptyFlow()
83                                     }
84                                 }
85                             }
86                         }
87                         .merge()
88                         .collect { signal ->
89                             when (signal) {
90                                 is AutoAddSignal.Add -> {
91                                     if (signal.spec !in previouslyAdded.value) {
92                                         currentTilesInteractor.addTile(signal.spec, signal.position)
93                                         qsPipelineLogger.logTileAutoAdded(
94                                             userId,
95                                             signal.spec,
96                                             signal.position
97                                         )
98                                         repository.markTileAdded(userId, signal.spec)
99                                     }
100                                 }
101                                 is AutoAddSignal.Remove -> {
102                                     currentTilesInteractor.removeTiles(setOf(signal.spec))
103                                     qsPipelineLogger.logTileAutoRemoved(userId, signal.spec)
104                                     repository.unmarkTileAdded(userId, signal.spec)
105                                 }
106                             }
107                         }
108                 }
109             }
110         }
111     }
112 
113     override fun dump(pw: PrintWriter, args: Array<out String>) {
114         with(pw.asIndenting()) {
115             println("AutoAddables:")
116             indentIfPossible { autoAddables.forEach { println(it.description) } }
117         }
118     }
119 
120     companion object {
121         private const val TAG = "AutoAddInteractor"
122     }
123 }
124