Merge "Remove favorites when app is uninstalled" into rvc-dev

This commit is contained in:
Fabian Kozynski
2020-03-03 14:03:13 +00:00
committed by Android (Google) Code Review
9 changed files with 145 additions and 6 deletions

View File

@@ -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.
*

View File

@@ -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")

View File

@@ -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(

View File

@@ -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) {

View File

@@ -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>)
}
}

View File

@@ -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) }
/**

View File

@@ -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) {

View File

@@ -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(

View File

@@ -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)
}
}