Merge "Add userId check before loading icon in Device Controls" into tm-dev am: cdd1be4645
Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/23982566 Change-Id: I2ff38e0885b5cf7b498b1fe7a40d5fb0457c1bb9 Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
@@ -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
|
||||
@@ -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 {
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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)
|
||||
@@ -228,6 +232,7 @@ internal class ControlHolder(
|
||||
visibility = View.VISIBLE
|
||||
}
|
||||
|
||||
private val canUseIconPredicate = CanUseIconPredicate(currentUserId)
|
||||
private val accessibilityDelegate = ControlHolderAccessibilityDelegate(
|
||||
this::stateDescription,
|
||||
this::getLayoutPosition,
|
||||
@@ -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)
|
||||
|
||||
@@ -180,7 +180,7 @@ 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, currentUserTracker.currentUserId).apply {
|
||||
registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() {
|
||||
var hasAnimated = false
|
||||
override fun onChanged() {
|
||||
|
||||
@@ -164,7 +164,7 @@ class ControlsFavoritingActivity @Inject constructor(
|
||||
}
|
||||
|
||||
executor.execute {
|
||||
structurePager.adapter = StructureAdapter(listOfStructures)
|
||||
structurePager.adapter = StructureAdapter(listOfStructures, currentUserTracker.currentUserId)
|
||||
structurePager.setCurrentItem(structureIndex)
|
||||
if (error) {
|
||||
statusText.text = resources.getString(R.string.controls_favorite_load_error,
|
||||
@@ -210,7 +210,7 @@ class ControlsFavoritingActivity @Inject constructor(
|
||||
structurePager.alpha = 0.0f
|
||||
pageIndicator.alpha = 0.0f
|
||||
structurePager.apply {
|
||||
adapter = StructureAdapter(emptyList())
|
||||
adapter = StructureAdapter(emptyList(), currentUserTracker.currentUserId)
|
||||
registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {
|
||||
override fun onPageSelected(position: Int) {
|
||||
super.onPageSelected(position)
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -40,7 +42,8 @@ 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
|
||||
@@ -48,7 +51,7 @@ class StructureAdapter(
|
||||
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()
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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 {
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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 {
|
||||
|
||||
@@ -411,7 +411,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)
|
||||
|
||||
@@ -63,7 +63,7 @@ class TemperatureControlBehavior : Behavior {
|
||||
// interactions (touch, range)
|
||||
subBehavior = cvh.bindBehavior(
|
||||
subBehavior,
|
||||
ControlViewHolder.findBehaviorClass(
|
||||
cvh.findBehaviorClass(
|
||||
control.status,
|
||||
subTemplate,
|
||||
control.deviceType
|
||||
|
||||
@@ -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
|
||||
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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(
|
||||
|
||||
Reference in New Issue
Block a user