1 /*
2  * Copyright (C) 2019 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.controls.management
18 
19 import android.annotation.WorkerThread
20 import android.content.ComponentName
21 import android.content.Context
22 import android.content.Intent
23 import android.content.pm.PackageManager
24 import android.os.UserHandle
25 import android.service.controls.ControlsProviderService
26 import android.util.Log
27 import com.android.internal.annotations.VisibleForTesting
28 import com.android.settingslib.applications.ServiceListing
29 import com.android.settingslib.widget.CandidateInfo
30 import com.android.systemui.Dumpable
31 import com.android.systemui.controls.ControlsServiceInfo
32 import com.android.systemui.dagger.SysUISingleton
33 import com.android.systemui.dagger.qualifiers.Background
34 import com.android.systemui.dump.DumpManager
35 import com.android.systemui.flags.FeatureFlags
36 import com.android.systemui.flags.Flags
37 import com.android.systemui.settings.UserTracker
38 import com.android.systemui.util.ActivityTaskManagerProxy
39 import com.android.systemui.util.asIndenting
40 import com.android.systemui.util.indentIfPossible
41 import java.io.PrintWriter
42 import java.util.concurrent.Executor
43 import java.util.concurrent.atomic.AtomicInteger
44 import javax.inject.Inject
45 
46 private fun createServiceListing(context: Context): ServiceListing {
47     return ServiceListing.Builder(context).apply {
48         setIntentAction(ControlsProviderService.SERVICE_CONTROLS)
49         setPermission("android.permission.BIND_CONTROLS")
50         setNoun("Controls Provider")
51         setSetting("controls_providers")
52         setTag("controls_providers")
53         setAddDeviceLockedFlags(true)
54     }.build()
55 }
56 
57 /**
58  * Provides a listing of components to be used as ControlsServiceProvider.
59  *
60  * This controller keeps track of components that satisfy:
61  *
62  * * Has an intent-filter responding to [ControlsProviderService.CONTROLS_ACTION]
63  * * Has the bind permission `android.permission.BIND_CONTROLS`
64  */
65 @SysUISingleton
66 class ControlsListingControllerImpl @VisibleForTesting constructor(
67     private val context: Context,
68     @Background private val backgroundExecutor: Executor,
69     private val serviceListingBuilder: (Context) -> ServiceListing,
70     private val userTracker: UserTracker,
71     private val activityTaskManagerProxy: ActivityTaskManagerProxy,
72     dumpManager: DumpManager,
73     private val featureFlags: FeatureFlags
74 ) : ControlsListingController, Dumpable {
75 
76     @Inject
77     constructor(
78             context: Context,
79             @Background executor: Executor,
80             userTracker: UserTracker,
81             activityTaskManagerProxy: ActivityTaskManagerProxy,
82             dumpManager: DumpManager,
83             featureFlags: FeatureFlags
84     ) : this(
85         context,
86         executor,
87         ::createServiceListing,
88         userTracker,
89         activityTaskManagerProxy,
90         dumpManager,
91         featureFlags
92     )
93 
94     private var serviceListing = serviceListingBuilder(context)
95     // All operations in background thread
96     private val callbacks = mutableSetOf<ControlsListingController.ControlsListingCallback>()
97 
98     companion object {
99         private const val TAG = "ControlsListingControllerImpl"
100     }
101 
102     private var availableServices = emptyList<ControlsServiceInfo>()
103     private var userChangeInProgress = AtomicInteger(0)
104 
105     override var currentUserId = userTracker.userId
106         private set
107 
108     private val serviceListingCallback = ServiceListing.Callback { list ->
109         Log.d(TAG, "ServiceConfig reloaded, count: ${list.size}")
110         val newServices = list.map { ControlsServiceInfo(userTracker.userContext, it) }
111         // After here, `list` is not captured, so we don't risk modifying it outside of the callback
112         backgroundExecutor.execute {
113             if (userChangeInProgress.get() > 0) return@execute
114             updateServices(newServices)
115         }
116     }
117 
118     init {
119         Log.d(TAG, "Initializing")
120         dumpManager.registerDumpable(TAG, this)
121         serviceListing.addCallback(serviceListingCallback)
122         serviceListing.setListening(true)
123         serviceListing.reload()
124     }
125 
126     private fun updateServices(newServices: List<ControlsServiceInfo>) {
127         if (activityTaskManagerProxy.supportsMultiWindow(context)) {
128             val allowAllApps = featureFlags.isEnabled(Flags.APP_PANELS_ALL_APPS_ALLOWED)
129             newServices.forEach {
130                 it.resolvePanelActivity(allowAllApps) }
131         }
132 
133         if (newServices != availableServices) {
134             availableServices = newServices
135             callbacks.forEach {
136                 it.onServicesUpdated(getCurrentServices())
137             }
138         }
139     }
140 
141     override fun changeUser(newUser: UserHandle) {
142         userChangeInProgress.incrementAndGet()
143         serviceListing.setListening(false)
144 
145         backgroundExecutor.execute {
146             if (userChangeInProgress.decrementAndGet() == 0) {
147                 currentUserId = newUser.identifier
148                 val contextForUser = context.createContextAsUser(newUser, 0)
149                 serviceListing = serviceListingBuilder(contextForUser)
150                 serviceListing.addCallback(serviceListingCallback)
151                 serviceListing.setListening(true)
152                 serviceListing.reload()
153             }
154         }
155     }
156 
157     /**
158      * Adds a callback to this controller.
159      *
160      * The callback will be notified after it is added as well as any time that the valid
161      * components change.
162      *
163      * @param listener a callback to be notified
164      */
165     override fun addCallback(listener: ControlsListingController.ControlsListingCallback) {
166         backgroundExecutor.execute {
167             if (userChangeInProgress.get() > 0) {
168                 // repost this event, as callers may rely on the initial callback from
169                 // onServicesUpdated
170                 addCallback(listener)
171             } else {
172                 val services = getCurrentServices()
173                 Log.d(TAG, "Subscribing callback, service count: ${services.size}")
174                 callbacks.add(listener)
175                 listener.onServicesUpdated(services)
176             }
177         }
178     }
179 
180     /**
181      * Removes a callback from this controller.
182      *
183      * @param listener the callback to be removed.
184      */
185     override fun removeCallback(listener: ControlsListingController.ControlsListingCallback) {
186         backgroundExecutor.execute {
187             Log.d(TAG, "Unsubscribing callback")
188             callbacks.remove(listener)
189         }
190     }
191 
192     /**
193      * @return a list of components that satisfy the requirements to be a
194      *         [ControlsProviderService]
195      */
196     override fun getCurrentServices(): List<ControlsServiceInfo> =
197             availableServices.map(ControlsServiceInfo::copy)
198 
199     @WorkerThread
200     override fun forceReload() {
201         val packageManager = context.packageManager
202         val intent = Intent(ControlsProviderService.SERVICE_CONTROLS)
203         val user = userTracker.userHandle
204         val flags = PackageManager.GET_SERVICES or
205                 PackageManager.GET_META_DATA or
206                 PackageManager.MATCH_DIRECT_BOOT_UNAWARE or
207                 PackageManager.MATCH_DIRECT_BOOT_AWARE
208         val services = packageManager.queryIntentServicesAsUser(
209                 intent,
210                 PackageManager.ResolveInfoFlags.of(flags.toLong()),
211                 user
212         ).map { ControlsServiceInfo(userTracker.userContext, it.serviceInfo) }
213         updateServices(services)
214     }
215 
216     /**
217      * Get the localized label for the component.
218      *
219      * @param name the name of the component
220      * @return a label as returned by [CandidateInfo.loadLabel] or `null`.
221      */
222     override fun getAppLabel(name: ComponentName): CharSequence? {
223         return availableServices.firstOrNull { it.componentName == name }
224                 ?.loadLabel()
225     }
226 
227     override fun dump(writer: PrintWriter, args: Array<out String>) {
228         writer.println("ControlsListingController:")
229         writer.asIndenting().indentIfPossible {
230             println("Callbacks: $callbacks")
231             println("Services: ${getCurrentServices()}")
232         }
233     }
234 }
235