Merge "Controls UI - Multi-user switch updates" into rvc-dev am: 7604a184d9 am: 44f3e98c7b
Change-Id: Id8dfa91e07e67df7b904b52347ffefcfd8bff16e
This commit is contained in:
@@ -72,6 +72,9 @@ class ControlsControllerImpl @Inject constructor (
|
|||||||
private const val USER_CHANGE_RETRY_DELAY = 500L // ms
|
private const val USER_CHANGE_RETRY_DELAY = 500L // ms
|
||||||
private const val DEFAULT_ENABLED = 1
|
private const val DEFAULT_ENABLED = 1
|
||||||
private const val PERMISSION_SELF = "com.android.systemui.permission.SELF"
|
private const val PERMISSION_SELF = "com.android.systemui.permission.SELF"
|
||||||
|
|
||||||
|
private fun isAvailable(userId: Int, cr: ContentResolver) = Settings.Secure.getIntForUser(
|
||||||
|
cr, CONTROLS_AVAILABLE, DEFAULT_ENABLED, userId) != 0
|
||||||
}
|
}
|
||||||
|
|
||||||
private var userChanging: Boolean = true
|
private var userChanging: Boolean = true
|
||||||
@@ -85,8 +88,7 @@ class ControlsControllerImpl @Inject constructor (
|
|||||||
|
|
||||||
private val contentResolver: ContentResolver
|
private val contentResolver: ContentResolver
|
||||||
get() = context.contentResolver
|
get() = context.contentResolver
|
||||||
override var available = Settings.Secure.getIntForUser(
|
override var available = isAvailable(currentUserId, contentResolver)
|
||||||
contentResolver, CONTROLS_AVAILABLE, DEFAULT_ENABLED, currentUserId) != 0
|
|
||||||
private set
|
private set
|
||||||
|
|
||||||
private val persistenceWrapper: ControlsFavoritePersistenceWrapper
|
private val persistenceWrapper: ControlsFavoritePersistenceWrapper
|
||||||
@@ -119,8 +121,7 @@ class ControlsControllerImpl @Inject constructor (
|
|||||||
BackupManager(userStructure.userContext)
|
BackupManager(userStructure.userContext)
|
||||||
)
|
)
|
||||||
auxiliaryPersistenceWrapper.changeFile(userStructure.auxiliaryFile)
|
auxiliaryPersistenceWrapper.changeFile(userStructure.auxiliaryFile)
|
||||||
available = Settings.Secure.getIntForUser(contentResolver, CONTROLS_AVAILABLE,
|
available = isAvailable(newUser.identifier, contentResolver)
|
||||||
DEFAULT_ENABLED, newUser.identifier) != 0
|
|
||||||
resetFavorites(available)
|
resetFavorites(available)
|
||||||
bindingController.changeUser(newUser)
|
bindingController.changeUser(newUser)
|
||||||
listingController.changeUser(newUser)
|
listingController.changeUser(newUser)
|
||||||
@@ -131,7 +132,6 @@ class ControlsControllerImpl @Inject constructor (
|
|||||||
override fun onReceive(context: Context, intent: Intent) {
|
override fun onReceive(context: Context, intent: Intent) {
|
||||||
if (intent.action == Intent.ACTION_USER_SWITCHED) {
|
if (intent.action == Intent.ACTION_USER_SWITCHED) {
|
||||||
userChanging = true
|
userChanging = true
|
||||||
listingController.removeCallback(listingCallback)
|
|
||||||
val newUser =
|
val newUser =
|
||||||
UserHandle.of(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, sendingUserId))
|
UserHandle.of(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, sendingUserId))
|
||||||
if (currentUser == newUser) {
|
if (currentUser == newUser) {
|
||||||
@@ -151,7 +151,6 @@ class ControlsControllerImpl @Inject constructor (
|
|||||||
executor.execute {
|
executor.execute {
|
||||||
Log.d(TAG, "Restore finished, storing auxiliary favorites")
|
Log.d(TAG, "Restore finished, storing auxiliary favorites")
|
||||||
auxiliaryPersistenceWrapper.initialize()
|
auxiliaryPersistenceWrapper.initialize()
|
||||||
listingController.removeCallback(listingCallback)
|
|
||||||
persistenceWrapper.storeFavorites(auxiliaryPersistenceWrapper.favorites)
|
persistenceWrapper.storeFavorites(auxiliaryPersistenceWrapper.favorites)
|
||||||
resetFavorites(available)
|
resetFavorites(available)
|
||||||
}
|
}
|
||||||
@@ -172,8 +171,7 @@ class ControlsControllerImpl @Inject constructor (
|
|||||||
if (userChanging || userId != currentUserId) {
|
if (userChanging || userId != currentUserId) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
available = Settings.Secure.getIntForUser(contentResolver, CONTROLS_AVAILABLE,
|
available = isAvailable(currentUserId, contentResolver)
|
||||||
DEFAULT_ENABLED, currentUserId) != 0
|
|
||||||
resetFavorites(available)
|
resetFavorites(available)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -244,6 +242,7 @@ class ControlsControllerImpl @Inject constructor (
|
|||||||
null
|
null
|
||||||
)
|
)
|
||||||
contentResolver.registerContentObserver(URI, false, settingObserver, UserHandle.USER_ALL)
|
contentResolver.registerContentObserver(URI, false, settingObserver, UserHandle.USER_ALL)
|
||||||
|
listingController.addCallback(listingCallback)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun destroy() {
|
fun destroy() {
|
||||||
@@ -258,7 +257,6 @@ class ControlsControllerImpl @Inject constructor (
|
|||||||
|
|
||||||
if (shouldLoad) {
|
if (shouldLoad) {
|
||||||
Favorites.load(persistenceWrapper.readFavorites())
|
Favorites.load(persistenceWrapper.readFavorites())
|
||||||
listingController.addCallback(listingCallback)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -569,12 +567,12 @@ class UserStructure(context: Context, user: UserHandle) {
|
|||||||
val userContext = context.createContextAsUser(user, 0)
|
val userContext = context.createContextAsUser(user, 0)
|
||||||
|
|
||||||
val file = Environment.buildPath(
|
val file = Environment.buildPath(
|
||||||
context.filesDir,
|
userContext.filesDir,
|
||||||
ControlsFavoritePersistenceWrapper.FILE_NAME
|
ControlsFavoritePersistenceWrapper.FILE_NAME
|
||||||
)
|
)
|
||||||
|
|
||||||
val auxiliaryFile = Environment.buildPath(
|
val auxiliaryFile = Environment.buildPath(
|
||||||
context.filesDir,
|
userContext.filesDir,
|
||||||
AuxiliaryPersistenceWrapper.AUXILIARY_FILE_NAME
|
AuxiliaryPersistenceWrapper.AUXILIARY_FILE_NAME
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ import com.android.settingslib.widget.CandidateInfo
|
|||||||
import com.android.systemui.controls.ControlsServiceInfo
|
import com.android.systemui.controls.ControlsServiceInfo
|
||||||
import com.android.systemui.dagger.qualifiers.Background
|
import com.android.systemui.dagger.qualifiers.Background
|
||||||
import java.util.concurrent.Executor
|
import java.util.concurrent.Executor
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
|
|
||||||
@@ -75,6 +76,7 @@ class ControlsListingControllerImpl @VisibleForTesting constructor(
|
|||||||
|
|
||||||
private var availableComponents = emptySet<ComponentName>()
|
private var availableComponents = emptySet<ComponentName>()
|
||||||
private var availableServices = emptyList<ServiceInfo>()
|
private var availableServices = emptyList<ServiceInfo>()
|
||||||
|
private var userChangeInProgress = AtomicInteger(0)
|
||||||
|
|
||||||
override var currentUserId = ActivityManager.getCurrentUser()
|
override var currentUserId = ActivityManager.getCurrentUser()
|
||||||
private set
|
private set
|
||||||
@@ -85,6 +87,7 @@ class ControlsListingControllerImpl @VisibleForTesting constructor(
|
|||||||
newServices.mapTo(mutableSetOf<ComponentName>(), { s -> s.getComponentName() })
|
newServices.mapTo(mutableSetOf<ComponentName>(), { s -> s.getComponentName() })
|
||||||
|
|
||||||
backgroundExecutor.execute {
|
backgroundExecutor.execute {
|
||||||
|
if (userChangeInProgress.get() > 0) return@execute
|
||||||
if (!newComponents.equals(availableComponents)) {
|
if (!newComponents.equals(availableComponents)) {
|
||||||
Log.d(TAG, "ServiceConfig reloaded, count: ${newComponents.size}")
|
Log.d(TAG, "ServiceConfig reloaded, count: ${newComponents.size}")
|
||||||
availableComponents = newComponents
|
availableComponents = newComponents
|
||||||
@@ -105,16 +108,11 @@ class ControlsListingControllerImpl @VisibleForTesting constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun changeUser(newUser: UserHandle) {
|
override fun changeUser(newUser: UserHandle) {
|
||||||
backgroundExecutor.execute {
|
userChangeInProgress.incrementAndGet()
|
||||||
serviceListing.setListening(false)
|
serviceListing.setListening(false)
|
||||||
|
|
||||||
// Notify all callbacks in order to clear their existing state prior to attaching
|
backgroundExecutor.execute {
|
||||||
// a new listener
|
if (userChangeInProgress.decrementAndGet() == 0) {
|
||||||
availableServices = emptyList()
|
|
||||||
callbacks.forEach {
|
|
||||||
it.onServicesUpdated(emptyList())
|
|
||||||
}
|
|
||||||
|
|
||||||
currentUserId = newUser.identifier
|
currentUserId = newUser.identifier
|
||||||
val contextForUser = context.createContextAsUser(newUser, 0)
|
val contextForUser = context.createContextAsUser(newUser, 0)
|
||||||
serviceListing = serviceListingBuilder(contextForUser)
|
serviceListing = serviceListingBuilder(contextForUser)
|
||||||
@@ -123,6 +121,7 @@ class ControlsListingControllerImpl @VisibleForTesting constructor(
|
|||||||
serviceListing.reload()
|
serviceListing.reload()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds a callback to this controller.
|
* Adds a callback to this controller.
|
||||||
@@ -134,12 +133,18 @@ class ControlsListingControllerImpl @VisibleForTesting constructor(
|
|||||||
*/
|
*/
|
||||||
override fun addCallback(listener: ControlsListingController.ControlsListingCallback) {
|
override fun addCallback(listener: ControlsListingController.ControlsListingCallback) {
|
||||||
backgroundExecutor.execute {
|
backgroundExecutor.execute {
|
||||||
|
if (userChangeInProgress.get() > 0) {
|
||||||
|
// repost this event, as callers may rely on the initial callback from
|
||||||
|
// onServicesUpdated
|
||||||
|
addCallback(listener)
|
||||||
|
} else {
|
||||||
val services = getCurrentServices()
|
val services = getCurrentServices()
|
||||||
Log.d(TAG, "Subscribing callback, service count: ${services.size}")
|
Log.d(TAG, "Subscribing callback, service count: ${services.size}")
|
||||||
callbacks.add(listener)
|
callbacks.add(listener)
|
||||||
listener.onServicesUpdated(services)
|
listener.onServicesUpdated(services)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Removes a callback from this controller.
|
* Removes a callback from this controller.
|
||||||
|
|||||||
@@ -796,24 +796,6 @@ class ControlsControllerImplTest : SysuiTestCase() {
|
|||||||
.getCachedFavoritesAndRemoveFor(TEST_COMPONENT_2)
|
.getCachedFavoritesAndRemoveFor(TEST_COMPONENT_2)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testListingCallbackNotListeningWhileReadingFavorites() {
|
|
||||||
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)
|
|
||||||
|
|
||||||
val inOrder = inOrder(persistenceWrapper, listingController)
|
|
||||||
|
|
||||||
inOrder.verify(listingController).removeCallback(listingCallbackCaptor.value)
|
|
||||||
inOrder.verify(persistenceWrapper).readFavorites()
|
|
||||||
inOrder.verify(listingController).addCallback(listingCallbackCaptor.value)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testSeedFavoritesForComponent() {
|
fun testSeedFavoritesForComponent() {
|
||||||
var succeeded = false
|
var succeeded = false
|
||||||
|
|||||||
@@ -65,7 +65,10 @@ class ControlsListingControllerImplTest : SysuiTestCase() {
|
|||||||
@Mock
|
@Mock
|
||||||
private lateinit var serviceInfo: ServiceInfo
|
private lateinit var serviceInfo: ServiceInfo
|
||||||
@Mock
|
@Mock
|
||||||
private lateinit var componentName: ComponentName
|
private lateinit var serviceInfo2: ServiceInfo
|
||||||
|
|
||||||
|
private var componentName = ComponentName("pkg1", "class1")
|
||||||
|
private var componentName2 = ComponentName("pkg2", "class2")
|
||||||
|
|
||||||
private val executor = FakeExecutor(FakeSystemClock())
|
private val executor = FakeExecutor(FakeSystemClock())
|
||||||
|
|
||||||
@@ -82,6 +85,7 @@ class ControlsListingControllerImplTest : SysuiTestCase() {
|
|||||||
MockitoAnnotations.initMocks(this)
|
MockitoAnnotations.initMocks(this)
|
||||||
|
|
||||||
`when`(serviceInfo.componentName).thenReturn(componentName)
|
`when`(serviceInfo.componentName).thenReturn(componentName)
|
||||||
|
`when`(serviceInfo2.componentName).thenReturn(componentName2)
|
||||||
|
|
||||||
val wrapper = object : ContextWrapper(mContext) {
|
val wrapper = object : ContextWrapper(mContext) {
|
||||||
override fun createContextAsUser(user: UserHandle, flags: Int): Context {
|
override fun createContextAsUser(user: UserHandle, flags: Int): Context {
|
||||||
@@ -179,7 +183,7 @@ class ControlsListingControllerImplTest : SysuiTestCase() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testChangeUserResetsExistingCallbackServices() {
|
fun testChangeUserSendsCorrectServiceUpdate() {
|
||||||
val list = listOf(serviceInfo)
|
val list = listOf(serviceInfo)
|
||||||
controller.addCallback(mockCallback)
|
controller.addCallback(mockCallback)
|
||||||
|
|
||||||
@@ -197,10 +201,21 @@ class ControlsListingControllerImplTest : SysuiTestCase() {
|
|||||||
assertEquals(1, captor.value.size)
|
assertEquals(1, captor.value.size)
|
||||||
|
|
||||||
reset(mockCallback)
|
reset(mockCallback)
|
||||||
|
reset(mockSL)
|
||||||
|
|
||||||
|
val updatedList = listOf(serviceInfo)
|
||||||
|
serviceListingCallbackCaptor.value.onServicesReloaded(updatedList)
|
||||||
controller.changeUser(UserHandle.of(otherUser))
|
controller.changeUser(UserHandle.of(otherUser))
|
||||||
executor.runAllReady()
|
executor.runAllReady()
|
||||||
assertEquals(otherUser, controller.currentUserId)
|
assertEquals(otherUser, controller.currentUserId)
|
||||||
|
|
||||||
|
// this event should was triggered just before the user change, and should
|
||||||
|
// be ignored
|
||||||
|
verify(mockCallback, never()).onServicesUpdated(any())
|
||||||
|
|
||||||
|
serviceListingCallbackCaptor.value.onServicesReloaded(emptyList<ServiceInfo>())
|
||||||
|
executor.runAllReady()
|
||||||
|
|
||||||
verify(mockCallback).onServicesUpdated(capture(captor))
|
verify(mockCallback).onServicesUpdated(capture(captor))
|
||||||
assertEquals(0, captor.value.size)
|
assertEquals(0, captor.value.size)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user