Add multi-user support to Controls
This commit adds multi-user support by doing the following: * Listening when user changes and switching the controllers user (and context). * Using activities that show for all users and are finished on user switched. The setting has to be enabled for each user separately. Also: * fixes calling subscribe when on load to the ControlsProviderService. * better dumps. Test: atest Test: check that files are not shared between users Test: check that user folder is removed when user is deleted Bug:147732882 Change-Id: I349b0136473016e6bd6b71e26045f11a839272d1
This commit is contained in:
@@ -646,6 +646,7 @@
|
||||
android:label="Controls Providers"
|
||||
android:theme="@style/Theme.SystemUI"
|
||||
android:exported="true"
|
||||
android:showForAllUsers="true"
|
||||
android:excludeFromRecents="true"
|
||||
android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation|keyboard|keyboardHidden"
|
||||
android:visibleToInstantApps="true">
|
||||
@@ -655,6 +656,7 @@
|
||||
android:parentActivityName=".controls.management.ControlsProviderSelectorActivity"
|
||||
android:theme="@style/Theme.SystemUI"
|
||||
android:excludeFromRecents="true"
|
||||
android:showForAllUsers="true"
|
||||
android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation|keyboard|keyboardHidden"
|
||||
android:visibleToInstantApps="true">
|
||||
</activity>
|
||||
|
||||
@@ -18,4 +18,8 @@ package com.android.systemui.controls
|
||||
|
||||
import android.service.controls.Control
|
||||
|
||||
data class ControlStatus(val control: Control, val favorite: Boolean, val removed: Boolean = false)
|
||||
data class ControlStatus(
|
||||
val control: Control,
|
||||
val favorite: Boolean,
|
||||
val removed: Boolean = false
|
||||
)
|
||||
@@ -22,7 +22,7 @@ import com.android.settingslib.applications.DefaultAppInfo
|
||||
|
||||
class ControlsServiceInfo(
|
||||
context: Context,
|
||||
serviceInfo: ServiceInfo
|
||||
val serviceInfo: ServiceInfo
|
||||
) : DefaultAppInfo(
|
||||
context,
|
||||
context.packageManager,
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright (C) 2020 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.systemui.controls
|
||||
|
||||
import android.os.UserHandle
|
||||
|
||||
interface UserAwareController {
|
||||
|
||||
fun changeUser(newUser: UserHandle) {}
|
||||
val currentUserId: Int
|
||||
}
|
||||
@@ -19,8 +19,9 @@ package com.android.systemui.controls.controller
|
||||
import android.content.ComponentName
|
||||
import android.service.controls.Control
|
||||
import android.service.controls.actions.ControlAction
|
||||
import com.android.systemui.controls.UserAwareController
|
||||
|
||||
interface ControlsBindingController {
|
||||
interface ControlsBindingController : UserAwareController {
|
||||
fun bindAndLoad(component: ComponentName, callback: (List<Control>) -> Unit)
|
||||
fun bindServices(components: List<ComponentName>)
|
||||
fun subscribe(controls: List<ControlInfo>)
|
||||
|
||||
@@ -19,6 +19,7 @@ package com.android.systemui.controls.controller
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.os.IBinder
|
||||
import android.os.UserHandle
|
||||
import android.service.controls.Control
|
||||
import android.service.controls.IControlsActionCallback
|
||||
import android.service.controls.IControlsLoadCallback
|
||||
@@ -50,12 +51,17 @@ open class ControlsBindingControllerImpl @Inject constructor(
|
||||
|
||||
private val refreshing = AtomicBoolean(false)
|
||||
|
||||
private var currentUser = context.user
|
||||
|
||||
override val currentUserId: Int
|
||||
get() = currentUser.identifier
|
||||
|
||||
@GuardedBy("componentMap")
|
||||
private val tokenMap: MutableMap<IBinder, ControlsProviderLifecycleManager> =
|
||||
ArrayMap<IBinder, ControlsProviderLifecycleManager>()
|
||||
@GuardedBy("componentMap")
|
||||
private val componentMap: MutableMap<ComponentName, ControlsProviderLifecycleManager> =
|
||||
ArrayMap<ComponentName, ControlsProviderLifecycleManager>()
|
||||
private val componentMap: MutableMap<Key, ControlsProviderLifecycleManager> =
|
||||
ArrayMap<Key, ControlsProviderLifecycleManager>()
|
||||
|
||||
private val loadCallbackService = object : IControlsLoadCallback.Stub() {
|
||||
override fun accept(token: IBinder, controls: MutableList<Control>) {
|
||||
@@ -103,6 +109,7 @@ open class ControlsBindingControllerImpl @Inject constructor(
|
||||
loadCallbackService,
|
||||
actionCallbackService,
|
||||
subscriberService,
|
||||
currentUser,
|
||||
component
|
||||
)
|
||||
}
|
||||
@@ -110,7 +117,7 @@ open class ControlsBindingControllerImpl @Inject constructor(
|
||||
private fun retrieveLifecycleManager(component: ComponentName):
|
||||
ControlsProviderLifecycleManager {
|
||||
synchronized(componentMap) {
|
||||
val provider = componentMap.getOrPut(component) {
|
||||
val provider = componentMap.getOrPut(Key(component, currentUser)) {
|
||||
createProviderManager(component)
|
||||
}
|
||||
tokenMap.putIfAbsent(provider.token, provider)
|
||||
@@ -137,7 +144,7 @@ open class ControlsBindingControllerImpl @Inject constructor(
|
||||
val providersWithFavorites = controlsByComponentName.keys
|
||||
synchronized(componentMap) {
|
||||
componentMap.forEach {
|
||||
if (it.key !in providersWithFavorites) {
|
||||
if (it.key.component !in providersWithFavorites) {
|
||||
backgroundExecutor.execute { it.value.unbindService() }
|
||||
}
|
||||
}
|
||||
@@ -167,6 +174,36 @@ open class ControlsBindingControllerImpl @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
override fun changeUser(newUser: UserHandle) {
|
||||
if (newUser == currentUser) return
|
||||
synchronized(componentMap) {
|
||||
unbindAllProvidersLocked() // unbind all providers from the old user
|
||||
}
|
||||
refreshing.set(false)
|
||||
currentUser = newUser
|
||||
}
|
||||
|
||||
private fun unbindAllProvidersLocked() {
|
||||
componentMap.values.forEach {
|
||||
if (it.user == currentUser) {
|
||||
it.unbindService()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return StringBuilder(" ControlsBindingController:\n").apply {
|
||||
append(" refreshing=${refreshing.get()}\n")
|
||||
append(" currentUser=$currentUser\n")
|
||||
append(" Providers:\n")
|
||||
synchronized(componentMap) {
|
||||
componentMap.values.forEach {
|
||||
append(" $it\n")
|
||||
}
|
||||
}
|
||||
}.toString()
|
||||
}
|
||||
|
||||
private abstract inner class CallbackRunnable(val token: IBinder) : Runnable {
|
||||
protected val provider: ControlsProviderLifecycleManager? =
|
||||
synchronized(componentMap) {
|
||||
@@ -183,6 +220,10 @@ open class ControlsBindingControllerImpl @Inject constructor(
|
||||
Log.e(TAG, "No provider found for token:$token")
|
||||
return
|
||||
}
|
||||
if (provider.user != currentUser) {
|
||||
Log.e(TAG, "User ${provider.user} is not current user")
|
||||
return
|
||||
}
|
||||
synchronized(componentMap) {
|
||||
if (token !in tokenMap.keys) {
|
||||
Log.e(TAG, "Provider for token:$token does not exist anymore")
|
||||
@@ -204,6 +245,10 @@ open class ControlsBindingControllerImpl @Inject constructor(
|
||||
if (!refreshing.get()) {
|
||||
Log.d(TAG, "onRefresh outside of window from:${provider?.componentName}")
|
||||
}
|
||||
if (provider?.user != currentUser) {
|
||||
Log.e(TAG, "User ${provider?.user} is not current user")
|
||||
return
|
||||
}
|
||||
provider?.let {
|
||||
lazyController.get().refreshStatus(it.componentName, control)
|
||||
}
|
||||
@@ -229,7 +274,7 @@ open class ControlsBindingControllerImpl @Inject constructor(
|
||||
) : CallbackRunnable(token) {
|
||||
override fun run() {
|
||||
provider?.let {
|
||||
Log.i(TAG, "onComplete receive from '${provider?.componentName}'")
|
||||
Log.i(TAG, "onComplete receive from '${provider.componentName}'")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -240,7 +285,7 @@ open class ControlsBindingControllerImpl @Inject constructor(
|
||||
) : CallbackRunnable(token) {
|
||||
override fun run() {
|
||||
provider?.let {
|
||||
Log.e(TAG, "onError receive from '${provider?.componentName}': $error")
|
||||
Log.e(TAG, "onError receive from '${provider.componentName}': $error")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -251,9 +296,15 @@ open class ControlsBindingControllerImpl @Inject constructor(
|
||||
@ControlAction.ResponseResult val response: Int
|
||||
) : CallbackRunnable(token) {
|
||||
override fun run() {
|
||||
if (provider?.user != currentUser) {
|
||||
Log.e(TAG, "User ${provider?.user} is not current user")
|
||||
return
|
||||
}
|
||||
provider?.let {
|
||||
lazyController.get().onActionResponse(it.componentName, controlId, response)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private data class Key(val component: ComponentName, val user: UserHandle)
|
||||
@@ -20,8 +20,9 @@ import android.content.ComponentName
|
||||
import android.service.controls.Control
|
||||
import android.service.controls.actions.ControlAction
|
||||
import com.android.systemui.controls.ControlStatus
|
||||
import com.android.systemui.controls.UserAwareController
|
||||
|
||||
interface ControlsController {
|
||||
interface ControlsController : UserAwareController {
|
||||
val available: Boolean
|
||||
|
||||
fun getFavoriteControls(): List<ControlInfo>
|
||||
|
||||
@@ -17,10 +17,13 @@
|
||||
package com.android.systemui.controls.controller
|
||||
|
||||
import android.app.PendingIntent
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import android.os.Environment
|
||||
import android.os.UserHandle
|
||||
import android.provider.Settings
|
||||
import android.service.controls.Control
|
||||
import android.service.controls.actions.ControlAction
|
||||
@@ -29,14 +32,17 @@ import android.util.Log
|
||||
import com.android.internal.annotations.GuardedBy
|
||||
import com.android.systemui.DumpController
|
||||
import com.android.systemui.Dumpable
|
||||
import com.android.systemui.broadcast.BroadcastDispatcher
|
||||
import com.android.systemui.controls.ControlStatus
|
||||
import com.android.systemui.controls.management.ControlsFavoritingActivity
|
||||
import com.android.systemui.controls.management.ControlsListingController
|
||||
import com.android.systemui.controls.ui.ControlsUiController
|
||||
import com.android.systemui.dagger.qualifiers.Background
|
||||
import com.android.systemui.util.concurrency.DelayableExecutor
|
||||
import java.io.FileDescriptor
|
||||
import java.io.PrintWriter
|
||||
import java.util.Optional
|
||||
import java.util.concurrent.TimeUnit
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@@ -46,35 +52,101 @@ class ControlsControllerImpl @Inject constructor (
|
||||
@Background private val executor: DelayableExecutor,
|
||||
private val uiController: ControlsUiController,
|
||||
private val bindingController: ControlsBindingController,
|
||||
private val optionalWrapper: Optional<ControlsFavoritePersistenceWrapper>,
|
||||
private val listingController: ControlsListingController,
|
||||
broadcastDispatcher: BroadcastDispatcher,
|
||||
optionalWrapper: Optional<ControlsFavoritePersistenceWrapper>,
|
||||
dumpController: DumpController
|
||||
) : Dumpable, ControlsController {
|
||||
|
||||
companion object {
|
||||
private const val TAG = "ControlsControllerImpl"
|
||||
const val CONTROLS_AVAILABLE = "systemui.controls_available"
|
||||
const val USER_CHANGE_RETRY_DELAY = 500L // ms
|
||||
}
|
||||
|
||||
override val available = Settings.Secure.getInt(
|
||||
// Map of map: ComponentName -> (String -> ControlInfo).
|
||||
// Only for current user
|
||||
@GuardedBy("currentFavorites")
|
||||
private val currentFavorites = ArrayMap<ComponentName, MutableMap<String, ControlInfo>>()
|
||||
|
||||
private var userChanging = true
|
||||
override var available = Settings.Secure.getInt(
|
||||
context.contentResolver, CONTROLS_AVAILABLE, 0) != 0
|
||||
val persistenceWrapper = optionalWrapper.orElseGet {
|
||||
private set
|
||||
|
||||
private var currentUser = context.user
|
||||
override val currentUserId
|
||||
get() = currentUser.identifier
|
||||
|
||||
private val persistenceWrapper = optionalWrapper.orElseGet {
|
||||
ControlsFavoritePersistenceWrapper(
|
||||
Environment.buildPath(
|
||||
context.filesDir,
|
||||
ControlsFavoritePersistenceWrapper.FILE_NAME),
|
||||
context.filesDir,
|
||||
ControlsFavoritePersistenceWrapper.FILE_NAME
|
||||
),
|
||||
executor
|
||||
)
|
||||
}
|
||||
|
||||
// Map of map: ComponentName -> (String -> ControlInfo)
|
||||
@GuardedBy("currentFavorites")
|
||||
private val currentFavorites = ArrayMap<ComponentName, MutableMap<String, ControlInfo>>()
|
||||
|
||||
init {
|
||||
private fun setValuesForUser(newUser: UserHandle) {
|
||||
Log.d(TAG, "Changing to user: $newUser")
|
||||
currentUser = newUser
|
||||
val userContext = context.createContextAsUser(currentUser, 0)
|
||||
val fileName = Environment.buildPath(
|
||||
userContext.filesDir, ControlsFavoritePersistenceWrapper.FILE_NAME)
|
||||
persistenceWrapper.changeFile(fileName)
|
||||
available = Settings.Secure.getIntForUser(
|
||||
context.contentResolver, CONTROLS_AVAILABLE, 0) != 0
|
||||
synchronized(currentFavorites) {
|
||||
currentFavorites.clear()
|
||||
}
|
||||
if (available) {
|
||||
dumpController.registerDumpable(this)
|
||||
loadFavorites()
|
||||
}
|
||||
bindingController.changeUser(newUser)
|
||||
listingController.changeUser(newUser)
|
||||
userChanging = false
|
||||
}
|
||||
|
||||
private val userSwitchReceiver = object : BroadcastReceiver() {
|
||||
override fun onReceive(context: Context, intent: Intent) {
|
||||
if (intent.action == Intent.ACTION_USER_SWITCHED) {
|
||||
userChanging = true
|
||||
val newUser =
|
||||
UserHandle.of(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, sendingUserId))
|
||||
if (currentUser == newUser) {
|
||||
userChanging = false
|
||||
return
|
||||
}
|
||||
setValuesForUser(newUser)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
dumpController.registerDumpable(this)
|
||||
if (available) {
|
||||
loadFavorites()
|
||||
}
|
||||
userChanging = false
|
||||
broadcastDispatcher.registerReceiver(
|
||||
userSwitchReceiver,
|
||||
IntentFilter(Intent.ACTION_USER_SWITCHED),
|
||||
executor,
|
||||
UserHandle.ALL
|
||||
)
|
||||
}
|
||||
|
||||
private fun confirmAvailability(): Boolean {
|
||||
if (userChanging) {
|
||||
Log.w(TAG, "Controls not available while user is changing")
|
||||
return false
|
||||
}
|
||||
if (!available) {
|
||||
Log.d(TAG, "Controls not available")
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
private fun loadFavorites() {
|
||||
@@ -91,8 +163,18 @@ class ControlsControllerImpl @Inject constructor (
|
||||
componentName: ComponentName,
|
||||
callback: (List<ControlStatus>) -> Unit
|
||||
) {
|
||||
if (!available) {
|
||||
Log.d(TAG, "Controls not available")
|
||||
if (!confirmAvailability()) {
|
||||
if (userChanging) {
|
||||
// Try again later, userChanging should not last forever. If so, we have bigger
|
||||
// problems
|
||||
executor.executeDelayed(
|
||||
{ loadForComponent(componentName, callback) },
|
||||
USER_CHANGE_RETRY_DELAY,
|
||||
TimeUnit.MILLISECONDS
|
||||
)
|
||||
} else {
|
||||
callback(emptyList())
|
||||
}
|
||||
return
|
||||
}
|
||||
bindingController.bindAndLoad(componentName) {
|
||||
@@ -158,10 +240,7 @@ class ControlsControllerImpl @Inject constructor (
|
||||
}
|
||||
|
||||
override fun subscribeToFavorites() {
|
||||
if (!available) {
|
||||
Log.d(TAG, "Controls not available")
|
||||
return
|
||||
}
|
||||
if (!confirmAvailability()) return
|
||||
// Make a copy of the favorites list
|
||||
val favorites = synchronized(currentFavorites) {
|
||||
currentFavorites.flatMap { it.value.values.toList() }
|
||||
@@ -170,18 +249,12 @@ class ControlsControllerImpl @Inject constructor (
|
||||
}
|
||||
|
||||
override fun unsubscribe() {
|
||||
if (!available) {
|
||||
Log.d(TAG, "Controls not available")
|
||||
return
|
||||
}
|
||||
if (!confirmAvailability()) return
|
||||
bindingController.unsubscribe()
|
||||
}
|
||||
|
||||
override fun changeFavoriteStatus(controlInfo: ControlInfo, state: Boolean) {
|
||||
if (!available) {
|
||||
Log.d(TAG, "Controls not available")
|
||||
return
|
||||
}
|
||||
if (!confirmAvailability()) return
|
||||
var changed = false
|
||||
val listOfControls = synchronized(currentFavorites) {
|
||||
if (state) {
|
||||
@@ -211,7 +284,7 @@ class ControlsControllerImpl @Inject constructor (
|
||||
}
|
||||
|
||||
override fun refreshStatus(componentName: ComponentName, control: Control) {
|
||||
if (!available) {
|
||||
if (!confirmAvailability()) {
|
||||
Log.d(TAG, "Controls not available")
|
||||
return
|
||||
}
|
||||
@@ -227,28 +300,24 @@ class ControlsControllerImpl @Inject constructor (
|
||||
}
|
||||
|
||||
override fun onActionResponse(componentName: ComponentName, controlId: String, response: Int) {
|
||||
if (!available) {
|
||||
Log.d(TAG, "Controls not available")
|
||||
return
|
||||
}
|
||||
if (!confirmAvailability()) return
|
||||
uiController.onActionResponse(componentName, controlId, response)
|
||||
}
|
||||
|
||||
override fun getFavoriteControls(): List<ControlInfo> {
|
||||
if (!available) {
|
||||
Log.d(TAG, "Controls not available")
|
||||
return emptyList()
|
||||
}
|
||||
if (!confirmAvailability()) return emptyList()
|
||||
synchronized(currentFavorites) {
|
||||
return favoritesAsListLocked()
|
||||
}
|
||||
}
|
||||
|
||||
override fun action(controlInfo: ControlInfo, action: ControlAction) {
|
||||
if (!confirmAvailability()) return
|
||||
bindingController.action(controlInfo, action)
|
||||
}
|
||||
|
||||
override fun clearFavorites() {
|
||||
if (!confirmAvailability()) return
|
||||
val changed = synchronized(currentFavorites) {
|
||||
currentFavorites.isNotEmpty().also {
|
||||
currentFavorites.clear()
|
||||
@@ -261,6 +330,9 @@ class ControlsControllerImpl @Inject constructor (
|
||||
|
||||
override fun dump(fd: FileDescriptor, pw: PrintWriter, args: Array<out String>) {
|
||||
pw.println("ControlsController state:")
|
||||
pw.println(" Available: $available")
|
||||
pw.println(" Changing users: $userChanging")
|
||||
pw.println(" Current user: ${currentUser.identifier}")
|
||||
pw.println(" Favorites:")
|
||||
synchronized(currentFavorites) {
|
||||
currentFavorites.forEach {
|
||||
@@ -269,5 +341,6 @@ class ControlsControllerImpl @Inject constructor (
|
||||
}
|
||||
}
|
||||
}
|
||||
pw.println(bindingController.toString())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,7 +16,6 @@
|
||||
|
||||
package com.android.systemui.controls.controller
|
||||
|
||||
import android.app.ActivityManager
|
||||
import android.content.ComponentName
|
||||
import android.util.AtomicFile
|
||||
import android.util.Log
|
||||
@@ -32,8 +31,8 @@ import java.io.FileNotFoundException
|
||||
import java.io.IOException
|
||||
|
||||
class ControlsFavoritePersistenceWrapper(
|
||||
val file: File,
|
||||
val executor: DelayableExecutor
|
||||
private var file: File,
|
||||
private var executor: DelayableExecutor
|
||||
) {
|
||||
|
||||
companion object {
|
||||
@@ -47,11 +46,13 @@ class ControlsFavoritePersistenceWrapper(
|
||||
private const val TAG_TYPE = "type"
|
||||
}
|
||||
|
||||
val currentUser: Int
|
||||
get() = ActivityManager.getCurrentUser()
|
||||
fun changeFile(fileName: File) {
|
||||
file = fileName
|
||||
}
|
||||
|
||||
fun storeFavorites(list: List<ControlInfo>) {
|
||||
executor.execute {
|
||||
Log.d(TAG, "Saving data to file: $file")
|
||||
val atomicFile = AtomicFile(file)
|
||||
val writer = try {
|
||||
atomicFile.startWrite()
|
||||
@@ -98,6 +99,7 @@ class ControlsFavoritePersistenceWrapper(
|
||||
return emptyList()
|
||||
}
|
||||
try {
|
||||
Log.d(TAG, "Reading data from file: $file")
|
||||
val parser = Xml.newPullParser()
|
||||
parser.setInput(reader, null)
|
||||
return parseXml(parser)
|
||||
@@ -111,7 +113,7 @@ class ControlsFavoritePersistenceWrapper(
|
||||
}
|
||||
|
||||
private fun parseXml(parser: XmlPullParser): List<ControlInfo> {
|
||||
var type: Int = 0
|
||||
var type = 0
|
||||
val infos = mutableListOf<ControlInfo>()
|
||||
while (parser.next().also { type = it } != XmlPullParser.END_DOCUMENT) {
|
||||
if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
|
||||
@@ -123,9 +125,9 @@ class ControlsFavoritePersistenceWrapper(
|
||||
parser.getAttributeValue(null, TAG_COMPONENT))
|
||||
val id = parser.getAttributeValue(null, TAG_ID)
|
||||
val title = parser.getAttributeValue(null, TAG_TITLE)
|
||||
val type = parser.getAttributeValue(null, TAG_TYPE)?.toInt()
|
||||
if (component != null && id != null && title != null && type != null) {
|
||||
infos.add(ControlInfo(component, id, title, type))
|
||||
val deviceType = parser.getAttributeValue(null, TAG_TYPE)?.toInt()
|
||||
if (component != null && id != null && title != null && deviceType != null) {
|
||||
infos.add(ControlInfo(component, id, title, deviceType))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ import android.os.Binder
|
||||
import android.os.Bundle
|
||||
import android.os.IBinder
|
||||
import android.os.RemoteException
|
||||
import android.os.UserHandle
|
||||
import android.service.controls.Control
|
||||
import android.service.controls.ControlsProviderService.CALLBACK_BUNDLE
|
||||
import android.service.controls.ControlsProviderService.CALLBACK_TOKEN
|
||||
@@ -46,6 +47,7 @@ class ControlsProviderLifecycleManager(
|
||||
private val loadCallbackService: IControlsLoadCallback.Stub,
|
||||
private val actionCallbackService: IControlsActionCallback.Stub,
|
||||
private val subscriberService: IControlsSubscriber.Stub,
|
||||
val user: UserHandle,
|
||||
val componentName: ComponentName
|
||||
) : IBinder.DeathRecipient {
|
||||
|
||||
@@ -96,7 +98,7 @@ class ControlsProviderLifecycleManager(
|
||||
}
|
||||
bindTryCount++
|
||||
try {
|
||||
isBound = context.bindService(intent, serviceConnection, BIND_FLAGS)
|
||||
isBound = context.bindServiceAsUser(intent, serviceConnection, BIND_FLAGS, user)
|
||||
} catch (e: SecurityException) {
|
||||
Log.e(TAG, "Failed to bind to service", e)
|
||||
isBound = false
|
||||
@@ -152,7 +154,9 @@ class ControlsProviderLifecycleManager(
|
||||
load()
|
||||
}
|
||||
queue.filter { it is Message.Subscribe }.flatMap { (it as Message.Subscribe).list }.run {
|
||||
subscribe(this)
|
||||
if (this.isNotEmpty()) {
|
||||
subscribe(this)
|
||||
}
|
||||
}
|
||||
queue.filter { it is Message.Action }.forEach {
|
||||
val msg = it as Message.Action
|
||||
@@ -286,6 +290,15 @@ class ControlsProviderLifecycleManager(
|
||||
maybeUnbindAndRemoveCallback()
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return StringBuilder("ControlsProviderLifecycleManager(").apply {
|
||||
append("component=$componentName")
|
||||
append(", user=$user")
|
||||
append(", bound=$isBound")
|
||||
append(")")
|
||||
}.toString()
|
||||
}
|
||||
|
||||
sealed class Message {
|
||||
abstract val type: Int
|
||||
object Load : Message() {
|
||||
@@ -301,4 +314,4 @@ class ControlsProviderLifecycleManager(
|
||||
override val type = MSG_ACTION
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -23,6 +23,7 @@ import android.view.ViewGroup
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.android.settingslib.widget.CandidateInfo
|
||||
import com.android.systemui.R
|
||||
|
||||
@@ -22,15 +22,18 @@ import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.android.systemui.broadcast.BroadcastDispatcher
|
||||
import com.android.systemui.controls.controller.ControlInfo
|
||||
import com.android.systemui.controls.controller.ControlsControllerImpl
|
||||
import com.android.systemui.dagger.qualifiers.Main
|
||||
import com.android.systemui.settings.CurrentUserTracker
|
||||
import java.util.concurrent.Executor
|
||||
import javax.inject.Inject
|
||||
|
||||
class ControlsFavoritingActivity @Inject constructor(
|
||||
@Main private val executor: Executor,
|
||||
private val controller: ControlsControllerImpl
|
||||
private val controller: ControlsControllerImpl,
|
||||
broadcastDispatcher: BroadcastDispatcher
|
||||
) : Activity() {
|
||||
|
||||
companion object {
|
||||
@@ -42,11 +45,24 @@ class ControlsFavoritingActivity @Inject constructor(
|
||||
private lateinit var recyclerView: RecyclerView
|
||||
private lateinit var adapter: ControlAdapter
|
||||
|
||||
private val currentUserTracker = object : CurrentUserTracker(broadcastDispatcher) {
|
||||
private val startingUser = controller.currentUserId
|
||||
|
||||
override fun onUserSwitched(newUserId: Int) {
|
||||
if (newUserId != startingUser) {
|
||||
stopTracking()
|
||||
finish()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private var component: ComponentName? = null
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
val app = intent.getCharSequenceExtra(EXTRA_APP)
|
||||
val component = intent.getParcelableExtra<ComponentName>(EXTRA_COMPONENT)
|
||||
component = intent.getParcelableExtra<ComponentName>(EXTRA_COMPONENT)
|
||||
|
||||
// If we have no component name, there's not much we can do.
|
||||
val callback = component?.let {
|
||||
@@ -68,6 +84,11 @@ class ControlsFavoritingActivity @Inject constructor(
|
||||
}
|
||||
setContentView(recyclerView)
|
||||
|
||||
currentUserTracker.startTracking()
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
component?.let {
|
||||
controller.loadForComponent(it) {
|
||||
executor.execute {
|
||||
|
||||
@@ -18,14 +18,17 @@ package com.android.systemui.controls.management
|
||||
|
||||
import android.content.ComponentName
|
||||
import com.android.settingslib.widget.CandidateInfo
|
||||
import com.android.systemui.controls.UserAwareController
|
||||
import com.android.systemui.statusbar.policy.CallbackController
|
||||
|
||||
interface ControlsListingController :
|
||||
CallbackController<ControlsListingController.ControlsListingCallback> {
|
||||
CallbackController<ControlsListingController.ControlsListingCallback>,
|
||||
UserAwareController {
|
||||
|
||||
fun getCurrentServices(): List<CandidateInfo>
|
||||
fun getAppLabel(name: ComponentName): CharSequence? = ""
|
||||
|
||||
@FunctionalInterface
|
||||
interface ControlsListingCallback {
|
||||
fun onServicesUpdated(list: List<CandidateInfo>)
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ package com.android.systemui.controls.management
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.content.pm.ServiceInfo
|
||||
import android.os.UserHandle
|
||||
import android.service.controls.ControlsProviderService
|
||||
import android.util.Log
|
||||
import com.android.internal.annotations.VisibleForTesting
|
||||
@@ -31,6 +32,16 @@ import java.util.concurrent.Executor
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
private fun createServiceListing(context: Context): ServiceListing {
|
||||
return ServiceListing.Builder(context).apply {
|
||||
setIntentAction(ControlsProviderService.SERVICE_CONTROLS)
|
||||
setPermission("android.permission.BIND_CONTROLS")
|
||||
setNoun("Controls Provider")
|
||||
setSetting("controls_providers")
|
||||
setTag("controls_providers")
|
||||
}.build()
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides a listing of components to be used as ControlsServiceProvider.
|
||||
*
|
||||
@@ -43,41 +54,55 @@ import javax.inject.Singleton
|
||||
class ControlsListingControllerImpl @VisibleForTesting constructor(
|
||||
private val context: Context,
|
||||
@Background private val backgroundExecutor: Executor,
|
||||
private val serviceListing: ServiceListing
|
||||
private val serviceListingBuilder: (Context) -> ServiceListing
|
||||
) : ControlsListingController {
|
||||
|
||||
@Inject
|
||||
constructor(context: Context, executor: Executor): this(
|
||||
context,
|
||||
executor,
|
||||
ServiceListing.Builder(context)
|
||||
.setIntentAction(ControlsProviderService.SERVICE_CONTROLS)
|
||||
.setPermission("android.permission.BIND_CONTROLS")
|
||||
.setNoun("Controls Provider")
|
||||
.setSetting("controls_providers")
|
||||
.setTag("controls_providers")
|
||||
.build()
|
||||
::createServiceListing
|
||||
)
|
||||
|
||||
private var serviceListing = serviceListingBuilder(context)
|
||||
|
||||
companion object {
|
||||
private const val TAG = "ControlsListingControllerImpl"
|
||||
}
|
||||
|
||||
private var availableServices = emptyList<ServiceInfo>()
|
||||
|
||||
init {
|
||||
serviceListing.addCallback {
|
||||
Log.d(TAG, "ServiceConfig reloaded")
|
||||
availableServices = it.toList()
|
||||
override var currentUserId = context.userId
|
||||
private set
|
||||
|
||||
backgroundExecutor.execute {
|
||||
callbacks.forEach {
|
||||
it.onServicesUpdated(getCurrentServices())
|
||||
}
|
||||
private val serviceListingCallback = ServiceListing.Callback {
|
||||
Log.d(TAG, "ServiceConfig reloaded")
|
||||
availableServices = it.toList()
|
||||
|
||||
backgroundExecutor.execute {
|
||||
callbacks.forEach {
|
||||
it.onServicesUpdated(getCurrentServices())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
serviceListing.addCallback(serviceListingCallback)
|
||||
}
|
||||
|
||||
override fun changeUser(newUser: UserHandle) {
|
||||
backgroundExecutor.execute {
|
||||
callbacks.clear()
|
||||
availableServices = emptyList()
|
||||
serviceListing.setListening(false)
|
||||
serviceListing.removeCallback(serviceListingCallback)
|
||||
currentUserId = newUser.identifier
|
||||
val contextForUser = context.createContextAsUser(newUser, 0)
|
||||
serviceListing = serviceListingBuilder(contextForUser)
|
||||
serviceListing.addCallback(serviceListingCallback)
|
||||
}
|
||||
}
|
||||
|
||||
// All operations in background thread
|
||||
private val callbacks = mutableSetOf<ControlsListingController.ControlsListingCallback>()
|
||||
|
||||
@@ -91,6 +116,7 @@ class ControlsListingControllerImpl @VisibleForTesting constructor(
|
||||
*/
|
||||
override fun addCallback(listener: ControlsListingController.ControlsListingCallback) {
|
||||
backgroundExecutor.execute {
|
||||
Log.d(TAG, "Subscribing callback")
|
||||
callbacks.add(listener)
|
||||
if (callbacks.size == 1) {
|
||||
serviceListing.setListening(true)
|
||||
@@ -108,6 +134,7 @@ class ControlsListingControllerImpl @VisibleForTesting constructor(
|
||||
*/
|
||||
override fun removeCallback(listener: ControlsListingController.ControlsListingCallback) {
|
||||
backgroundExecutor.execute {
|
||||
Log.d(TAG, "Unsubscribing callback")
|
||||
callbacks.remove(listener)
|
||||
if (callbacks.size == 0) {
|
||||
serviceListing.setListening(false)
|
||||
|
||||
@@ -22,7 +22,10 @@ import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.android.systemui.broadcast.BroadcastDispatcher
|
||||
import com.android.systemui.dagger.qualifiers.Background
|
||||
import com.android.systemui.dagger.qualifiers.Main
|
||||
import com.android.systemui.settings.CurrentUserTracker
|
||||
import com.android.systemui.util.LifecycleActivity
|
||||
import java.util.concurrent.Executor
|
||||
import javax.inject.Inject
|
||||
@@ -32,7 +35,9 @@ import javax.inject.Inject
|
||||
*/
|
||||
class ControlsProviderSelectorActivity @Inject constructor(
|
||||
@Main private val executor: Executor,
|
||||
private val listingController: ControlsListingController
|
||||
@Background private val backExecutor: Executor,
|
||||
private val listingController: ControlsListingController,
|
||||
broadcastDispatcher: BroadcastDispatcher
|
||||
) : LifecycleActivity() {
|
||||
|
||||
companion object {
|
||||
@@ -40,6 +45,16 @@ class ControlsProviderSelectorActivity @Inject constructor(
|
||||
}
|
||||
|
||||
private lateinit var recyclerView: RecyclerView
|
||||
private val currentUserTracker = object : CurrentUserTracker(broadcastDispatcher) {
|
||||
private val startingUser = listingController.currentUserId
|
||||
|
||||
override fun onUserSwitched(newUserId: Int) {
|
||||
if (newUserId != startingUser) {
|
||||
stopTracking()
|
||||
finish()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
@@ -50,6 +65,7 @@ class ControlsProviderSelectorActivity @Inject constructor(
|
||||
recyclerView.layoutManager = LinearLayoutManager(applicationContext)
|
||||
|
||||
setContentView(recyclerView)
|
||||
currentUserTracker.startTracking()
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -57,13 +73,17 @@ class ControlsProviderSelectorActivity @Inject constructor(
|
||||
* @param component a component name for a [ControlsProviderService]
|
||||
*/
|
||||
fun launchFavoritingActivity(component: ComponentName?) {
|
||||
component?.let {
|
||||
val intent = Intent(applicationContext, ControlsFavoritingActivity::class.java).apply {
|
||||
putExtra(ControlsFavoritingActivity.EXTRA_APP, listingController.getAppLabel(it))
|
||||
putExtra(ControlsFavoritingActivity.EXTRA_COMPONENT, it)
|
||||
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_SINGLE_TOP
|
||||
backExecutor.execute {
|
||||
component?.let {
|
||||
val intent = Intent(applicationContext, ControlsFavoritingActivity::class.java)
|
||||
.apply {
|
||||
putExtra(ControlsFavoritingActivity.EXTRA_APP,
|
||||
listingController.getAppLabel(it))
|
||||
putExtra(ControlsFavoritingActivity.EXTRA_COMPONENT, it)
|
||||
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_SINGLE_TOP
|
||||
}
|
||||
startActivity(intent)
|
||||
}
|
||||
startActivity(intent)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -19,6 +19,7 @@ package com.android.systemui.controls.controller
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.os.Binder
|
||||
import android.os.UserHandle
|
||||
import android.service.controls.Control
|
||||
import android.service.controls.DeviceTypes
|
||||
import android.testing.AndroidTestingRunner
|
||||
@@ -38,6 +39,7 @@ import org.mockito.Mockito
|
||||
import org.mockito.Mockito.`when`
|
||||
import org.mockito.Mockito.mock
|
||||
import org.mockito.Mockito.never
|
||||
import org.mockito.Mockito.reset
|
||||
import org.mockito.Mockito.verify
|
||||
import org.mockito.MockitoAnnotations
|
||||
|
||||
@@ -55,6 +57,9 @@ class ControlsBindingControllerTest : SysuiTestCase() {
|
||||
@Mock
|
||||
private lateinit var mockControlsController: ControlsController
|
||||
|
||||
private val user = UserHandle.of(mContext.userId)
|
||||
private val otherUser = UserHandle.of(user.identifier + 1)
|
||||
|
||||
private val executor = FakeExecutor(FakeSystemClock())
|
||||
private lateinit var controller: ControlsBindingController
|
||||
private val providers = TestableControlsBindingControllerImpl.providers
|
||||
@@ -74,6 +79,11 @@ class ControlsBindingControllerTest : SysuiTestCase() {
|
||||
providers.clear()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testStartOnUser() {
|
||||
assertEquals(user.identifier, controller.currentUserId)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testBindAndLoad() {
|
||||
val callback: (List<Control>) -> Unit = {}
|
||||
@@ -145,6 +155,41 @@ class ControlsBindingControllerTest : SysuiTestCase() {
|
||||
verify(it).unsubscribe()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testCurrentUserId() {
|
||||
controller.changeUser(otherUser)
|
||||
assertEquals(otherUser.identifier, controller.currentUserId)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testChangeUsers_providersHaveCorrectUser() {
|
||||
controller.bindServices(listOf(TEST_COMPONENT_NAME_1))
|
||||
controller.changeUser(otherUser)
|
||||
controller.bindServices(listOf(TEST_COMPONENT_NAME_2))
|
||||
|
||||
val provider1 = providers.first { it.componentName == TEST_COMPONENT_NAME_1 }
|
||||
assertEquals(user, provider1.user)
|
||||
val provider2 = providers.first { it.componentName == TEST_COMPONENT_NAME_2 }
|
||||
assertEquals(otherUser, provider2.user)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testChangeUsers_providersUnbound() {
|
||||
controller.bindServices(listOf(TEST_COMPONENT_NAME_1))
|
||||
controller.changeUser(otherUser)
|
||||
|
||||
val provider1 = providers.first { it.componentName == TEST_COMPONENT_NAME_1 }
|
||||
verify(provider1).unbindService()
|
||||
|
||||
controller.bindServices(listOf(TEST_COMPONENT_NAME_2))
|
||||
controller.changeUser(user)
|
||||
|
||||
reset(provider1)
|
||||
val provider2 = providers.first { it.componentName == TEST_COMPONENT_NAME_2 }
|
||||
verify(provider2).unbindService()
|
||||
verify(provider1, never()).unbindService()
|
||||
}
|
||||
}
|
||||
|
||||
class TestableControlsBindingControllerImpl(
|
||||
@@ -157,12 +202,16 @@ class TestableControlsBindingControllerImpl(
|
||||
val providers = mutableSetOf<ControlsProviderLifecycleManager>()
|
||||
}
|
||||
|
||||
// Replaces the real provider with a mock and puts the mock in a visible set.
|
||||
// The mock has the same componentName and user as the real one would have
|
||||
override fun createProviderManager(component: ComponentName):
|
||||
ControlsProviderLifecycleManager {
|
||||
val realProvider = super.createProviderManager(component)
|
||||
val provider = mock(ControlsProviderLifecycleManager::class.java)
|
||||
val token = Binder()
|
||||
`when`(provider.componentName).thenReturn(component)
|
||||
`when`(provider.componentName).thenReturn(realProvider.componentName)
|
||||
`when`(provider.token).thenReturn(token)
|
||||
`when`(provider.user).thenReturn(realProvider.user)
|
||||
providers.add(provider)
|
||||
return provider
|
||||
}
|
||||
|
||||
@@ -17,7 +17,12 @@
|
||||
package com.android.systemui.controls.controller
|
||||
|
||||
import android.app.PendingIntent
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.content.ContextWrapper
|
||||
import android.content.Intent
|
||||
import android.os.UserHandle
|
||||
import android.provider.Settings
|
||||
import android.service.controls.Control
|
||||
import android.service.controls.DeviceTypes
|
||||
@@ -26,6 +31,8 @@ import android.testing.AndroidTestingRunner
|
||||
import androidx.test.filters.SmallTest
|
||||
import com.android.systemui.DumpController
|
||||
import com.android.systemui.SysuiTestCase
|
||||
import com.android.systemui.broadcast.BroadcastDispatcher
|
||||
import com.android.systemui.controls.management.ControlsListingController
|
||||
import com.android.systemui.controls.ControlStatus
|
||||
import com.android.systemui.controls.ui.ControlsUiController
|
||||
import com.android.systemui.util.concurrency.FakeExecutor
|
||||
@@ -37,10 +44,11 @@ import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.mockito.ArgumentCaptor
|
||||
import org.mockito.ArgumentMatchers
|
||||
import org.mockito.ArgumentMatchers.eq
|
||||
import org.mockito.Captor
|
||||
import org.mockito.Mock
|
||||
import org.mockito.Mockito
|
||||
import org.mockito.Mockito.`when`
|
||||
import org.mockito.Mockito.mock
|
||||
import org.mockito.Mockito.never
|
||||
import org.mockito.Mockito.reset
|
||||
import org.mockito.Mockito.verify
|
||||
@@ -61,18 +69,25 @@ class ControlsControllerImplTest : SysuiTestCase() {
|
||||
private lateinit var pendingIntent: PendingIntent
|
||||
@Mock
|
||||
private lateinit var persistenceWrapper: ControlsFavoritePersistenceWrapper
|
||||
@Mock
|
||||
private lateinit var broadcastDispatcher: BroadcastDispatcher
|
||||
@Mock
|
||||
private lateinit var listingController: ControlsListingController
|
||||
|
||||
@Captor
|
||||
private lateinit var controlInfoListCaptor: ArgumentCaptor<List<ControlInfo>>
|
||||
@Captor
|
||||
private lateinit var controlLoadCallbackCaptor: ArgumentCaptor<(List<Control>) -> Unit>
|
||||
@Captor
|
||||
private lateinit var broadcastReceiverCaptor: ArgumentCaptor<BroadcastReceiver>
|
||||
|
||||
private lateinit var delayableExecutor: FakeExecutor
|
||||
private lateinit var controller: ControlsController
|
||||
|
||||
companion object {
|
||||
fun <T> capture(argumentCaptor: ArgumentCaptor<T>): T = argumentCaptor.capture()
|
||||
fun <T : Any> safeEq(value: T): T = eq(value) ?: value
|
||||
fun <T> eq(value: T): T = Mockito.eq(value) ?: value
|
||||
fun <T> any(): T = Mockito.any<T>()
|
||||
|
||||
private val TEST_COMPONENT = ComponentName("test.pkg", "test.class")
|
||||
private const val TEST_CONTROL_ID = "control1"
|
||||
@@ -89,24 +104,39 @@ class ControlsControllerImplTest : SysuiTestCase() {
|
||||
TEST_COMPONENT_2, TEST_CONTROL_ID_2, TEST_CONTROL_TITLE_2, TEST_DEVICE_TYPE_2)
|
||||
}
|
||||
|
||||
private val user = mContext.userId
|
||||
private val otherUser = user + 1
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
MockitoAnnotations.initMocks(this)
|
||||
|
||||
Settings.Secure.putInt(mContext.contentResolver,
|
||||
ControlsControllerImpl.CONTROLS_AVAILABLE, 1)
|
||||
Settings.Secure.putIntForUser(mContext.contentResolver,
|
||||
ControlsControllerImpl.CONTROLS_AVAILABLE, 1, otherUser)
|
||||
|
||||
delayableExecutor = FakeExecutor(FakeSystemClock())
|
||||
|
||||
val wrapper = object : ContextWrapper(mContext) {
|
||||
override fun createContextAsUser(user: UserHandle, flags: Int): Context {
|
||||
return baseContext
|
||||
}
|
||||
}
|
||||
|
||||
controller = ControlsControllerImpl(
|
||||
mContext,
|
||||
wrapper,
|
||||
delayableExecutor,
|
||||
uiController,
|
||||
bindingController,
|
||||
listingController,
|
||||
broadcastDispatcher,
|
||||
Optional.of(persistenceWrapper),
|
||||
dumpController
|
||||
)
|
||||
assertTrue(controller.available)
|
||||
verify(broadcastDispatcher).registerReceiver(
|
||||
capture(broadcastReceiverCaptor), any(), any(), eq(UserHandle.ALL))
|
||||
}
|
||||
|
||||
private fun builderFromInfo(controlInfo: ControlInfo): Control.StatelessBuilder {
|
||||
@@ -114,6 +144,11 @@ class ControlsControllerImplTest : SysuiTestCase() {
|
||||
.setDeviceType(controlInfo.deviceType).setTitle(controlInfo.controlTitle)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testStartOnUser() {
|
||||
assertEquals(user, controller.currentUserId)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testStartWithoutFavorites() {
|
||||
assertTrue(controller.getFavoriteControls().isEmpty())
|
||||
@@ -127,6 +162,8 @@ class ControlsControllerImplTest : SysuiTestCase() {
|
||||
delayableExecutor,
|
||||
uiController,
|
||||
bindingController,
|
||||
listingController,
|
||||
broadcastDispatcher,
|
||||
Optional.of(persistenceWrapper),
|
||||
dumpController
|
||||
)
|
||||
@@ -190,7 +227,7 @@ class ControlsControllerImplTest : SysuiTestCase() {
|
||||
controller.loadForComponent(TEST_COMPONENT) {}
|
||||
|
||||
reset(persistenceWrapper)
|
||||
verify(bindingController).bindAndLoad(safeEq(TEST_COMPONENT),
|
||||
verify(bindingController).bindAndLoad(eq(TEST_COMPONENT),
|
||||
capture(controlLoadCallbackCaptor))
|
||||
|
||||
controlLoadCallbackCaptor.value.invoke(listOf(control))
|
||||
@@ -262,7 +299,7 @@ class ControlsControllerImplTest : SysuiTestCase() {
|
||||
assertEquals(ControlStatus(control, false), controlStatus)
|
||||
}
|
||||
|
||||
verify(bindingController).bindAndLoad(safeEq(TEST_COMPONENT),
|
||||
verify(bindingController).bindAndLoad(eq(TEST_COMPONENT),
|
||||
capture(controlLoadCallbackCaptor))
|
||||
|
||||
controlLoadCallbackCaptor.value.invoke(listOf(control))
|
||||
@@ -287,7 +324,7 @@ class ControlsControllerImplTest : SysuiTestCase() {
|
||||
assertEquals(ControlStatus(control2, false), controlStatus2)
|
||||
}
|
||||
|
||||
verify(bindingController).bindAndLoad(safeEq(TEST_COMPONENT),
|
||||
verify(bindingController).bindAndLoad(eq(TEST_COMPONENT),
|
||||
capture(controlLoadCallbackCaptor))
|
||||
|
||||
controlLoadCallbackCaptor.value.invoke(listOf(control, control2))
|
||||
@@ -309,7 +346,7 @@ class ControlsControllerImplTest : SysuiTestCase() {
|
||||
assertTrue(controlStatus.removed)
|
||||
}
|
||||
|
||||
verify(bindingController).bindAndLoad(safeEq(TEST_COMPONENT),
|
||||
verify(bindingController).bindAndLoad(eq(TEST_COMPONENT),
|
||||
capture(controlLoadCallbackCaptor))
|
||||
|
||||
controlLoadCallbackCaptor.value.invoke(emptyList())
|
||||
@@ -325,7 +362,7 @@ class ControlsControllerImplTest : SysuiTestCase() {
|
||||
|
||||
controller.loadForComponent(TEST_COMPONENT) {}
|
||||
|
||||
verify(bindingController).bindAndLoad(safeEq(TEST_COMPONENT),
|
||||
verify(bindingController).bindAndLoad(eq(TEST_COMPONENT),
|
||||
capture(controlLoadCallbackCaptor))
|
||||
|
||||
controlLoadCallbackCaptor.value.invoke(listOf(control))
|
||||
@@ -358,4 +395,26 @@ class ControlsControllerImplTest : SysuiTestCase() {
|
||||
controller.clearFavorites()
|
||||
assertTrue(controller.getFavoriteControls().isEmpty())
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSwitchUsers() {
|
||||
controller.changeFavoriteStatus(TEST_CONTROL_INFO, true)
|
||||
|
||||
reset(persistenceWrapper)
|
||||
val intent = Intent(Intent.ACTION_USER_SWITCHED).apply {
|
||||
putExtra(Intent.EXTRA_USER_HANDLE, otherUser)
|
||||
}
|
||||
val pendingResult = mock(BroadcastReceiver.PendingResult::class.java)
|
||||
`when`(pendingResult.sendingUserId).thenReturn(otherUser)
|
||||
broadcastReceiverCaptor.value.pendingResult = pendingResult
|
||||
|
||||
broadcastReceiverCaptor.value.onReceive(mContext, intent)
|
||||
|
||||
verify(persistenceWrapper).changeFile(any())
|
||||
verify(persistenceWrapper).readFavorites()
|
||||
verify(bindingController).changeUser(UserHandle.of(otherUser))
|
||||
verify(listingController).changeUser(UserHandle.of(otherUser))
|
||||
assertTrue(controller.getFavoriteControls().isEmpty())
|
||||
assertEquals(otherUser, controller.currentUserId)
|
||||
}
|
||||
}
|
||||
@@ -17,6 +17,7 @@
|
||||
package com.android.systemui.controls.controller
|
||||
|
||||
import android.content.ComponentName
|
||||
import android.os.UserHandle
|
||||
import android.service.controls.Control
|
||||
import android.service.controls.IControlsActionCallback
|
||||
import android.service.controls.IControlsLoadCallback
|
||||
@@ -86,6 +87,7 @@ class ControlsProviderLifecycleManagerTest : SysuiTestCase() {
|
||||
loadCallback,
|
||||
actionCallback,
|
||||
subscriber,
|
||||
UserHandle.of(0),
|
||||
componentName
|
||||
)
|
||||
}
|
||||
@@ -148,4 +150,4 @@ class ControlsProviderLifecycleManagerTest : SysuiTestCase() {
|
||||
eq(actionCallback))
|
||||
assertEquals(action, wrapperCaptor.getValue().getWrappedAction())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -17,12 +17,15 @@
|
||||
package com.android.systemui.controls.management
|
||||
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.content.ContextWrapper
|
||||
import android.content.pm.ServiceInfo
|
||||
import android.os.UserHandle
|
||||
import android.testing.AndroidTestingRunner
|
||||
import androidx.test.filters.SmallTest
|
||||
import com.android.settingslib.applications.ServiceListing
|
||||
import com.android.settingslib.widget.CandidateInfo
|
||||
import com.android.systemui.SysuiTestCase
|
||||
import com.android.systemui.controls.ControlsServiceInfo
|
||||
import com.android.systemui.util.concurrency.FakeExecutor
|
||||
import com.android.systemui.util.time.FakeSystemClock
|
||||
import org.junit.After
|
||||
@@ -69,13 +72,22 @@ class ControlsListingControllerImplTest : SysuiTestCase() {
|
||||
private var serviceListingCallbackCaptor =
|
||||
ArgumentCaptor.forClass(ServiceListing.Callback::class.java)
|
||||
|
||||
private val user = mContext.userId
|
||||
private val otherUser = user + 1
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
MockitoAnnotations.initMocks(this)
|
||||
|
||||
`when`(serviceInfo.componentName).thenReturn(componentName)
|
||||
|
||||
controller = ControlsListingControllerImpl(mContext, executor, mockSL)
|
||||
val wrapper = object : ContextWrapper(mContext) {
|
||||
override fun createContextAsUser(user: UserHandle, flags: Int): Context {
|
||||
return baseContext
|
||||
}
|
||||
}
|
||||
|
||||
controller = ControlsListingControllerImpl(wrapper, executor, { mockSL })
|
||||
verify(mockSL).addCallback(capture(serviceListingCallbackCaptor))
|
||||
}
|
||||
|
||||
@@ -85,6 +97,11 @@ class ControlsListingControllerImplTest : SysuiTestCase() {
|
||||
executor.runAllReady()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testStartsOnUser() {
|
||||
assertEquals(user, controller.currentUserId)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testNoServices_notListening() {
|
||||
assertTrue(controller.getCurrentServices().isEmpty())
|
||||
@@ -167,8 +184,9 @@ class ControlsListingControllerImplTest : SysuiTestCase() {
|
||||
controller.addCallback(mockCallbackOther)
|
||||
|
||||
@Suppress("unchecked_cast")
|
||||
val captor: ArgumentCaptor<List<CandidateInfo>> =
|
||||
ArgumentCaptor.forClass(List::class.java) as ArgumentCaptor<List<CandidateInfo>>
|
||||
val captor: ArgumentCaptor<List<ControlsServiceInfo>> =
|
||||
ArgumentCaptor.forClass(List::class.java)
|
||||
as ArgumentCaptor<List<ControlsServiceInfo>>
|
||||
|
||||
executor.runAllReady()
|
||||
reset(mockCallback)
|
||||
@@ -185,4 +203,11 @@ class ControlsListingControllerImplTest : SysuiTestCase() {
|
||||
assertEquals(1, captor.value.size)
|
||||
assertEquals(componentName.flattenToString(), captor.value[0].key)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testChangeUser() {
|
||||
controller.changeUser(UserHandle.of(otherUser))
|
||||
executor.runAllReady()
|
||||
assertEquals(otherUser, controller.currentUserId)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user