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