From ed183ed9122416026ed27d4877f96a545fe42316 Mon Sep 17 00:00:00 2001 From: Anton Potapov Date: Tue, 4 Jul 2023 12:15:41 +0100 Subject: [PATCH] Add userId check before loading icon in Device Controls 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 --- .../controls/management/ControlAdapter.kt | 15 +++- .../management/ControlsEditingActivity.kt | 2 +- .../management/ControlsFavoritingActivity.kt | 4 +- .../controls/management/StructureAdapter.kt | 11 ++- .../controls/ui/CanUseIconPredicate.kt | 30 +++++++ .../systemui/controls/ui/ControlViewHolder.kt | 50 ++++++------ .../controls/ui/ControlsUiControllerImpl.kt | 3 +- .../controls/ui/TemperatureControlBehavior.kt | 2 +- .../systemui/controls/ui/ThumbnailBehavior.kt | 12 ++- .../controls/ui/CanUseIconPredicateTest.kt | 81 +++++++++++++++++++ .../controls/ui/ControlViewHolderTest.kt | 3 +- 11 files changed, 173 insertions(+), 40 deletions(-) create mode 100644 packages/SystemUI/src/com/android/systemui/controls/ui/CanUseIconPredicate.kt create mode 100644 packages/SystemUI/tests/src/com/android/systemui/controls/ui/CanUseIconPredicateTest.kt diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlAdapter.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlAdapter.kt index 3eb58bba1ca4..ec76f433b23b 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlAdapter.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlAdapter.kt @@ -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() { 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) 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 7df08651d5ab..8843349da2b5 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsEditingActivity.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsEditingActivity.kt @@ -205,7 +205,7 @@ open class ControlsEditingActivity @Inject constructor( val elevation = resources.getFloat(R.dimen.control_card_elevation) val recyclerView = requireViewById(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() { diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt index 3e97d3132bc7..efc3fb662f1a 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt @@ -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, @@ -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) diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/StructureAdapter.kt b/packages/SystemUI/src/com/android/systemui/controls/management/StructureAdapter.kt index 747bcbe1c229..5977d379acde 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/management/StructureAdapter.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/management/StructureAdapter.kt @@ -24,13 +24,15 @@ import androidx.recyclerview.widget.RecyclerView import com.android.systemui.R class StructureAdapter( - private val models: List + private val models: List, + private val currentUserId: Int, ) : RecyclerView.Adapter() { 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(R.id.listAll) val elevation = itemView.context.resources.getFloat(R.dimen.control_card_elevation) - controlAdapter = ControlAdapter(elevation) + controlAdapter = ControlAdapter(elevation, currentUserId) setUpRecyclerView() } diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/CanUseIconPredicate.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/CanUseIconPredicate.kt new file mode 100644 index 000000000000..61c21237144d --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/CanUseIconPredicate.kt @@ -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 + } +} diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt index 6a9aaf865251..931062865c64 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt @@ -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 { - 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 { + 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 { 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 554391649548..1c1f7702c4bd 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt @@ -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) diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/TemperatureControlBehavior.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/TemperatureControlBehavior.kt index a7dc09bb17e5..39d69704d817 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/ui/TemperatureControlBehavior.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/TemperatureControlBehavior.kt @@ -63,7 +63,7 @@ class TemperatureControlBehavior : Behavior { // interactions (touch, range) subBehavior = cvh.bindBehavior( subBehavior, - ControlViewHolder.findBehaviorClass( + cvh.findBehaviorClass( control.status, subTemplate, control.deviceType diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ThumbnailBehavior.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ThumbnailBehavior.kt index c2168aa8d9d9..0b57e792f9f7 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/ui/ThumbnailBehavior.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ThumbnailBehavior.kt @@ -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) diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/CanUseIconPredicateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/CanUseIconPredicateTest.kt new file mode 100644 index 000000000000..bfdb9231a9f8 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/CanUseIconPredicateTest.kt @@ -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() + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlViewHolderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlViewHolderTest.kt index d3c465dab438..42f28c8c6043 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlViewHolderTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlViewHolderTest.kt @@ -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(