1 /*
<lambda>null2  * 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 
18 package com.android.systemui.controls.start
19 
20 import android.content.BroadcastReceiver
21 import android.content.Context
22 import android.content.Intent
23 import android.content.IntentFilter
24 import android.os.UserHandle
25 import android.os.UserManager
26 import androidx.annotation.WorkerThread
27 import com.android.systemui.CoreStartable
28 import com.android.systemui.broadcast.BroadcastDispatcher
29 import com.android.systemui.common.shared.model.PackageChangeModel
30 import com.android.systemui.common.domain.interactor.PackageChangeInteractor
31 import com.android.systemui.controls.controller.ControlsController
32 import com.android.systemui.controls.dagger.ControlsComponent
33 import com.android.systemui.controls.management.ControlsListingController
34 import com.android.systemui.controls.panels.AuthorizedPanelsRepository
35 import com.android.systemui.controls.panels.SelectedComponentRepository
36 import com.android.systemui.controls.ui.SelectedItem
37 import com.android.systemui.dagger.SysUISingleton
38 import com.android.systemui.dagger.qualifiers.Application
39 import com.android.systemui.dagger.qualifiers.Background
40 import com.android.systemui.settings.UserTracker
41 import kotlinx.coroutines.CoroutineDispatcher
42 import kotlinx.coroutines.CoroutineScope
43 import kotlinx.coroutines.Job
44 import kotlinx.coroutines.flow.filter
45 import kotlinx.coroutines.flow.filterIsInstance
46 import kotlinx.coroutines.flow.flowOn
47 import kotlinx.coroutines.flow.launchIn
48 import kotlinx.coroutines.flow.onEach
49 import java.util.concurrent.Executor
50 import javax.inject.Inject
51 
52 /**
53  * Started with SystemUI to perform early operations for device controls subsystem (only if enabled)
54  *
55  * In particular, it will perform the following:
56  * * If there is no preferred selection for provider and at least one of the preferred packages
57  * provides a panel, it will select the first one that does.
58  * * If the preferred selection provides a panel, it will bind to that service (to reduce latency on
59  * displaying the panel).
60  *
61  * It will also perform those operations on user change.
62  */
63 @SysUISingleton
64 class ControlsStartable
65 @Inject
66 constructor(
67     @Application private val scope: CoroutineScope,
68     @Background private val bgDispatcher: CoroutineDispatcher,
69     @Background private val executor: Executor,
70     private val controlsComponent: ControlsComponent,
71     private val userTracker: UserTracker,
72     private val authorizedPanelsRepository: AuthorizedPanelsRepository,
73     private val selectedComponentRepository: SelectedComponentRepository,
74     private val packageChangeInteractor: PackageChangeInteractor,
75     private val userManager: UserManager,
76     private val broadcastDispatcher: BroadcastDispatcher,
77 ) : CoreStartable {
78 
79     // These two controllers can only be accessed after `start` method once we've checked if the
80     // feature is enabled
81     private val controlsController: ControlsController
82         get() = controlsComponent.getControlsController().get()
83 
84     private val controlsListingController: ControlsListingController
85         get() = controlsComponent.getControlsListingController().get()
86 
87     private val userTrackerCallback =
88         object : UserTracker.Callback {
89             override fun onUserChanged(newUser: Int, userContext: Context) {
90                 controlsController.changeUser(UserHandle.of(newUser))
91                 startForUser()
92             }
93         }
94 
95     private var packageJob: Job? = null
96 
97     override fun start() {}
98 
99     override fun onBootCompleted() {
100         if (!controlsComponent.isEnabled()) {
101             // Controls is disabled, we don't need this anymore
102             return
103         }
104         executor.execute(this::startForUser)
105         userTracker.addCallback(userTrackerCallback, executor)
106     }
107 
108     @WorkerThread
109     private fun startForUser() {
110         controlsListingController.forceReload()
111         selectDefaultPanelIfNecessary()
112         bindToPanel()
113         monitorPackageUninstall()
114     }
115 
116     private fun monitorPackageUninstall() {
117         packageJob?.cancel()
118         packageJob = packageChangeInteractor.packageChanged(userTracker.userHandle)
119             .filterIsInstance<PackageChangeModel.Uninstalled>()
120             .filter {
121                 val selectedPackage =
122                     selectedComponentRepository.getSelectedComponent()?.componentName?.packageName
123                 // Selected package was uninstalled
124                 it.packageName == selectedPackage
125             }
126             .onEach { selectedComponentRepository.removeSelectedComponent() }
127             .flowOn(bgDispatcher)
128             .launchIn(scope)
129     }
130 
131     private fun selectDefaultPanelIfNecessary() {
132         if (!selectedComponentRepository.shouldAddDefaultComponent()) {
133             return
134         }
135         val currentSelection = controlsController.getPreferredSelection()
136         if (currentSelection == SelectedItem.EMPTY_SELECTION) {
137             val availableServices = controlsListingController.getCurrentServices()
138             val panels = availableServices.filter { it.panelActivity != null }
139             authorizedPanelsRepository
140                 .getPreferredPackages()
141                 // Looking for the first element in the string array such that there is one package
142                 // that has a panel. It will return null if there are no packages in the array,
143                 // or if no packages in the array have a panel associated with it.
144                 .firstNotNullOfOrNull { name ->
145                     panels.firstOrNull { it.componentName.packageName == name }
146                 }
147                 ?.let { info ->
148                     controlsController.setPreferredSelection(
149                         SelectedItem.PanelItem(info.loadLabel(), info.componentName)
150                     )
151                 }
152         }
153     }
154 
155     private fun bindToPanel() {
156         if (userManager.isUserUnlocked(userTracker.userId)) {
157             bindToPanelInternal()
158         } else {
159             broadcastDispatcher.registerReceiver(
160                     receiver = object : BroadcastReceiver() {
161                         override fun onReceive(context: Context?, intent: Intent?) {
162                             if (userManager.isUserUnlocked(userTracker.userId)) {
163                                 bindToPanelInternal()
164                                 broadcastDispatcher.unregisterReceiver(this)
165                             }
166                         }
167                     },
168                     filter = IntentFilter(Intent.ACTION_USER_UNLOCKED),
169                     executor = executor,
170                     user = userTracker.userHandle,
171             )
172         }
173     }
174 
175     private fun bindToPanelInternal() {
176         val currentSelection = controlsController.getPreferredSelection()
177         val panels =
178                 controlsListingController.getCurrentServices().filter { it.panelActivity != null }
179         if (currentSelection is SelectedItem.PanelItem &&
180                 panels.firstOrNull { it.componentName == currentSelection.componentName } != null
181         ) {
182             controlsController.bindComponentForPanel(currentSelection.componentName)
183         }
184     }
185 }
186