Merge "Remove favorites when app is uninstalled" into rvc-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
35886c0d47
@@ -72,6 +72,11 @@ interface ControlsBindingController : UserAwareController {
|
||||
*/
|
||||
fun unsubscribe()
|
||||
|
||||
/**
|
||||
* Notify this controller that this component has been removed (uninstalled).
|
||||
*/
|
||||
fun onComponentRemoved(componentName: ComponentName)
|
||||
|
||||
/**
|
||||
* Consumer for load calls.
|
||||
*
|
||||
|
||||
@@ -187,6 +187,18 @@ open class ControlsBindingControllerImpl @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
override fun onComponentRemoved(componentName: ComponentName) {
|
||||
backgroundExecutor.execute {
|
||||
synchronized(componentMap) {
|
||||
val removed = componentMap.remove(Key(componentName, currentUser))
|
||||
removed?.let {
|
||||
it.unbindService()
|
||||
tokenMap.remove(it.token)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return StringBuilder(" ControlsBindingController:\n").apply {
|
||||
append(" refreshing=${refreshing.get()}\n")
|
||||
|
||||
@@ -38,6 +38,7 @@ import com.android.internal.annotations.VisibleForTesting
|
||||
import com.android.systemui.Dumpable
|
||||
import com.android.systemui.broadcast.BroadcastDispatcher
|
||||
import com.android.systemui.controls.ControlStatus
|
||||
import com.android.systemui.controls.ControlsServiceInfo
|
||||
import com.android.systemui.controls.management.ControlsListingController
|
||||
import com.android.systemui.controls.ui.ControlsUiController
|
||||
import com.android.systemui.dagger.qualifiers.Background
|
||||
@@ -123,6 +124,7 @@ class ControlsControllerImpl @Inject constructor (
|
||||
override fun onReceive(context: Context, intent: Intent) {
|
||||
if (intent.action == Intent.ACTION_USER_SWITCHED) {
|
||||
userChanging = true
|
||||
listingController.removeCallback(listingCallback)
|
||||
val newUser =
|
||||
UserHandle.of(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, sendingUserId))
|
||||
if (currentUser == newUser) {
|
||||
@@ -153,6 +155,34 @@ class ControlsControllerImpl @Inject constructor (
|
||||
}
|
||||
}
|
||||
|
||||
// Handling of removed components
|
||||
|
||||
/**
|
||||
* Check if any component has been removed and if so, remove all its favorites.
|
||||
*
|
||||
* If some component has been removed, the new set of favorites will also be saved.
|
||||
*/
|
||||
private val listingCallback = object : ControlsListingController.ControlsListingCallback {
|
||||
override fun onServicesUpdated(candidates: List<ControlsServiceInfo>) {
|
||||
executor.execute {
|
||||
val candidateComponents = candidates.map(ControlsServiceInfo::componentName)
|
||||
synchronized(currentFavorites) {
|
||||
val components = currentFavorites.keys.toSet() // create a copy
|
||||
components.forEach {
|
||||
if (it !in candidateComponents) {
|
||||
currentFavorites.remove(it)
|
||||
bindingController.onComponentRemoved(it)
|
||||
}
|
||||
}
|
||||
// Check if something has been removed, if so, store the new list
|
||||
if (components.size > currentFavorites.size) {
|
||||
persistenceWrapper.storeFavorites(favoritesAsListLocked())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
dumpManager.registerDumpable(javaClass.name, this)
|
||||
if (available) {
|
||||
@@ -187,6 +217,7 @@ class ControlsControllerImpl @Inject constructor (
|
||||
currentFavorites.getOrPut(it.component, { mutableListOf() }).add(it)
|
||||
}
|
||||
}
|
||||
listingController.addCallback(listingCallback)
|
||||
}
|
||||
|
||||
override fun loadForComponent(
|
||||
|
||||
@@ -29,6 +29,7 @@ import androidx.recyclerview.widget.RecyclerView
|
||||
import com.android.settingslib.applications.DefaultAppInfo
|
||||
import com.android.settingslib.widget.CandidateInfo
|
||||
import com.android.systemui.R
|
||||
import com.android.systemui.controls.ControlsServiceInfo
|
||||
import java.text.Collator
|
||||
import java.util.concurrent.Executor
|
||||
|
||||
@@ -58,7 +59,7 @@ class AppAdapter(
|
||||
private var listOfServices = emptyList<CandidateInfo>()
|
||||
|
||||
private val callback = object : ControlsListingController.ControlsListingCallback {
|
||||
override fun onServicesUpdated(candidates: List<CandidateInfo>) {
|
||||
override fun onServicesUpdated(candidates: List<ControlsServiceInfo>) {
|
||||
backgroundExecutor.execute {
|
||||
val collator = Collator.getInstance(resources.configuration.locales[0])
|
||||
val localeComparator = compareBy<CandidateInfo, CharSequence>(collator) {
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
package com.android.systemui.controls.management
|
||||
|
||||
import android.content.ComponentName
|
||||
import com.android.settingslib.widget.CandidateInfo
|
||||
import com.android.systemui.controls.ControlsServiceInfo
|
||||
import com.android.systemui.controls.UserAwareController
|
||||
import com.android.systemui.statusbar.policy.CallbackController
|
||||
|
||||
@@ -31,7 +31,7 @@ interface ControlsListingController :
|
||||
/**
|
||||
* @return the current list of services that satisfies the [ServiceListing].
|
||||
*/
|
||||
fun getCurrentServices(): List<CandidateInfo>
|
||||
fun getCurrentServices(): List<ControlsServiceInfo>
|
||||
|
||||
/**
|
||||
* Get the app label for a given component.
|
||||
@@ -45,6 +45,6 @@ interface ControlsListingController :
|
||||
|
||||
@FunctionalInterface
|
||||
interface ControlsListingCallback {
|
||||
fun onServicesUpdated(candidates: List<CandidateInfo>)
|
||||
fun onServicesUpdated(candidates: List<ControlsServiceInfo>)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -147,7 +147,7 @@ class ControlsListingControllerImpl @VisibleForTesting constructor(
|
||||
* @return a list of components that satisfy the requirements to be a
|
||||
* [ControlsProviderService]
|
||||
*/
|
||||
override fun getCurrentServices(): List<CandidateInfo> =
|
||||
override fun getCurrentServices(): List<ControlsServiceInfo> =
|
||||
availableServices.map { ControlsServiceInfo(context, it) }
|
||||
|
||||
/**
|
||||
|
||||
@@ -42,6 +42,7 @@ import com.android.systemui.controls.management.ControlsProviderSelectorActivity
|
||||
import com.android.systemui.dagger.qualifiers.Background
|
||||
import com.android.systemui.dagger.qualifiers.Main
|
||||
import com.android.systemui.R
|
||||
import com.android.systemui.controls.ControlsServiceInfo
|
||||
import com.android.systemui.util.concurrency.DelayableExecutor
|
||||
|
||||
import dagger.Lazy
|
||||
@@ -128,7 +129,7 @@ class ControlsUiControllerImpl @Inject constructor (
|
||||
get() = controlsController.get().available
|
||||
|
||||
private val listingCallback = object : ControlsListingController.ControlsListingCallback {
|
||||
override fun onServicesUpdated(candidates: List<CandidateInfo>) {
|
||||
override fun onServicesUpdated(candidates: List<ControlsServiceInfo>) {
|
||||
bgExecutor.execute {
|
||||
val collator = Collator.getInstance(context.resources.configuration.locales[0])
|
||||
val localeComparator = compareBy<CandidateInfo, CharSequence>(collator) {
|
||||
|
||||
@@ -40,6 +40,7 @@ import org.mockito.Mockito.`when`
|
||||
import org.mockito.Mockito.mock
|
||||
import org.mockito.Mockito.never
|
||||
import org.mockito.Mockito.reset
|
||||
import org.mockito.Mockito.times
|
||||
import org.mockito.Mockito.verify
|
||||
import org.mockito.MockitoAnnotations
|
||||
|
||||
@@ -194,6 +195,24 @@ class ControlsBindingControllerImplTest : SysuiTestCase() {
|
||||
verify(provider2).unbindService()
|
||||
verify(provider1, never()).unbindService()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testComponentRemoved_existingIsUnbound() {
|
||||
controller.bindServices(listOf(
|
||||
TEST_COMPONENT_NAME_1,
|
||||
TEST_COMPONENT_NAME_2,
|
||||
TEST_COMPONENT_NAME_3
|
||||
))
|
||||
|
||||
controller.onComponentRemoved(TEST_COMPONENT_NAME_2)
|
||||
|
||||
executor.runAllReady()
|
||||
|
||||
providers.forEach {
|
||||
verify(it, if (it.componentName == TEST_COMPONENT_NAME_2) times(1) else never())
|
||||
.unbindService()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class TestableControlsBindingControllerImpl(
|
||||
|
||||
@@ -22,6 +22,7 @@ import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.content.ContextWrapper
|
||||
import android.content.Intent
|
||||
import android.content.pm.ServiceInfo
|
||||
import android.os.UserHandle
|
||||
import android.provider.Settings
|
||||
import android.service.controls.Control
|
||||
@@ -32,6 +33,7 @@ import androidx.test.filters.SmallTest
|
||||
import com.android.systemui.SysuiTestCase
|
||||
import com.android.systemui.broadcast.BroadcastDispatcher
|
||||
import com.android.systemui.controls.ControlStatus
|
||||
import com.android.systemui.controls.ControlsServiceInfo
|
||||
import com.android.systemui.controls.management.ControlsListingController
|
||||
import com.android.systemui.controls.ui.ControlsUiController
|
||||
import com.android.systemui.dump.DumpManager
|
||||
@@ -50,6 +52,7 @@ import org.mockito.Captor
|
||||
import org.mockito.Mock
|
||||
import org.mockito.Mockito
|
||||
import org.mockito.Mockito.`when`
|
||||
import org.mockito.Mockito.inOrder
|
||||
import org.mockito.Mockito.mock
|
||||
import org.mockito.Mockito.never
|
||||
import org.mockito.Mockito.reset
|
||||
@@ -82,6 +85,9 @@ class ControlsControllerImplTest : SysuiTestCase() {
|
||||
ArgumentCaptor<ControlsBindingController.LoadCallback>
|
||||
@Captor
|
||||
private lateinit var broadcastReceiverCaptor: ArgumentCaptor<BroadcastReceiver>
|
||||
@Captor
|
||||
private lateinit var listingCallbackCaptor:
|
||||
ArgumentCaptor<ControlsListingController.ControlsListingCallback>
|
||||
|
||||
private lateinit var delayableExecutor: FakeExecutor
|
||||
private lateinit var controller: ControlsControllerImpl
|
||||
@@ -139,6 +145,7 @@ class ControlsControllerImplTest : SysuiTestCase() {
|
||||
assertTrue(controller.available)
|
||||
verify(broadcastDispatcher).registerReceiver(
|
||||
capture(broadcastReceiverCaptor), any(), any(), eq(UserHandle.ALL))
|
||||
verify(listingController).addCallback(capture(listingCallbackCaptor))
|
||||
}
|
||||
|
||||
private fun builderFromInfo(controlInfo: ControlInfo): Control.StatelessBuilder {
|
||||
@@ -606,4 +613,67 @@ class ControlsControllerImplTest : SysuiTestCase() {
|
||||
assertEquals(2, controller.countFavoritesForComponent(TEST_COMPONENT))
|
||||
assertEquals(listOrder2, controller.getFavoritesForComponent(TEST_COMPONENT))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testPackageRemoved_noFavorites_noRemovals() {
|
||||
controller.changeFavoriteStatus(TEST_CONTROL_INFO, true)
|
||||
|
||||
val serviceInfo = mock(ServiceInfo::class.java)
|
||||
`when`(serviceInfo.componentName).thenReturn(TEST_COMPONENT)
|
||||
val info = ControlsServiceInfo(mContext, serviceInfo)
|
||||
|
||||
// Don't want to check what happens before this call
|
||||
reset(persistenceWrapper)
|
||||
listingCallbackCaptor.value.onServicesUpdated(listOf(info))
|
||||
|
||||
delayableExecutor.runAllReady()
|
||||
|
||||
verify(bindingController, never()).onComponentRemoved(any())
|
||||
|
||||
assertEquals(1, controller.getFavoriteControls().size)
|
||||
assertEquals(TEST_CONTROL_INFO, controller.getFavoriteControls()[0])
|
||||
|
||||
verify(persistenceWrapper, never()).storeFavorites(ArgumentMatchers.anyList())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testPackageRemoved_hasFavorites() {
|
||||
controller.changeFavoriteStatus(TEST_CONTROL_INFO, true)
|
||||
controller.changeFavoriteStatus(TEST_CONTROL_INFO_2, true)
|
||||
|
||||
val serviceInfo = mock(ServiceInfo::class.java)
|
||||
`when`(serviceInfo.componentName).thenReturn(TEST_COMPONENT)
|
||||
val info = ControlsServiceInfo(mContext, serviceInfo)
|
||||
|
||||
// Don't want to check what happens before this call
|
||||
reset(persistenceWrapper)
|
||||
listingCallbackCaptor.value.onServicesUpdated(listOf(info))
|
||||
|
||||
delayableExecutor.runAllReady()
|
||||
|
||||
verify(bindingController).onComponentRemoved(TEST_COMPONENT_2)
|
||||
|
||||
assertEquals(1, controller.getFavoriteControls().size)
|
||||
assertEquals(TEST_CONTROL_INFO, controller.getFavoriteControls()[0])
|
||||
|
||||
verify(persistenceWrapper).storeFavorites(ArgumentMatchers.anyList())
|
||||
}
|
||||
|
||||
@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)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user