Introduce a CustomIconCache for editing favorites am: 95e63154f8
Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/12345483 Change-Id: I49ee3391eee93919dce7785c7270a6dd6a382879
This commit is contained in:
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* 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.content.ComponentName
|
||||
import android.graphics.drawable.Icon
|
||||
import androidx.annotation.GuardedBy
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
/**
|
||||
* Icon cache for custom icons sent with controls.
|
||||
*
|
||||
* It assumes that only one component can be current at the time, to minimize the number of icons
|
||||
* stored at a given time.
|
||||
*/
|
||||
@Singleton
|
||||
class CustomIconCache @Inject constructor() {
|
||||
|
||||
private var currentComponent: ComponentName? = null
|
||||
@GuardedBy("cache")
|
||||
private val cache: MutableMap<String, Icon> = LinkedHashMap()
|
||||
|
||||
/**
|
||||
* Store an icon in the cache.
|
||||
*
|
||||
* If the icons currently stored do not correspond to the component to be stored, the cache is
|
||||
* cleared first.
|
||||
*/
|
||||
fun store(component: ComponentName, controlId: String, icon: Icon?) {
|
||||
if (component != currentComponent) {
|
||||
clear()
|
||||
currentComponent = component
|
||||
}
|
||||
synchronized(cache) {
|
||||
if (icon != null) {
|
||||
cache.put(controlId, icon)
|
||||
} else {
|
||||
cache.remove(controlId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a custom icon stored in the cache.
|
||||
*
|
||||
* It will return null if the component requested is not the one whose icons are stored, or if
|
||||
* there is no icon cached for that id.
|
||||
*/
|
||||
fun retrieve(component: ComponentName, controlId: String): Icon? {
|
||||
if (component != currentComponent) return null
|
||||
return synchronized(cache) {
|
||||
cache.get(controlId)
|
||||
}
|
||||
}
|
||||
|
||||
private fun clear() {
|
||||
synchronized(cache) {
|
||||
cache.clear()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -29,6 +29,7 @@ import androidx.recyclerview.widget.ItemTouchHelper
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.android.systemui.R
|
||||
import com.android.systemui.broadcast.BroadcastDispatcher
|
||||
import com.android.systemui.controls.CustomIconCache
|
||||
import com.android.systemui.controls.controller.ControlsControllerImpl
|
||||
import com.android.systemui.controls.controller.StructureInfo
|
||||
import com.android.systemui.globalactions.GlobalActionsComponent
|
||||
@@ -42,7 +43,8 @@ import javax.inject.Inject
|
||||
class ControlsEditingActivity @Inject constructor(
|
||||
private val controller: ControlsControllerImpl,
|
||||
broadcastDispatcher: BroadcastDispatcher,
|
||||
private val globalActionsComponent: GlobalActionsComponent
|
||||
private val globalActionsComponent: GlobalActionsComponent,
|
||||
private val customIconCache: CustomIconCache
|
||||
) : LifecycleActivity() {
|
||||
|
||||
companion object {
|
||||
@@ -170,7 +172,7 @@ class ControlsEditingActivity @Inject constructor(
|
||||
|
||||
private fun setUpList() {
|
||||
val controls = controller.getFavoritesForStructure(component, structure)
|
||||
model = FavoritesModel(component, controls, favoritesModelCallback)
|
||||
model = FavoritesModel(customIconCache, component, controls, favoritesModelCallback)
|
||||
val elevation = resources.getFloat(R.dimen.control_card_elevation)
|
||||
val recyclerView = requireViewById<RecyclerView>(R.id.list)
|
||||
recyclerView.alpha = 0.0f
|
||||
|
||||
@@ -114,11 +114,27 @@ data class ControlStatusWrapper(
|
||||
val controlStatus: ControlStatus
|
||||
) : ElementWrapper(), ControlInterface by controlStatus
|
||||
|
||||
private fun nullIconGetter(_a: ComponentName, _b: String): Icon? = null
|
||||
|
||||
data class ControlInfoWrapper(
|
||||
override val component: ComponentName,
|
||||
val controlInfo: ControlInfo,
|
||||
override var favorite: Boolean
|
||||
) : ElementWrapper(), ControlInterface {
|
||||
|
||||
var customIconGetter: (ComponentName, String) -> Icon? = ::nullIconGetter
|
||||
private set
|
||||
|
||||
// Separate constructor so the getter is not used in auto-generated methods
|
||||
constructor(
|
||||
component: ComponentName,
|
||||
controlInfo: ControlInfo,
|
||||
favorite: Boolean,
|
||||
customIconGetter: (ComponentName, String) -> Icon?
|
||||
): this(component, controlInfo, favorite) {
|
||||
this.customIconGetter = customIconGetter
|
||||
}
|
||||
|
||||
override val controlId: String
|
||||
get() = controlInfo.controlId
|
||||
override val title: CharSequence
|
||||
@@ -128,8 +144,7 @@ data class ControlInfoWrapper(
|
||||
override val deviceType: Int
|
||||
get() = controlInfo.deviceType
|
||||
override val customIcon: Icon?
|
||||
// Will need to address to support for edit activity
|
||||
get() = null
|
||||
get() = customIconGetter(component, controlId)
|
||||
}
|
||||
|
||||
data class DividerWrapper(
|
||||
|
||||
@@ -21,6 +21,7 @@ import android.util.Log
|
||||
import androidx.recyclerview.widget.ItemTouchHelper
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.android.systemui.controls.ControlInterface
|
||||
import com.android.systemui.controls.CustomIconCache
|
||||
import com.android.systemui.controls.controller.ControlInfo
|
||||
import java.util.Collections
|
||||
|
||||
@@ -35,6 +36,7 @@ import java.util.Collections
|
||||
* @property favoritesModelCallback callback to notify on first change and empty favorites
|
||||
*/
|
||||
class FavoritesModel(
|
||||
private val customIconCache: CustomIconCache,
|
||||
private val componentName: ComponentName,
|
||||
favorites: List<ControlInfo>,
|
||||
private val favoritesModelCallback: FavoritesModelCallback
|
||||
@@ -83,7 +85,7 @@ class FavoritesModel(
|
||||
}
|
||||
|
||||
override val elements: List<ElementWrapper> = favorites.map {
|
||||
ControlInfoWrapper(componentName, it, true)
|
||||
ControlInfoWrapper(componentName, it, true, customIconCache::retrieve)
|
||||
} + DividerWrapper()
|
||||
|
||||
/**
|
||||
|
||||
@@ -44,6 +44,7 @@ import android.widget.Space
|
||||
import android.widget.TextView
|
||||
import com.android.systemui.R
|
||||
import com.android.systemui.controls.ControlsServiceInfo
|
||||
import com.android.systemui.controls.CustomIconCache
|
||||
import com.android.systemui.controls.controller.ControlInfo
|
||||
import com.android.systemui.controls.controller.ControlsController
|
||||
import com.android.systemui.controls.controller.StructureInfo
|
||||
@@ -75,7 +76,8 @@ class ControlsUiControllerImpl @Inject constructor (
|
||||
@Main val sharedPreferences: SharedPreferences,
|
||||
val controlActionCoordinator: ControlActionCoordinator,
|
||||
private val activityStarter: ActivityStarter,
|
||||
private val shadeController: ShadeController
|
||||
private val shadeController: ShadeController,
|
||||
private val iconCache: CustomIconCache
|
||||
) : ControlsUiController {
|
||||
|
||||
companion object {
|
||||
@@ -502,6 +504,7 @@ class ControlsUiControllerImpl @Inject constructor (
|
||||
controls.forEach { c ->
|
||||
controlsById.get(ControlKey(componentName, c.getControlId()))?.let {
|
||||
Log.d(ControlsUiController.TAG, "onRefreshState() for id: " + c.getControlId())
|
||||
iconCache.store(componentName, c.controlId, c.customIcon)
|
||||
val cws = ControlWithState(componentName, it.ci, c)
|
||||
val key = ControlKey(componentName, c.getControlId())
|
||||
controlsById.put(key, cws)
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
/*
|
||||
* 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.content.ComponentName
|
||||
import android.graphics.drawable.Icon
|
||||
import android.testing.AndroidTestingRunner
|
||||
import androidx.test.filters.SmallTest
|
||||
import com.android.systemui.SysuiTestCase
|
||||
import org.junit.Assert.assertNull
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.mockito.Mock
|
||||
import org.mockito.MockitoAnnotations
|
||||
|
||||
@SmallTest
|
||||
@RunWith(AndroidTestingRunner::class)
|
||||
class CustomIconCacheTest : SysuiTestCase() {
|
||||
|
||||
companion object {
|
||||
private val TEST_COMPONENT1 = ComponentName.unflattenFromString("pkg/.cls1")!!
|
||||
private val TEST_COMPONENT2 = ComponentName.unflattenFromString("pkg/.cls2")!!
|
||||
private const val CONTROL_ID_1 = "TEST_CONTROL_1"
|
||||
private const val CONTROL_ID_2 = "TEST_CONTROL_2"
|
||||
}
|
||||
|
||||
@Mock(stubOnly = true)
|
||||
private lateinit var icon1: Icon
|
||||
@Mock(stubOnly = true)
|
||||
private lateinit var icon2: Icon
|
||||
private lateinit var customIconCache: CustomIconCache
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
MockitoAnnotations.initMocks(this)
|
||||
|
||||
customIconCache = CustomIconCache()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testIconStoredCorrectly() {
|
||||
customIconCache.store(TEST_COMPONENT1, CONTROL_ID_1, icon1)
|
||||
|
||||
assertTrue(icon1 === customIconCache.retrieve(TEST_COMPONENT1, CONTROL_ID_1))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testIconNotStoredReturnsNull() {
|
||||
customIconCache.store(TEST_COMPONENT1, CONTROL_ID_1, icon1)
|
||||
|
||||
assertNull(customIconCache.retrieve(TEST_COMPONENT1, CONTROL_ID_2))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testWrongComponentReturnsNull() {
|
||||
customIconCache.store(TEST_COMPONENT1, CONTROL_ID_1, icon1)
|
||||
|
||||
assertNull(customIconCache.retrieve(TEST_COMPONENT2, CONTROL_ID_1))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testChangeComponentOldComponentIsRemoved() {
|
||||
customIconCache.store(TEST_COMPONENT1, CONTROL_ID_1, icon1)
|
||||
customIconCache.store(TEST_COMPONENT2, CONTROL_ID_2, icon2)
|
||||
|
||||
assertNull(customIconCache.retrieve(TEST_COMPONENT1, CONTROL_ID_1))
|
||||
assertNull(customIconCache.retrieve(TEST_COMPONENT1, CONTROL_ID_2))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testChangeComponentCorrectIconRetrieved() {
|
||||
customIconCache.store(TEST_COMPONENT1, CONTROL_ID_1, icon1)
|
||||
customIconCache.store(TEST_COMPONENT2, CONTROL_ID_1, icon2)
|
||||
|
||||
assertTrue(icon2 === customIconCache.retrieve(TEST_COMPONENT2, CONTROL_ID_1))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testStoreNull() {
|
||||
customIconCache.store(TEST_COMPONENT1, CONTROL_ID_1, icon1)
|
||||
customIconCache.store(TEST_COMPONENT1, CONTROL_ID_1, null)
|
||||
|
||||
assertNull(customIconCache.retrieve(TEST_COMPONENT1, CONTROL_ID_1))
|
||||
}
|
||||
}
|
||||
@@ -22,6 +22,7 @@ import androidx.recyclerview.widget.RecyclerView
|
||||
import androidx.test.filters.SmallTest
|
||||
import com.android.systemui.SysuiTestCase
|
||||
import com.android.systemui.controls.ControlInterface
|
||||
import com.android.systemui.controls.CustomIconCache
|
||||
import com.android.systemui.controls.controller.ControlInfo
|
||||
import com.android.systemui.util.mockito.any
|
||||
import com.android.systemui.util.mockito.eq
|
||||
@@ -57,6 +58,8 @@ class FavoritesModelTest : SysuiTestCase() {
|
||||
private lateinit var callback: FavoritesModel.FavoritesModelCallback
|
||||
@Mock
|
||||
private lateinit var adapter: RecyclerView.Adapter<*>
|
||||
@Mock
|
||||
private lateinit var customIconCache: CustomIconCache
|
||||
private lateinit var model: FavoritesModel
|
||||
private lateinit var dividerWrapper: DividerWrapper
|
||||
|
||||
@@ -64,7 +67,7 @@ class FavoritesModelTest : SysuiTestCase() {
|
||||
fun setUp() {
|
||||
MockitoAnnotations.initMocks(this)
|
||||
|
||||
model = FavoritesModel(TEST_COMPONENT, INITIAL_FAVORITES, callback)
|
||||
model = FavoritesModel(customIconCache, TEST_COMPONENT, INITIAL_FAVORITES, callback)
|
||||
model.attachAdapter(adapter)
|
||||
dividerWrapper = model.elements.first { it is DividerWrapper } as DividerWrapper
|
||||
}
|
||||
@@ -89,7 +92,7 @@ class FavoritesModelTest : SysuiTestCase() {
|
||||
@Test
|
||||
fun testInitialElements() {
|
||||
val expected = INITIAL_FAVORITES.map {
|
||||
ControlInfoWrapper(TEST_COMPONENT, it, true)
|
||||
ControlInfoWrapper(TEST_COMPONENT, it, true, customIconCache::retrieve)
|
||||
} + DividerWrapper()
|
||||
assertEquals(expected, model.elements)
|
||||
}
|
||||
@@ -287,5 +290,13 @@ class FavoritesModelTest : SysuiTestCase() {
|
||||
verify(callback).onFirstChange()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testCacheCalledWhenGettingCustomIcon() {
|
||||
val wrapper = model.elements[0] as ControlInfoWrapper
|
||||
wrapper.customIcon
|
||||
|
||||
verify(customIconCache).retrieve(TEST_COMPONENT, wrapper.controlId)
|
||||
}
|
||||
|
||||
private fun getDividerPosition(): Int = model.elements.indexOf(dividerWrapper)
|
||||
}
|
||||
Reference in New Issue
Block a user