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 
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.controls.controller.ControlsController
30 import com.android.systemui.controls.dagger.ControlsComponent
31 import com.android.systemui.controls.management.ControlsListingController
32 import com.android.systemui.controls.panels.AuthorizedPanelsRepository
33 import com.android.systemui.controls.panels.SelectedComponentRepository
34 import com.android.systemui.controls.ui.SelectedItem
35 import com.android.systemui.dagger.SysUISingleton
36 import com.android.systemui.dagger.qualifiers.Background
37 import com.android.systemui.settings.UserTracker
38 import java.util.concurrent.Executor
39 import javax.inject.Inject
40 
41 /**
42  * Started with SystemUI to perform early operations for device controls subsystem (only if enabled)
43  *
44  * In particular, it will perform the following:
45  * * If there is no preferred selection for provider and at least one of the preferred packages
46  * provides a panel, it will select the first one that does.
47  * * If the preferred selection provides a panel, it will bind to that service (to reduce latency on
48  * displaying the panel).
49  *
50  * It will also perform those operations on user change.
51  */
52 @SysUISingleton
53 class ControlsStartable
54 @Inject
55 constructor(
56         @Background private val executor: Executor,
57         private val controlsComponent: ControlsComponent,
58         private val userTracker: UserTracker,
59         private val authorizedPanelsRepository: AuthorizedPanelsRepository,
60         private val selectedComponentRepository: SelectedComponentRepository,
61         private val userManager: UserManager,
62         private val broadcastDispatcher: BroadcastDispatcher,
63 ) : CoreStartable {
64 
65     // These two controllers can only be accessed after `start` method once we've checked if the
66     // feature is enabled
67     private val controlsController: ControlsController
68         get() = controlsComponent.getControlsController().get()
69 
70     private val controlsListingController: ControlsListingController
71         get() = controlsComponent.getControlsListingController().get()
72 
73     private val userTrackerCallback =
74         object : UserTracker.Callback {
75             override fun onUserChanged(newUser: Int, userContext: Context) {
76                 controlsController.changeUser(UserHandle.of(newUser))
77                 startForUser()
78             }
79         }
80 
81     override fun start() {}
82 
83     override fun onBootCompleted() {
84         if (!controlsComponent.isEnabled()) {
85             // Controls is disabled, we don't need this anymore
86             return
87         }
88         executor.execute(this::startForUser)
89         userTracker.addCallback(userTrackerCallback, executor)
90     }
91 
92     @WorkerThread
93     private fun startForUser() {
94         controlsListingController.forceReload()
95         selectDefaultPanelIfNecessary()
96         bindToPanel()
97     }
98 
99     private fun selectDefaultPanelIfNecessary() {
100         if (!selectedComponentRepository.shouldAddDefaultComponent()) {
101             return
102         }
103         val currentSelection = controlsController.getPreferredSelection()
104         if (currentSelection == SelectedItem.EMPTY_SELECTION) {
105             val availableServices = controlsListingController.getCurrentServices()
106             val panels = availableServices.filter { it.panelActivity != null }
107             authorizedPanelsRepository
108                 .getPreferredPackages()
109                 // Looking for the first element in the string array such that there is one package
110                 // that has a panel. It will return null if there are no packages in the array,
111                 // or if no packages in the array have a panel associated with it.
112                 .firstNotNullOfOrNull { name ->
113                     panels.firstOrNull { it.componentName.packageName == name }
114                 }
115                 ?.let { info ->
116                     controlsController.setPreferredSelection(
117                         SelectedItem.PanelItem(info.loadLabel(), info.componentName)
118                     )
119                 }
120         }
121     }
122 
123     private fun bindToPanel() {
124         if (userManager.isUserUnlocked(userTracker.userId)) {
125             bindToPanelInternal()
126         } else {
127             broadcastDispatcher.registerReceiver(
128                     receiver = object : BroadcastReceiver() {
129                         override fun onReceive(context: Context?, intent: Intent?) {
130                             if (userManager.isUserUnlocked(userTracker.userId)) {
131                                 bindToPanelInternal()
132                                 broadcastDispatcher.unregisterReceiver(this)
133                             }
134                         }
135                     },
136                     filter = IntentFilter(Intent.ACTION_USER_UNLOCKED),
137                     executor = executor,
138                     user = userTracker.userHandle,
139             )
140         }
141     }
142 
143     private fun bindToPanelInternal() {
144         val currentSelection = controlsController.getPreferredSelection()
145         val panels =
146                 controlsListingController.getCurrentServices().filter { it.panelActivity != null }
147         if (currentSelection is SelectedItem.PanelItem &&
148                 panels.firstOrNull { it.componentName == currentSelection.componentName } != null
149         ) {
150             controlsController.bindComponentForPanel(currentSelection.componentName)
151         }
152     }
153 }
154