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.content.ComponentName
20 import android.content.Context
21 import android.content.pm.ServiceInfo
22 import android.os.UserHandle
23 import android.service.controls.ControlsProviderService
24 import android.util.Log
25 import com.android.internal.annotations.VisibleForTesting
26 import com.android.settingslib.applications.ServiceListing
27 import com.android.settingslib.widget.CandidateInfo
28 import com.android.systemui.controls.ControlsServiceInfo
29 import com.android.systemui.dagger.SysUISingleton
30 import com.android.systemui.dagger.qualifiers.Background
31 import com.android.systemui.settings.UserTracker
32 import java.util.concurrent.Executor
33 import java.util.concurrent.atomic.AtomicInteger
34 import javax.inject.Inject
35 
36 private fun createServiceListing(context: Context): ServiceListing {
37     return ServiceListing.Builder(context).apply {
38         setIntentAction(ControlsProviderService.SERVICE_CONTROLS)
39         setPermission("android.permission.BIND_CONTROLS")
40         setNoun("Controls Provider")
41         setSetting("controls_providers")
42         setTag("controls_providers")
43         setAddDeviceLockedFlags(true)
44     }.build()
45 }
46 
47 /**
48  * Provides a listing of components to be used as ControlsServiceProvider.
49  *
50  * This controller keeps track of components that satisfy:
51  *
52  * * Has an intent-filter responding to [ControlsProviderService.CONTROLS_ACTION]
53  * * Has the bind permission `android.permission.BIND_CONTROLS`
54  */
55 @SysUISingleton
56 class ControlsListingControllerImpl @VisibleForTesting constructor(
57     private val context: Context,
58     @Background private val backgroundExecutor: Executor,
59     private val serviceListingBuilder: (Context) -> ServiceListing,
60     userTracker: UserTracker
61 ) : ControlsListingController {
62 
63     @Inject
64     constructor(context: Context, executor: Executor, userTracker: UserTracker): this(
65             context,
66             executor,
67             ::createServiceListing,
68             userTracker
69     )
70 
71     private var serviceListing = serviceListingBuilder(context)
72     // All operations in background thread
73     private val callbacks = mutableSetOf<ControlsListingController.ControlsListingCallback>()
74 
75     companion object {
76         private const val TAG = "ControlsListingControllerImpl"
77     }
78 
79     private var availableComponents = emptySet<ComponentName>()
80     private var availableServices = emptyList<ServiceInfo>()
81     private var userChangeInProgress = AtomicInteger(0)
82 
83     override var currentUserId = userTracker.userId
84         private set
85 
86     private val serviceListingCallback = ServiceListing.Callback {
87         val newServices = it.toList()
88         val newComponents =
89             newServices.mapTo(mutableSetOf<ComponentName>(), { s -> s.getComponentName() })
90 
91         backgroundExecutor.execute {
92             if (userChangeInProgress.get() > 0) return@execute
93             if (!newComponents.equals(availableComponents)) {
94                 Log.d(TAG, "ServiceConfig reloaded, count: ${newComponents.size}")
95                 availableComponents = newComponents
96                 availableServices = newServices
97                 val currentServices = getCurrentServices()
98                 callbacks.forEach {
99                     it.onServicesUpdated(currentServices)
100                 }
101             }
102         }
103     }
104 
105     init {
106         Log.d(TAG, "Initializing")
107         serviceListing.addCallback(serviceListingCallback)
108         serviceListing.setListening(true)
109         serviceListing.reload()
110     }
111 
112     override fun changeUser(newUser: UserHandle) {
113         userChangeInProgress.incrementAndGet()
114         serviceListing.setListening(false)
115 
116         backgroundExecutor.execute {
117             if (userChangeInProgress.decrementAndGet() == 0) {
118                 currentUserId = newUser.identifier
119                 val contextForUser = context.createContextAsUser(newUser, 0)
120                 serviceListing = serviceListingBuilder(contextForUser)
121                 serviceListing.addCallback(serviceListingCallback)
122                 serviceListing.setListening(true)
123                 serviceListing.reload()
124             }
125         }
126     }
127 
128     /**
129      * Adds a callback to this controller.
130      *
131      * The callback will be notified after it is added as well as any time that the valid
132      * components change.
133      *
134      * @param listener a callback to be notified
135      */
136     override fun addCallback(listener: ControlsListingController.ControlsListingCallback) {
137         backgroundExecutor.execute {
138             if (userChangeInProgress.get() > 0) {
139                 // repost this event, as callers may rely on the initial callback from
140                 // onServicesUpdated
141                 addCallback(listener)
142             } else {
143                 val services = getCurrentServices()
144                 Log.d(TAG, "Subscribing callback, service count: ${services.size}")
145                 callbacks.add(listener)
146                 listener.onServicesUpdated(services)
147             }
148         }
149     }
150 
151     /**
152      * Removes a callback from this controller.
153      *
154      * @param listener the callback to be removed.
155      */
156     override fun removeCallback(listener: ControlsListingController.ControlsListingCallback) {
157         backgroundExecutor.execute {
158             Log.d(TAG, "Unsubscribing callback")
159             callbacks.remove(listener)
160         }
161     }
162 
163     /**
164      * @return a list of components that satisfy the requirements to be a
165      *         [ControlsProviderService]
166      */
167     override fun getCurrentServices(): List<ControlsServiceInfo> =
168             availableServices.map { ControlsServiceInfo(context, it) }
169 
170     /**
171      * Get the localized label for the component.
172      *
173      * @param name the name of the component
174      * @return a label as returned by [CandidateInfo.loadLabel] or `null`.
175      */
176     override fun getAppLabel(name: ComponentName): CharSequence? {
177         return getCurrentServices().firstOrNull { it.componentName == name }
178                 ?.loadLabel()
179     }
180 }
181