Skip to content

Commit

Permalink
Add userId check before loading icon in Device Controls
Browse files Browse the repository at this point in the history
Test: manual with the steps from the bug
Test: manual with a normal icon
Test: atest CanUseIconPredicate
Test: atest ControlViewHolderTest
Bug: 272025416
(cherry picked from https://googleplex-android-review.googlesource.com/q/commit:22f97f081ccc6f6a7230b15447a6c885dfe4fa59)
Merged-In: Ib0e677f7ccbed6299ea07939519c7dcf6d371bec
Change-Id: Ib0e677f7ccbed6299ea07939519c7dcf6d371bec
  • Loading branch information
Anton Potapov authored and thestinger committed Oct 3, 2023
1 parent 922a786 commit ed183ed
Show file tree
Hide file tree
Showing 11 changed files with 173 additions and 40 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import androidx.core.view.accessibility.AccessibilityNodeInfoCompat
import androidx.recyclerview.widget.RecyclerView
import com.android.systemui.R
import com.android.systemui.controls.ControlInterface
import com.android.systemui.controls.ui.CanUseIconPredicate
import com.android.systemui.controls.ui.RenderInfo

private typealias ModelFavoriteChanger = (String, Boolean) -> Unit
Expand All @@ -51,7 +52,8 @@ private typealias ModelFavoriteChanger = (String, Boolean) -> Unit
* @property elevation elevation of each control view
*/
class ControlAdapter(
private val elevation: Float
private val elevation: Float,
private val currentUserId: Int,
) : RecyclerView.Adapter<Holder>() {

companion object {
Expand Down Expand Up @@ -107,7 +109,8 @@ class ControlAdapter(
background = parent.context.getDrawable(
R.drawable.control_background_ripple)
},
model?.moveHelper // Indicates that position information is needed
currentUserId,
model?.moveHelper, // Indicates that position information is needed
) { id, favorite ->
model?.changeFavoriteStatus(id, favorite)
}
Expand Down Expand Up @@ -212,8 +215,9 @@ private class ZoneHolder(view: View) : Holder(view) {
*/
internal class ControlHolder(
view: View,
currentUserId: Int,
val moveHelper: ControlsModel.MoveHelper?,
val favoriteCallback: ModelFavoriteChanger
val favoriteCallback: ModelFavoriteChanger,
) : Holder(view) {
private val favoriteStateDescription =
itemView.context.getString(R.string.accessibility_control_favorite)
Expand All @@ -228,6 +232,7 @@ internal class ControlHolder(
visibility = View.VISIBLE
}

private val canUseIconPredicate = CanUseIconPredicate(currentUserId)
private val accessibilityDelegate = ControlHolderAccessibilityDelegate(
this::stateDescription,
this::getLayoutPosition,
Expand Down Expand Up @@ -287,7 +292,9 @@ internal class ControlHolder(
val fg = context.getResources().getColorStateList(ri.foreground, context.getTheme())

icon.imageTintList = null
ci.customIcon?.let {
ci.customIcon
?.takeIf(canUseIconPredicate)
?.let {
icon.setImageIcon(it)
} ?: run {
icon.setImageDrawable(ri.icon)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,7 @@ open class ControlsEditingActivity @Inject constructor(
val elevation = resources.getFloat(R.dimen.control_card_elevation)
val recyclerView = requireViewById<RecyclerView>(R.id.list)
recyclerView.alpha = 0.0f
val adapter = ControlAdapter(elevation).apply {
val adapter = ControlAdapter(elevation, controller.currentUserId).apply {
registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() {
var hasAnimated = false
override fun onChanged() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ open class ControlsFavoritingActivity @Inject constructor(
}

executor.execute {
structurePager.adapter = StructureAdapter(listOfStructures)
structurePager.adapter = StructureAdapter(listOfStructures, controller.currentUserId)
structurePager.setCurrentItem(structureIndex)
if (error) {
statusText.text = resources.getString(R.string.controls_favorite_load_error,
Expand Down Expand Up @@ -221,7 +221,7 @@ open class ControlsFavoritingActivity @Inject constructor(
structurePager.alpha = 0.0f
pageIndicator.alpha = 0.0f
structurePager.apply {
adapter = StructureAdapter(emptyList())
adapter = StructureAdapter(emptyList(), controller.currentUserId)
registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {
override fun onPageSelected(position: Int) {
super.onPageSelected(position)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,15 @@ import androidx.recyclerview.widget.RecyclerView
import com.android.systemui.R

class StructureAdapter(
private val models: List<StructureContainer>
private val models: List<StructureContainer>,
private val currentUserId: Int,
) : RecyclerView.Adapter<StructureAdapter.StructureHolder>() {

override fun onCreateViewHolder(parent: ViewGroup, p1: Int): StructureHolder {
val layoutInflater = LayoutInflater.from(parent.context)
return StructureHolder(
layoutInflater.inflate(R.layout.controls_structure_page, parent, false)
layoutInflater.inflate(R.layout.controls_structure_page, parent, false),
currentUserId,
)
}

Expand All @@ -40,15 +42,16 @@ class StructureAdapter(
holder.bind(models[index].model)
}

class StructureHolder(view: View) : RecyclerView.ViewHolder(view) {
class StructureHolder(view: View, currentUserId: Int) :
RecyclerView.ViewHolder(view) {

private val recyclerView: RecyclerView
private val controlAdapter: ControlAdapter

init {
recyclerView = itemView.requireViewById<RecyclerView>(R.id.listAll)
val elevation = itemView.context.resources.getFloat(R.dimen.control_card_elevation)
controlAdapter = ControlAdapter(elevation)
controlAdapter = ControlAdapter(elevation, currentUserId)
setUpRecyclerView()
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*
* Copyright (C) 2023 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.ui

import android.content.ContentProvider
import android.graphics.drawable.Icon

class CanUseIconPredicate(private val currentUserId: Int) : (Icon) -> Boolean {

override fun invoke(icon: Icon): Boolean =
if (icon.type == Icon.TYPE_URI || icon.type == Icon.TYPE_URI_ADAPTIVE_BITMAP) {
ContentProvider.getUserIdFromUri(icon.uri, currentUserId) == currentUserId
} else {
true
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,8 @@ class ControlViewHolder(
val bgExecutor: DelayableExecutor,
val controlActionCoordinator: ControlActionCoordinator,
val controlsMetricsLogger: ControlsMetricsLogger,
val uid: Int
val uid: Int,
val currentUserId: Int,
) {

companion object {
Expand All @@ -85,29 +86,9 @@ class ControlViewHolder(
private val ATTR_DISABLED = intArrayOf(-android.R.attr.state_enabled)
const val MIN_LEVEL = 0
const val MAX_LEVEL = 10000

fun findBehaviorClass(
status: Int,
template: ControlTemplate,
deviceType: Int
): Supplier<out Behavior> {
return when {
status != Control.STATUS_OK -> Supplier { StatusBehavior() }
template == ControlTemplate.NO_TEMPLATE -> Supplier { TouchBehavior() }
template is ThumbnailTemplate -> Supplier { ThumbnailBehavior() }

// Required for legacy support, or where cameras do not use the new template
deviceType == DeviceTypes.TYPE_CAMERA -> Supplier { TouchBehavior() }
template is ToggleTemplate -> Supplier { ToggleBehavior() }
template is StatelessTemplate -> Supplier { TouchBehavior() }
template is ToggleRangeTemplate -> Supplier { ToggleRangeBehavior() }
template is RangeTemplate -> Supplier { ToggleRangeBehavior() }
template is TemperatureControlTemplate -> Supplier { TemperatureControlBehavior() }
else -> Supplier { DefaultBehavior() }
}
}
}

private val canUseIconPredicate = CanUseIconPredicate(currentUserId)
private val toggleBackgroundIntensity: Float = layout.context.resources
.getFraction(R.fraction.controls_toggle_bg_intensity, 1, 1)
private var stateAnimator: ValueAnimator? = null
Expand Down Expand Up @@ -147,6 +128,27 @@ class ControlViewHolder(
status.setSelected(true)
}

fun findBehaviorClass(
status: Int,
template: ControlTemplate,
deviceType: Int
): Supplier<out Behavior> {
return when {
status != Control.STATUS_OK -> Supplier { StatusBehavior() }
template == ControlTemplate.NO_TEMPLATE -> Supplier { TouchBehavior() }
template is ThumbnailTemplate -> Supplier { ThumbnailBehavior(currentUserId) }

// Required for legacy support, or where cameras do not use the new template
deviceType == DeviceTypes.TYPE_CAMERA -> Supplier { TouchBehavior() }
template is ToggleTemplate -> Supplier { ToggleBehavior() }
template is StatelessTemplate -> Supplier { TouchBehavior() }
template is ToggleRangeTemplate -> Supplier { ToggleRangeBehavior() }
template is RangeTemplate -> Supplier { ToggleRangeBehavior() }
template is TemperatureControlTemplate -> Supplier { TemperatureControlBehavior() }
else -> Supplier { DefaultBehavior() }
}
}

fun bindData(cws: ControlWithState, isLocked: Boolean) {
// If an interaction is in progress, the update may visually interfere with the action the
// action the user wants to make. Don't apply the update, and instead assume a new update
Expand Down Expand Up @@ -473,7 +475,9 @@ class ControlViewHolder(

status.setTextColor(color)

control?.getCustomIcon()?.let {
control?.customIcon
?.takeIf(canUseIconPredicate)
?.let {
icon.setImageIcon(it)
icon.imageTintList = it.tintList
} ?: run {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -685,7 +685,8 @@ class ControlsUiControllerImpl @Inject constructor (
bgExecutor,
controlActionCoordinator,
controlsMetricsLogger,
selected.uid
selected.uid,
controlsController.get().currentUserId,
)
cvh.bindData(it, false /* isLocked, will be ignored on initial load */)
controlViewsById.put(key, cvh)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ class TemperatureControlBehavior : Behavior {
// interactions (touch, range)
subBehavior = cvh.bindBehavior(
subBehavior,
ControlViewHolder.findBehaviorClass(
cvh.findBehaviorClass(
control.status,
subTemplate,
control.deviceType
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ import com.android.systemui.controls.ui.ControlViewHolder.Companion.MIN_LEVEL
* Supports display of static images on the background of the tile. When marked active, the title
* and subtitle will not be visible. To be used with {@link Thumbnailtemplate} only.
*/
class ThumbnailBehavior : Behavior {
class ThumbnailBehavior(currentUserId: Int) : Behavior {
lateinit var template: ThumbnailTemplate
lateinit var control: Control
lateinit var cvh: ControlViewHolder
Expand All @@ -42,6 +42,7 @@ class ThumbnailBehavior : Behavior {
private var shadowRadius: Float = 0f
private var shadowColor: Int = 0

private val canUseIconPredicate = CanUseIconPredicate(currentUserId)
private val enabled: Boolean
get() = template.isActive()

Expand Down Expand Up @@ -80,11 +81,16 @@ class ThumbnailBehavior : Behavior {
cvh.status.setShadowLayer(shadowOffsetX, shadowOffsetY, shadowRadius, shadowColor)

cvh.bgExecutor.execute {
val drawable = template.getThumbnail().loadDrawable(cvh.context)
val drawable = template.thumbnail
?.takeIf(canUseIconPredicate)
?.loadDrawable(cvh.context)
cvh.uiExecutor.execute {
val radius = cvh.context.getResources()
.getDimensionPixelSize(R.dimen.control_corner_radius).toFloat()
clipLayer.setDrawable(CornerDrawable(drawable, radius))
// TODO(b/290037843): Add a placeholder
drawable?.let {
clipLayer.drawable = CornerDrawable(it, radius)
}
clipLayer.setColorFilter(BlendModeColorFilter(cvh.context.resources
.getColor(R.color.control_thumbnail_tint), BlendMode.LUMINOSITY))
cvh.applyRenderInfo(enabled, colorOffset)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
/*
* Copyright (C) 2023 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.ui

import android.content.ContentProvider
import android.graphics.Bitmap
import android.graphics.drawable.Icon
import android.net.Uri
import android.os.UserHandle
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.google.common.truth.Truth.assertThat
import org.junit.Test
import org.junit.runner.RunWith

@SmallTest
@RunWith(AndroidTestingRunner::class)
class CanUseIconPredicateTest : SysuiTestCase() {

private companion object {
const val USER_ID_1 = 1
const val USER_ID_2 = 2
}

val underTest: CanUseIconPredicate = CanUseIconPredicate(USER_ID_1)

@Test
fun testReturnsFalseForDifferentUser() {
val user2Icon =
Icon.createWithContentUri(
ContentProvider.createContentUriForUser(
Uri.parse("content://test"),
UserHandle.of(USER_ID_2)
)
)

assertThat(underTest.invoke(user2Icon)).isFalse()
}

@Test
fun testReturnsTrueForCorrectUser() {
val user1Icon =
Icon.createWithContentUri(
ContentProvider.createContentUriForUser(
Uri.parse("content://test"),
UserHandle.of(USER_ID_1)
)
)

assertThat(underTest.invoke(user1Icon)).isTrue()
}

@Test
fun testReturnsTrueForUriWithoutUser() {
val uriIcon = Icon.createWithContentUri(Uri.parse("content://test"))

assertThat(underTest.invoke(uriIcon)).isTrue()
}

@Test
fun testReturnsTrueForNonUriIcon() {
val bitmapIcon = Icon.createWithBitmap(Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888))

assertThat(underTest.invoke(bitmapIcon)).isTrue()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,8 @@ class ControlViewHolderTest : SysuiTestCase() {
FakeExecutor(clock),
mock(ControlActionCoordinator::class.java),
mock(ControlsMetricsLogger::class.java),
uid = 100
uid = 100,
0,
)

val cws = ControlWithState(
Expand Down

0 comments on commit ed183ed

Please sign in to comment.