1 /*
<lambda>null2  * Copyright (C) 2024 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.dreams.homecontrols.domain.interactor
18 
19 import android.annotation.SuppressLint
20 import android.app.DreamManager
21 import android.content.ComponentName
22 import android.os.PowerManager
23 import android.os.UserHandle
24 import com.android.systemui.common.domain.interactor.PackageChangeInteractor
25 import com.android.systemui.common.shared.model.PackageChangeModel
26 import com.android.systemui.controls.ControlsServiceInfo
27 import com.android.systemui.controls.dagger.ControlsComponent
28 import com.android.systemui.controls.management.ControlsListingController
29 import com.android.systemui.controls.panels.AuthorizedPanelsRepository
30 import com.android.systemui.controls.panels.SelectedComponentRepository
31 import com.android.systemui.dagger.SysUISingleton
32 import com.android.systemui.dagger.qualifiers.Background
33 import com.android.systemui.user.data.repository.UserRepository
34 import com.android.systemui.util.kotlin.getOrNull
35 import com.android.systemui.util.kotlin.pairwiseBy
36 import com.android.systemui.util.kotlin.sample
37 import com.android.systemui.util.time.SystemClock
38 import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
39 import javax.inject.Inject
40 import kotlin.math.abs
41 import kotlin.time.Duration.Companion.milliseconds
42 import kotlinx.coroutines.CoroutineScope
43 import kotlinx.coroutines.ExperimentalCoroutinesApi
44 import kotlinx.coroutines.channels.BufferOverflow
45 import kotlinx.coroutines.channels.awaitClose
46 import kotlinx.coroutines.flow.Flow
47 import kotlinx.coroutines.flow.MutableSharedFlow
48 import kotlinx.coroutines.flow.SharingStarted
49 import kotlinx.coroutines.flow.StateFlow
50 import kotlinx.coroutines.flow.combine
51 import kotlinx.coroutines.flow.emptyFlow
52 import kotlinx.coroutines.flow.filter
53 import kotlinx.coroutines.flow.filterNotNull
54 import kotlinx.coroutines.flow.flatMapLatest
55 import kotlinx.coroutines.flow.map
56 import kotlinx.coroutines.flow.onStart
57 import kotlinx.coroutines.flow.stateIn
58 
59 @SysUISingleton
60 @OptIn(ExperimentalCoroutinesApi::class)
61 class HomeControlsComponentInteractor
62 @Inject
63 constructor(
64     private val selectedComponentRepository: SelectedComponentRepository,
65     controlsComponent: ControlsComponent,
66     authorizedPanelsRepository: AuthorizedPanelsRepository,
67     userRepository: UserRepository,
68     private val packageChangeInteractor: PackageChangeInteractor,
69     private val systemClock: SystemClock,
70     private val powerManager: PowerManager,
71     private val dreamManager: DreamManager,
72     @Background private val bgScope: CoroutineScope
73 ) {
74     private val controlsListingController: ControlsListingController? =
75         controlsComponent.getControlsListingController().getOrNull()
76 
77     /** Gets the current user's selected panel, or null if there isn't one */
78     private val selectedPanel: Flow<SelectedComponentRepository.SelectedComponent?> =
79         userRepository.selectedUserInfo
80             .flatMapLatest { user ->
81                 selectedComponentRepository.selectedComponentFlow(user.userHandle)
82             }
83             .map { if (it?.isPanel == true) it else null }
84 
85     /** Gets the current user's authorized panels */
86     private val allAuthorizedPanels: Flow<Set<String>> =
87         userRepository.selectedUserInfo.flatMapLatest { user ->
88             authorizedPanelsRepository.observeAuthorizedPanels(user.userHandle)
89         }
90 
91     /** Gets all the available services from [ControlsListingController] */
92     private fun allAvailableServices(): Flow<List<ControlsServiceInfo>> {
93         if (controlsListingController == null) {
94             return emptyFlow()
95         }
96         return conflatedCallbackFlow {
97                 val listener =
98                     object : ControlsListingController.ControlsListingCallback {
99                         override fun onServicesUpdated(serviceInfos: List<ControlsServiceInfo>) {
100                             trySend(serviceInfos)
101                         }
102                     }
103                 controlsListingController.addCallback(listener)
104                 awaitClose { controlsListingController.removeCallback(listener) }
105             }
106             .onStart { emit(controlsListingController.getCurrentServices()) }
107     }
108 
109     /** Gets all panels which are available and authorized by the user */
110     private val allAvailableAndAuthorizedPanels: Flow<List<PanelComponent>> =
111         combine(
112             allAvailableServices(),
113             allAuthorizedPanels,
114         ) { serviceInfos, authorizedPanels ->
115             serviceInfos.mapNotNull {
116                 val panelActivity = it.panelActivity
117                 if (it.componentName.packageName in authorizedPanels && panelActivity != null) {
118                     PanelComponent(it.componentName, panelActivity)
119                 } else {
120                     null
121                 }
122             }
123         }
124 
125     val panelComponent: StateFlow<ComponentName?> =
126         combine(
127                 allAvailableAndAuthorizedPanels,
128                 selectedPanel,
129             ) { panels, selected ->
130                 val item =
131                     panels.firstOrNull { it.componentName == selected?.componentName }
132                         ?: panels.firstOrNull()
133                 item?.panelActivity
134             }
135             .stateIn(bgScope, SharingStarted.Eagerly, null)
136 
137     private val taskFragmentFinished =
138         MutableSharedFlow<Long>(replay = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST)
139 
140     fun onDreamEndUnexpectedly() {
141         powerManager.userActivity(
142             systemClock.uptimeMillis(),
143             PowerManager.USER_ACTIVITY_EVENT_OTHER,
144             PowerManager.USER_ACTIVITY_FLAG_NO_CHANGE_LIGHTS,
145         )
146         taskFragmentFinished.tryEmit(systemClock.currentTimeMillis())
147     }
148 
149     /**
150      * Monitors if the current home panel package is updated and causes the dream to finish, and
151      * attempts to restart the dream in this case.
152      */
153     @SuppressLint("MissingPermission")
154     suspend fun monitorUpdatesAndRestart() {
155         taskFragmentFinished.resetReplayCache()
156         panelComponent
157             .flatMapLatest { component ->
158                 if (component == null) return@flatMapLatest emptyFlow()
159                 packageChangeInteractor.packageChanged(UserHandle.CURRENT, component.packageName)
160             }
161             .filter { it.isUpdate() }
162             // Wait for an UpdatedStarted - UpdateFinished pair to ensure the update has finished.
163             .pairwiseBy(::validateUpdatePair)
164             .filterNotNull()
165             .sample(taskFragmentFinished, ::Pair)
166             .filter { (updateStarted, lastFinishedTimestamp) ->
167                 abs(updateStarted.timeMillis - lastFinishedTimestamp) <=
168                     MAX_UPDATE_CORRELATION_DELAY.inWholeMilliseconds
169             }
170             .collect { dreamManager.startDream() }
171     }
172 
173     private data class PanelComponent(
174         val componentName: ComponentName,
175         val panelActivity: ComponentName,
176     )
177 
178     companion object {
179         /**
180          * The maximum delay between a package update **starting** and the task fragment finishing
181          * which causes us to correlate the package update as the cause of the task fragment
182          * finishing.
183          */
184         val MAX_UPDATE_CORRELATION_DELAY = 500.milliseconds
185     }
186 }
187 
PackageChangeModelnull188 private fun PackageChangeModel.isUpdate() =
189     this is PackageChangeModel.UpdateStarted || this is PackageChangeModel.UpdateFinished
190 
191 private fun validateUpdatePair(
192     updateStarted: PackageChangeModel,
193     updateFinished: PackageChangeModel
194 ): PackageChangeModel.UpdateStarted? =
195     when {
196         !updateStarted.isSamePackage(updateFinished) -> null
197         updateStarted !is PackageChangeModel.UpdateStarted -> null
198         updateFinished !is PackageChangeModel.UpdateFinished -> null
199         else -> updateStarted
200     }
201