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.flags.Flags 37 import com.android.systemui.settings.UserTracker 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 dumpManager: DumpManager, 71 private val featureFlags: FeatureFlags 72 ) : ControlsListingController, Dumpable { 73 74 @Inject 75 constructor( 76 context: Context, 77 @Background executor: Executor, 78 userTracker: UserTracker, 79 dumpManager: DumpManager, 80 featureFlags: FeatureFlags 81 ) : this(context, executor, ::createServiceListing, userTracker, dumpManager, featureFlags) 82 83 private var serviceListing = serviceListingBuilder(context) 84 // All operations in background thread 85 private val callbacks = mutableSetOf<ControlsListingController.ControlsListingCallback>() 86 87 companion object { 88 private const val TAG = "ControlsListingControllerImpl" 89 } 90 91 private var availableServices = emptyList<ControlsServiceInfo>() 92 private var userChangeInProgress = AtomicInteger(0) 93 94 override var currentUserId = userTracker.userId 95 private set 96 listnull97 private val serviceListingCallback = ServiceListing.Callback { list -> 98 Log.d(TAG, "ServiceConfig reloaded, count: ${list.size}") 99 val newServices = list.map { ControlsServiceInfo(userTracker.userContext, it) } 100 // After here, `list` is not captured, so we don't risk modifying it outside of the callback 101 backgroundExecutor.execute { 102 if (userChangeInProgress.get() > 0) return@execute 103 updateServices(newServices) 104 } 105 } 106 107 init { 108 Log.d(TAG, "Initializing") 109 dumpManager.registerDumpable(TAG, this) 110 serviceListing.addCallback(serviceListingCallback) 111 serviceListing.setListening(true) 112 serviceListing.reload() 113 } 114 updateServicesnull115 private fun updateServices(newServices: List<ControlsServiceInfo>) { 116 if (featureFlags.isEnabled(Flags.USE_APP_PANELS)) { 117 val allowAllApps = featureFlags.isEnabled(Flags.APP_PANELS_ALL_APPS_ALLOWED) 118 newServices.forEach { 119 it.resolvePanelActivity(allowAllApps) } 120 } 121 122 if (newServices != availableServices) { 123 availableServices = newServices 124 callbacks.forEach { 125 it.onServicesUpdated(getCurrentServices()) 126 } 127 } 128 } 129 changeUsernull130 override fun changeUser(newUser: UserHandle) { 131 userChangeInProgress.incrementAndGet() 132 serviceListing.setListening(false) 133 134 backgroundExecutor.execute { 135 if (userChangeInProgress.decrementAndGet() == 0) { 136 currentUserId = newUser.identifier 137 val contextForUser = context.createContextAsUser(newUser, 0) 138 serviceListing = serviceListingBuilder(contextForUser) 139 serviceListing.addCallback(serviceListingCallback) 140 serviceListing.setListening(true) 141 serviceListing.reload() 142 } 143 } 144 } 145 146 /** 147 * Adds a callback to this controller. 148 * 149 * The callback will be notified after it is added as well as any time that the valid 150 * components change. 151 * 152 * @param listener a callback to be notified 153 */ addCallbacknull154 override fun addCallback(listener: ControlsListingController.ControlsListingCallback) { 155 backgroundExecutor.execute { 156 if (userChangeInProgress.get() > 0) { 157 // repost this event, as callers may rely on the initial callback from 158 // onServicesUpdated 159 addCallback(listener) 160 } else { 161 val services = getCurrentServices() 162 Log.d(TAG, "Subscribing callback, service count: ${services.size}") 163 callbacks.add(listener) 164 listener.onServicesUpdated(services) 165 } 166 } 167 } 168 169 /** 170 * Removes a callback from this controller. 171 * 172 * @param listener the callback to be removed. 173 */ removeCallbacknull174 override fun removeCallback(listener: ControlsListingController.ControlsListingCallback) { 175 backgroundExecutor.execute { 176 Log.d(TAG, "Unsubscribing callback") 177 callbacks.remove(listener) 178 } 179 } 180 181 /** 182 * @return a list of components that satisfy the requirements to be a 183 * [ControlsProviderService] 184 */ getCurrentServicesnull185 override fun getCurrentServices(): List<ControlsServiceInfo> = 186 availableServices.map(ControlsServiceInfo::copy) 187 188 @WorkerThread 189 override fun forceReload() { 190 val packageManager = context.packageManager 191 val intent = Intent(ControlsProviderService.SERVICE_CONTROLS) 192 val user = userTracker.userHandle 193 val flags = PackageManager.GET_SERVICES or 194 PackageManager.GET_META_DATA or 195 PackageManager.MATCH_DIRECT_BOOT_UNAWARE or 196 PackageManager.MATCH_DIRECT_BOOT_AWARE 197 val services = packageManager.queryIntentServicesAsUser( 198 intent, 199 PackageManager.ResolveInfoFlags.of(flags.toLong()), 200 user 201 ).map { ControlsServiceInfo(userTracker.userContext, it.serviceInfo) } 202 updateServices(services) 203 } 204 205 /** 206 * Get the localized label for the component. 207 * 208 * @param name the name of the component 209 * @return a label as returned by [CandidateInfo.loadLabel] or `null`. 210 */ getAppLabelnull211 override fun getAppLabel(name: ComponentName): CharSequence? { 212 return availableServices.firstOrNull { it.componentName == name } 213 ?.loadLabel() 214 } 215 dumpnull216 override fun dump(writer: PrintWriter, args: Array<out String>) { 217 writer.println("ControlsListingController:") 218 writer.asIndenting().indentIfPossible { 219 println("Callbacks: $callbacks") 220 println("Services: ${getCurrentServices()}") 221 } 222 } 223 } 224