blob: 48f191d1280182a8bb30c3127524085f0621cdfc [file] [log] [blame]
Fabian Kozynskif10b6ab2019-12-27 09:31:04 -05001/*
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
17package com.android.systemui.controls.management
18
Fabian Kozynskia43c4b22020-02-24 15:43:42 -050019import android.app.ActivityManager
Fabian Kozynskif10b6ab2019-12-27 09:31:04 -050020import android.content.ComponentName
21import android.content.Context
22import android.content.pm.ServiceInfo
Fabian Kozynski7988bd42020-01-30 12:21:52 -050023import android.os.UserHandle
Fabian Kozynskif10b6ab2019-12-27 09:31:04 -050024import android.service.controls.ControlsProviderService
25import android.util.Log
26import com.android.internal.annotations.VisibleForTesting
Fabian Kozynskif10b6ab2019-12-27 09:31:04 -050027import com.android.settingslib.applications.ServiceListing
28import com.android.settingslib.widget.CandidateInfo
29import com.android.systemui.controls.ControlsServiceInfo
30import com.android.systemui.dagger.qualifiers.Background
31import java.util.concurrent.Executor
32import javax.inject.Inject
33import javax.inject.Singleton
34
Fabian Kozynski7988bd42020-01-30 12:21:52 -050035private fun createServiceListing(context: Context): ServiceListing {
36 return ServiceListing.Builder(context).apply {
37 setIntentAction(ControlsProviderService.SERVICE_CONTROLS)
38 setPermission("android.permission.BIND_CONTROLS")
39 setNoun("Controls Provider")
40 setSetting("controls_providers")
41 setTag("controls_providers")
Matt Pietalb0a9eef2020-04-29 09:33:29 -040042 setAddDeviceLockedFlags(true)
Fabian Kozynski7988bd42020-01-30 12:21:52 -050043 }.build()
44}
45
Fabian Kozynskif10b6ab2019-12-27 09:31:04 -050046/**
47 * Provides a listing of components to be used as ControlsServiceProvider.
48 *
49 * This controller keeps track of components that satisfy:
50 *
51 * * Has an intent-filter responding to [ControlsProviderService.CONTROLS_ACTION]
52 * * Has the bind permission `android.permission.BIND_CONTROLS`
53 */
54@Singleton
55class ControlsListingControllerImpl @VisibleForTesting constructor(
56 private val context: Context,
57 @Background private val backgroundExecutor: Executor,
Fabian Kozynski7988bd42020-01-30 12:21:52 -050058 private val serviceListingBuilder: (Context) -> ServiceListing
Fabian Kozynskif10b6ab2019-12-27 09:31:04 -050059) : ControlsListingController {
60
61 @Inject
62 constructor(context: Context, executor: Executor): this(
63 context,
64 executor,
Fabian Kozynski7988bd42020-01-30 12:21:52 -050065 ::createServiceListing
Fabian Kozynskif10b6ab2019-12-27 09:31:04 -050066 )
67
Fabian Kozynski7988bd42020-01-30 12:21:52 -050068 private var serviceListing = serviceListingBuilder(context)
Fabian Kozynski1a478622020-05-04 14:21:31 -040069 // All operations in background thread
70 private val callbacks = mutableSetOf<ControlsListingController.ControlsListingCallback>()
Fabian Kozynski7988bd42020-01-30 12:21:52 -050071
Fabian Kozynskif10b6ab2019-12-27 09:31:04 -050072 companion object {
73 private const val TAG = "ControlsListingControllerImpl"
74 }
75
Matt Pietalb0a9eef2020-04-29 09:33:29 -040076 private var availableComponents = emptySet<ComponentName>()
Fabian Kozynskif10b6ab2019-12-27 09:31:04 -050077 private var availableServices = emptyList<ServiceInfo>()
78
Fabian Kozynskia43c4b22020-02-24 15:43:42 -050079 override var currentUserId = ActivityManager.getCurrentUser()
Fabian Kozynski7988bd42020-01-30 12:21:52 -050080 private set
Fabian Kozynskif10b6ab2019-12-27 09:31:04 -050081
Fabian Kozynski7988bd42020-01-30 12:21:52 -050082 private val serviceListingCallback = ServiceListing.Callback {
Matt Pietalb0a9eef2020-04-29 09:33:29 -040083 val newServices = it.toList()
84 val newComponents =
85 newServices.mapTo(mutableSetOf<ComponentName>(), { s -> s.getComponentName() })
Fabian Kozynski7988bd42020-01-30 12:21:52 -050086
87 backgroundExecutor.execute {
Matt Pietalb0a9eef2020-04-29 09:33:29 -040088 if (!newComponents.equals(availableComponents)) {
89 Log.d(TAG, "ServiceConfig reloaded, count: ${newComponents.size}")
90 availableComponents = newComponents
91 availableServices = newServices
92 val currentServices = getCurrentServices()
93 callbacks.forEach {
94 it.onServicesUpdated(currentServices)
95 }
Fabian Kozynskif10b6ab2019-12-27 09:31:04 -050096 }
97 }
98 }
99
Fabian Kozynski7988bd42020-01-30 12:21:52 -0500100 init {
Matt Pietalb0a9eef2020-04-29 09:33:29 -0400101 Log.d(TAG, "Initializing")
Fabian Kozynski7988bd42020-01-30 12:21:52 -0500102 serviceListing.addCallback(serviceListingCallback)
Fabian Kozynski35086732020-03-10 14:25:32 -0400103 serviceListing.setListening(true)
104 serviceListing.reload()
Fabian Kozynski7988bd42020-01-30 12:21:52 -0500105 }
106
107 override fun changeUser(newUser: UserHandle) {
108 backgroundExecutor.execute {
109 callbacks.clear()
110 availableServices = emptyList()
111 serviceListing.setListening(false)
Fabian Kozynski7988bd42020-01-30 12:21:52 -0500112 currentUserId = newUser.identifier
113 val contextForUser = context.createContextAsUser(newUser, 0)
114 serviceListing = serviceListingBuilder(contextForUser)
115 serviceListing.addCallback(serviceListingCallback)
Fabian Kozynski35086732020-03-10 14:25:32 -0400116 serviceListing.setListening(true)
117 serviceListing.reload()
Fabian Kozynski7988bd42020-01-30 12:21:52 -0500118 }
119 }
120
Fabian Kozynskif10b6ab2019-12-27 09:31:04 -0500121 /**
122 * Adds a callback to this controller.
123 *
124 * The callback will be notified after it is added as well as any time that the valid
125 * components change.
126 *
127 * @param listener a callback to be notified
128 */
129 override fun addCallback(listener: ControlsListingController.ControlsListingCallback) {
130 backgroundExecutor.execute {
Matt Pietalb0a9eef2020-04-29 09:33:29 -0400131 val services = getCurrentServices()
132 Log.d(TAG, "Subscribing callback, service count: ${services.size}")
Fabian Kozynskif10b6ab2019-12-27 09:31:04 -0500133 callbacks.add(listener)
Matt Pietalb0a9eef2020-04-29 09:33:29 -0400134 listener.onServicesUpdated(services)
Fabian Kozynskif10b6ab2019-12-27 09:31:04 -0500135 }
136 }
137
138 /**
139 * Removes a callback from this controller.
140 *
141 * @param listener the callback to be removed.
142 */
143 override fun removeCallback(listener: ControlsListingController.ControlsListingCallback) {
144 backgroundExecutor.execute {
Fabian Kozynski7988bd42020-01-30 12:21:52 -0500145 Log.d(TAG, "Unsubscribing callback")
Fabian Kozynskif10b6ab2019-12-27 09:31:04 -0500146 callbacks.remove(listener)
Fabian Kozynskif10b6ab2019-12-27 09:31:04 -0500147 }
148 }
149
150 /**
151 * @return a list of components that satisfy the requirements to be a
152 * [ControlsProviderService]
153 */
Fabian Kozynski84371de2020-02-27 10:58:25 -0500154 override fun getCurrentServices(): List<ControlsServiceInfo> =
Fabian Kozynskif10b6ab2019-12-27 09:31:04 -0500155 availableServices.map { ControlsServiceInfo(context, it) }
156
157 /**
158 * Get the localized label for the component.
159 *
160 * @param name the name of the component
161 * @return a label as returned by [CandidateInfo.loadLabel] or `null`.
162 */
163 override fun getAppLabel(name: ComponentName): CharSequence? {
Matt Pietal638253a2020-03-02 09:10:43 -0500164 return getCurrentServices().firstOrNull { it.componentName == name }
Fabian Kozynskif10b6ab2019-12-27 09:31:04 -0500165 ?.loadLabel()
166 }
Matt Pietal638253a2020-03-02 09:10:43 -0500167}