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