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