summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--packages/SystemUI/src/com/android/systemui/controls/CustomIconCache.kt76
-rw-r--r--packages/SystemUI/src/com/android/systemui/controls/management/ControlsEditingActivity.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/controls/management/ControlsModel.kt19
-rw-r--r--packages/SystemUI/src/com/android/systemui/controls/management/FavoritesModel.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt5
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/controls/CustomIconCacheTest.kt101
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/controls/management/FavoritesModelTest.kt15
7 files changed, 218 insertions, 8 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/controls/CustomIconCache.kt b/packages/SystemUI/src/com/android/systemui/controls/CustomIconCache.kt
new file mode 100644
index 000000000000..cca0f1653757
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/controls/CustomIconCache.kt
@@ -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()
+ }
+ }
+} \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsEditingActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsEditingActivity.kt
index ff40a8a883ae..f68388d5db3f 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsEditingActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsEditingActivity.kt
@@ -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
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsModel.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsModel.kt
index 4ef64a5cddbf..ad0e7a541f98 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsModel.kt
@@ -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(
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/FavoritesModel.kt b/packages/SystemUI/src/com/android/systemui/controls/management/FavoritesModel.kt
index 524250134e9b..f9ce6362f4f8 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/FavoritesModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/FavoritesModel.kt
@@ -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()
/**
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
index 1eb7e2168a6a..5f75c96be128 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
@@ -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)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/CustomIconCacheTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/CustomIconCacheTest.kt
new file mode 100644
index 000000000000..4d0f2ed47495
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/CustomIconCacheTest.kt
@@ -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))
+ }
+} \ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/management/FavoritesModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/management/FavoritesModelTest.kt
index ce33a8d49fac..f0003ed603ab 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/management/FavoritesModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/management/FavoritesModelTest.kt
@@ -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)
} \ No newline at end of file