Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 1dd200f9 authored by Treehugger Robot's avatar Treehugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Fix icon loading in controls" into main

parents b2aa2290 9a922ab3
Loading
Loading
Loading
Loading
+7 −9
Original line number Diff line number Diff line
@@ -12,13 +12,14 @@ import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.res.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.controls.ControlsMetricsLogger
import com.android.systemui.controls.controller.ControlInfo
import com.android.systemui.controls.controller.ControlsController
import com.android.systemui.res.R
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.time.FakeSystemClock
import com.android.systemui.utils.SafeIconLoader
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -32,6 +33,7 @@ class TemperatureControlBehaviorTest : SysuiTestCase() {
    @Mock lateinit var controlsMetricsLogger: ControlsMetricsLogger
    @Mock lateinit var controlActionCoordinator: ControlActionCoordinator
    @Mock lateinit var controlsController: ControlsController
    @Mock lateinit var safeIconLoader: SafeIconLoader

    private val fakeSystemClock = FakeSystemClock()
    private val underTest = TemperatureControlBehavior()
@@ -53,6 +55,7 @@ class TemperatureControlBehaviorTest : SysuiTestCase() {
                controlsMetricsLogger,
                0,
                0,
                safeIconLoader,
            )
    }

@@ -61,12 +64,7 @@ class TemperatureControlBehaviorTest : SysuiTestCase() {
        val controlWithState =
            ControlWithState(
                ComponentName("test.pkg", "TestClass"),
                ControlInfo(
                    "test_id",
                    "test title",
                    "test subtitle",
                    DeviceTypes.TYPE_AC_UNIT,
                ),
                ControlInfo("test_id", "test title", "test subtitle", DeviceTypes.TYPE_AC_UNIT),
                Control.StatefulBuilder(
                        "",
                        PendingIntent.getActivity(
@@ -87,11 +85,11 @@ class TemperatureControlBehaviorTest : SysuiTestCase() {
                            ),
                            0,
                            0,
                            0
                            0,
                        )
                    )
                    .setStatus(Control.STATUS_OK)
                    .build()
                    .build(),
            )
        viewHolder.bindData(controlWithState, false)
        underTest.initialize(viewHolder)
+26 −26
Original line number Diff line number Diff line
@@ -43,7 +43,7 @@ class AllModel(
    private val controls: List<ControlStatus>,
    initialFavoriteIds: List<String>,
    private val emptyZoneString: CharSequence,
    private val controlsModelCallback: ControlsModel.ControlsModelCallback
    private val controlsModelCallback: ControlsModel.ControlsModelCallback,
) : ControlsModel {

    private var modified = false
@@ -51,11 +51,10 @@ class AllModel(
    override val moveHelper = null

    override val favorites: List<ControlInfo>
        get() = favoriteIds.mapNotNull { id ->
        get() =
            favoriteIds.mapNotNull { id ->
                val control = controls.firstOrNull { it.control.controlId == id }?.control
            control?.let {
                ControlInfo.fromControl(it)
            }
                control?.let { ControlInfo.fromControl(it) }
            }

    private val favoriteIds = run {
@@ -66,11 +65,13 @@ class AllModel(
    override val elements: List<ElementWrapper> = createWrappers(controls)

    override fun changeFavoriteStatus(controlId: String, favorite: Boolean) {
        val toChange = elements.firstOrNull {
        val toChange =
            elements.firstOrNull {
                it is ControlStatusWrapper && it.controlStatus.control.controlId == controlId
            } as ControlStatusWrapper?
        if (favorite == toChange?.controlStatus?.favorite) return
        val changed: Boolean = if (favorite) {
        val changed: Boolean =
            if (favorite) {
                favoriteIds.add(controlId)
            } else {
                favoriteIds.remove(controlId)
@@ -82,13 +83,12 @@ class AllModel(
            }
            controlsModelCallback.onChange()
        }
        toChange?.let {
            it.controlStatus.favorite = favorite
        }
        toChange?.let { it.controlStatus.favorite = favorite }
    }

    private fun createWrappers(list: List<ControlStatus>): List<ElementWrapper> {
        val map = list.groupByTo(OrderedMap(ArrayMap<CharSequence, MutableList<ControlStatus>>())) {
        val map =
            list.groupByTo(OrderedMap(ArrayMap<CharSequence, MutableList<ControlStatus>>())) {
                it.control.zone ?: ""
            }
        val output = mutableListOf<ElementWrapper>()
+89 −76
Original line number Diff line number Diff line
@@ -36,10 +36,11 @@ import androidx.core.view.AccessibilityDelegateCompat
import androidx.core.view.ViewCompat
import androidx.core.view.accessibility.AccessibilityNodeInfoCompat
import androidx.recyclerview.widget.RecyclerView
import com.android.systemui.res.R
import com.android.systemui.controls.ControlInterface
import com.android.systemui.controls.ui.CanUseIconPredicate
import com.android.systemui.controls.ui.RenderInfo
import com.android.systemui.res.R
import com.android.systemui.utils.SafeIconLoader

private typealias ModelFavoriteChanger = (String, Boolean) -> Unit

@@ -54,6 +55,7 @@ private typealias ModelFavoriteChanger = (String, Boolean) -> Unit
class ControlAdapter(
    private val elevation: Float,
    private val currentUserId: Int,
    private val safeIconLoader: SafeIconLoader,
) : RecyclerView.Adapter<Holder>() {

    companion object {
@@ -62,9 +64,8 @@ class ControlAdapter(
        const val TYPE_DIVIDER = 2

        /**
         * For low-dp width screens that also employ an increased font scale, adjust the
         * number of columns. This helps prevent text truncation on these devices.
         *
         * For low-dp width screens that also employ an increased font scale, adjust the number of
         * columns. This helps prevent text truncation on these devices.
         */
        @JvmStatic
        fun findMaxColumns(res: Resources): Int {
@@ -78,10 +79,12 @@ class ControlAdapter(

            val config = res.configuration
            val isPortrait = config.orientation == Configuration.ORIENTATION_PORTRAIT
            if (isPortrait &&
            if (
                isPortrait &&
                    config.screenWidthDp != Configuration.SCREEN_WIDTH_DP_UNDEFINED &&
                    config.screenWidthDp <= maxColumnsAdjustWidth &&
                    config.fontScale >= maxColumnsAdjustFontScale) {
                    config.fontScale >= maxColumnsAdjustFontScale
            ) {
                maxColumns--
            }

@@ -106,11 +109,12 @@ class ControlAdapter(
                            rightMargin = 0
                        }
                        elevation = this@ControlAdapter.elevation
                        background = parent.context.getDrawable(
                                R.drawable.control_background_ripple)
                        background =
                            parent.context.getDrawable(R.drawable.control_background_ripple)
                    },
                    currentUserId,
                    model?.moveHelper, // Indicates that position information is needed
                    safeIconLoader,
                ) { id, favorite ->
                    model?.changeFavoriteStatus(id, favorite)
                }
@@ -119,8 +123,13 @@ class ControlAdapter(
                ZoneHolder(layoutInflater.inflate(R.layout.controls_zone_header, parent, false))
            }
            TYPE_DIVIDER -> {
                DividerHolder(layoutInflater.inflate(
                        R.layout.controls_horizontal_divider_with_empty, parent, false))
                DividerHolder(
                    layoutInflater.inflate(
                        R.layout.controls_horizontal_divider_with_empty,
                        parent,
                        false,
                    )
                )
            }
            else -> throw IllegalStateException("Wrong viewType: $viewType")
        }
@@ -134,9 +143,7 @@ class ControlAdapter(
    override fun getItemCount() = model?.elements?.size ?: 0

    override fun onBindViewHolder(holder: Holder, index: Int) {
        model?.let {
            holder.bindData(it.elements[index])
        }
        model?.let { holder.bindData(it.elements[index]) }
    }

    override fun onBindViewHolder(holder: Holder, position: Int, payloads: MutableList<Any>) {
@@ -166,13 +173,12 @@ class ControlAdapter(

/**
 * Holder for binding views in the [RecyclerView]-
 *
 * @param view the [View] for this [Holder]
 */
sealed class Holder(view: View) : RecyclerView.ViewHolder(view) {

    /**
     * Bind the data from the model into the view
     */
    /** Bind the data from the model into the view */
    abstract fun bindData(wrapper: ElementWrapper)

    open fun updateFavorite(favorite: Boolean) {}
@@ -181,12 +187,13 @@ sealed class Holder(view: View) : RecyclerView.ViewHolder(view) {
/**
 * Holder for using with [DividerWrapper] to display a divider between zones.
 *
 * The divider can be shown or hidden. It also has a view the height of a control, that can
 * be toggled visible or gone.
 * The divider can be shown or hidden. It also has a view the height of a control, that can be
 * toggled visible or gone.
 */
private class DividerHolder(view: View) : Holder(view) {
    private val frame: View = itemView.requireViewById(R.id.frame)
    private val divider: View = itemView.requireViewById(R.id.divider)

    override fun bindData(wrapper: ElementWrapper) {
        wrapper as DividerWrapper
        frame.visibility = if (wrapper.showNone) View.VISIBLE else View.GONE
@@ -194,9 +201,7 @@ private class DividerHolder(view: View) : Holder(view) {
    }
}

/**
 * Holder for using with [ZoneNameWrapper] to display names of zones.
 */
/** Holder for using with [ZoneNameWrapper] to display names of zones. */
private class ZoneHolder(view: View) : Holder(view) {
    private val zone: TextView = itemView as TextView

@@ -208,15 +213,17 @@ private class ZoneHolder(view: View) : Holder(view) {

/**
 * Holder for using with [ControlStatusWrapper] to display names of zones.
 *
 * @param moveHelper a helper interface to facilitate a11y rearranging. Null indicates no
 *   rearranging
 * @param favoriteCallback this callback will be called whenever the favorite state of the
 *                         [Control] this view represents changes.
 * @param favoriteCallback this callback will be called whenever the favorite state of the [Control]
 *   this view represents changes.
 */
internal class ControlHolder(
    view: View,
    currentUserId: Int,
    val moveHelper: ControlsModel.MoveHelper?,
    val safeIconLoader: SafeIconLoader,
    val favoriteCallback: ModelFavoriteChanger,
) : Holder(view) {
    private val favoriteStateDescription =
@@ -228,15 +235,15 @@ internal class ControlHolder(
    private val title: TextView = itemView.requireViewById(R.id.title)
    private val subtitle: TextView = itemView.requireViewById(R.id.subtitle)
    private val removed: TextView = itemView.requireViewById(R.id.status)
    private val favorite: CheckBox = itemView.requireViewById<CheckBox>(R.id.favorite).apply {
        visibility = View.VISIBLE
    }
    private val favorite: CheckBox =
        itemView.requireViewById<CheckBox>(R.id.favorite).apply { visibility = View.VISIBLE }

    private val canUseIconPredicate = CanUseIconPredicate(currentUserId)
    private val accessibilityDelegate = ControlHolderAccessibilityDelegate(
    private val accessibilityDelegate =
        ControlHolderAccessibilityDelegate(
            this::stateDescription,
            this::getLayoutPosition,
        moveHelper
            moveHelper,
        )

    init {
@@ -252,7 +259,9 @@ internal class ControlHolder(
        } else {
            val position = layoutPosition + 1
            return itemView.context.getString(
                R.string.accessibility_control_favorite_position, position)
                R.string.accessibility_control_favorite_position,
                position,
            )
        }
    }

@@ -262,7 +271,8 @@ internal class ControlHolder(
        title.text = wrapper.title
        subtitle.text = wrapper.subtitle
        updateFavorite(wrapper.favorite)
        removed.text = if (wrapper.removed) {
        removed.text =
            if (wrapper.removed) {
                itemView.context.getText(R.string.controls_removed)
            } else {
                ""
@@ -282,7 +292,7 @@ internal class ControlHolder(

    private fun getRenderInfo(
        component: ComponentName,
        @DeviceTypes.DeviceType deviceType: Int
        @DeviceTypes.DeviceType deviceType: Int,
    ): RenderInfo {
        return RenderInfo.lookup(itemView.context, component, deviceType)
    }
@@ -292,11 +302,12 @@ internal class ControlHolder(
        val fg = context.getResources().getColorStateList(ri.foreground, context.getTheme())

        icon.imageTintList = null
        ci.customIcon
                ?.takeIf(canUseIconPredicate)
                ?.let {
            icon.setImageIcon(it)
        } ?: run {
        ci.customIcon?.takeIf(canUseIconPredicate)?.let {
            val drawable = safeIconLoader.load(it)
            icon.setImageDrawable(drawable)
            drawable
        }
            ?: run {
                icon.setImageDrawable(ri.icon)

                // Do not color app icons
@@ -317,14 +328,13 @@ internal class ControlHolder(
 *
 * @param stateRetriever function to determine the state description based on the favorite state
 * @param positionRetriever function to obtain the position of this control. It only has to be
 *                          correct in controls that are currently favorites (and therefore can
 *                          be moved).
 *   correct in controls that are currently favorites (and therefore can be moved).
 * @param moveHelper helper interface to determine if a control can be moved and actually move it.
 */
private class ControlHolderAccessibilityDelegate(
    val stateRetriever: (Boolean) -> CharSequence?,
    val positionRetriever: () -> Int,
    val moveHelper: ControlsModel.MoveHelper?
    val moveHelper: ControlsModel.MoveHelper?,
) : AccessibilityDelegateCompat() {

    var isFavorite = false
@@ -369,24 +379,28 @@ private class ControlHolderAccessibilityDelegate(

    private fun addClickAction(host: View, info: AccessibilityNodeInfoCompat) {
        // Change the text for the double-tap action
        val clickActionString = if (isFavorite) {
        val clickActionString =
            if (isFavorite) {
                host.context.getString(R.string.accessibility_control_change_unfavorite)
            } else {
                host.context.getString(R.string.accessibility_control_change_favorite)
            }
        val click = AccessibilityNodeInfoCompat.AccessibilityActionCompat(
        val click =
            AccessibilityNodeInfoCompat.AccessibilityActionCompat(
                AccessibilityNodeInfo.ACTION_CLICK,
                // “favorite/unfavorite”
            clickActionString)
                clickActionString,
            )
        info.addAction(click)
    }

    private fun maybeAddMoveBeforeAction(host: View, info: AccessibilityNodeInfoCompat) {
        if (moveHelper?.canMoveBefore(positionRetriever()) ?: false) {
            val newPosition = positionRetriever() + 1 - 1
            val moveBefore = AccessibilityNodeInfoCompat.AccessibilityActionCompat(
            val moveBefore =
                AccessibilityNodeInfoCompat.AccessibilityActionCompat(
                    MOVE_BEFORE_ID,
                host.context.getString(R.string.accessibility_control_move, newPosition)
                    host.context.getString(R.string.accessibility_control_move, newPosition),
                )
            info.addAction(moveBefore)
            info.isContextClickable = true
@@ -396,9 +410,10 @@ private class ControlHolderAccessibilityDelegate(
    private fun maybeAddMoveAfterAction(host: View, info: AccessibilityNodeInfoCompat) {
        if (moveHelper?.canMoveAfter(positionRetriever()) ?: false) {
            val newPosition = positionRetriever() + 1 + 1
            val moveAfter = AccessibilityNodeInfoCompat.AccessibilityActionCompat(
            val moveAfter =
                AccessibilityNodeInfoCompat.AccessibilityActionCompat(
                    MOVE_AFTER_ID,
                host.context.getString(R.string.accessibility_control_move, newPosition)
                    host.context.getString(R.string.accessibility_control_move, newPosition),
                )
            info.addAction(moveAfter)
            info.isContextClickable = true
@@ -406,16 +421,14 @@ private class ControlHolderAccessibilityDelegate(
    }
}

class MarginItemDecorator(
    private val topMargin: Int,
    private val sideMargins: Int
) : RecyclerView.ItemDecoration() {
class MarginItemDecorator(private val topMargin: Int, private val sideMargins: Int) :
    RecyclerView.ItemDecoration() {

    override fun getItemOffsets(
        outRect: Rect,
        view: View,
        parent: RecyclerView,
        state: RecyclerView.State
        state: RecyclerView.State,
    ) {
        val position = parent.getChildAdapterPosition(view)
        if (position == RecyclerView.NO_POSITION) return
+15 −1
Original line number Diff line number Diff line
@@ -22,6 +22,7 @@ import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.os.Process
import android.util.Log
import android.view.View
import android.view.ViewGroup
@@ -42,6 +43,7 @@ import com.android.systemui.controls.ui.ControlsActivity
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.res.R
import com.android.systemui.settings.UserTracker
import com.android.systemui.utils.SafeIconLoader
import java.util.concurrent.Executor
import javax.inject.Inject

@@ -53,6 +55,8 @@ constructor(
    private val controller: ControlsControllerImpl,
    private val userTracker: UserTracker,
    private val customIconCache: CustomIconCache,
    private val controlsListingController: ControlsListingController,
    private val safeIconLoaderFactory: SafeIconLoader.Factory,
) : ComponentActivity(), ControlsManagementActivity {

    companion object {
@@ -258,8 +262,18 @@ constructor(
        val elevation = resources.getFloat(R.dimen.control_card_elevation)
        val recyclerView = requireViewById<RecyclerView>(R.id.list)
        recyclerView.alpha = 0.0f
        val uid =
            controlsListingController
                .getCurrentServices()
                .firstOrNull { it.componentName == component }
                ?.serviceInfo
                ?.applicationInfo
                ?.uid ?: Process.INVALID_UID
        val packageName = component.packageName
        val safeIconLoader = safeIconLoaderFactory.create(uid, packageName, userTracker.userId)

        val adapter =
            ControlAdapter(elevation, userTracker.userId).apply {
            ControlAdapter(elevation, userTracker.userId, safeIconLoader).apply {
                registerAdapterDataObserver(
                    object : RecyclerView.AdapterDataObserver() {
                        var hasAnimated = false
+27 −2
Original line number Diff line number Diff line
@@ -25,6 +25,7 @@ import android.content.Context
import android.content.Intent
import android.content.res.Configuration
import android.os.Bundle
import android.os.Process.INVALID_UID
import android.text.TextUtils
import android.util.Log
import android.view.Gravity
@@ -47,6 +48,7 @@ import com.android.systemui.controls.ui.ControlsActivity
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.res.R
import com.android.systemui.settings.UserTracker
import com.android.systemui.utils.SafeIconLoader
import java.text.Collator
import java.util.concurrent.Executor
import javax.inject.Inject
@@ -57,6 +59,8 @@ constructor(
    @Main private val executor: Executor,
    private val controller: ControlsControllerImpl,
    private val userTracker: UserTracker,
    private val safeIconLoaderFactory: SafeIconLoader.Factory,
    private val controlsListingController: ControlsListingController,
) : ComponentActivity(), ControlsManagementActivity {

    companion object {
@@ -196,9 +200,20 @@ constructor(
                        listOfStructures = listOf(listOfStructures[structureIndex])
                    }

                    val uid =
                        controlsListingController
                            .getCurrentServices()
                            .firstOrNull { it.componentName == componentName }
                            ?.serviceInfo
                            ?.applicationInfo
                            ?.uid ?: INVALID_UID
                    val packageName = componentName.packageName
                    val safeIconLoader =
                        safeIconLoaderFactory.create(uid, packageName, userTracker.userId)

                    executor.execute {
                        structurePager.adapter =
                            StructureAdapter(listOfStructures, userTracker.userId)
                            StructureAdapter(listOfStructures, userTracker.userId, safeIconLoader)
                        structurePager.setCurrentItem(structureIndex)
                        if (error) {
                            statusText.text =
@@ -260,8 +275,18 @@ constructor(
    private fun setUpPager() {
        structurePager.alpha = 0.0f
        pageIndicator.alpha = 0.0f
        val uid =
            controlsListingController
                .getCurrentServices()
                .firstOrNull { it.componentName == component }
                ?.serviceInfo
                ?.applicationInfo
                ?.uid ?: INVALID_UID
        val packageName = componentName?.packageName ?: ""
        val safeIconLoader = safeIconLoaderFactory.create(uid, packageName, userTracker.userId)

        structurePager.apply {
            adapter = StructureAdapter(emptyList(), userTracker.userId)
            adapter = StructureAdapter(emptyList(), userTracker.userId, safeIconLoader)
            registerOnPageChangeCallback(
                object : ViewPager2.OnPageChangeCallback() {
                    override fun onPageSelected(position: Int) {
Loading