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

Commit ce8bee1b authored by Olivier St-Onge's avatar Olivier St-Onge
Browse files

Pre load all icon drawables for QS tiles

This change is flag guarded. Preloading drawables improves performance in Compose and is needed for the QS refactor.

Test: manually
Test: local perfetto tracing
Flag: com.android.systemui.qs_ui_refactor_compose_fragment
Bug: 377290311
Change-Id: Ie70debb02f2d58e547d063f5c358286170f4c7b5
parent 0fd15aa6
Loading
Loading
Loading
Loading
+93 −112
Original line number Diff line number Diff line
@@ -27,7 +27,6 @@ import androidx.lifecycle.LifecycleOwner
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.internal.logging.MetricsLogger
import com.android.systemui.res.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.ActivityTransitionAnimator
import com.android.systemui.classifier.FalsingManagerFake
@@ -45,13 +44,14 @@ import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.qs.QSHost
import com.android.systemui.qs.QsEventLogger
import com.android.systemui.qs.logging.QSLogger
import com.android.systemui.qs.tileimpl.QSTileImpl
import com.android.systemui.res.R
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.capture
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.settings.FakeSettings
import com.android.systemui.util.settings.SecureSettings
import com.google.common.truth.Truth.assertThat
import java.util.Optional
import org.junit.After
import org.junit.Before
import org.junit.Test
@@ -67,40 +67,27 @@ import org.mockito.Mockito.verify
import org.mockito.Mockito.verifyNoMoreInteractions
import org.mockito.Mockito.`when`
import org.mockito.MockitoAnnotations
import java.util.Optional

@SmallTest
@RunWith(AndroidJUnit4::class)
@TestableLooper.RunWithLooper(setAsMainLooper = true)
class DeviceControlsTileTest : SysuiTestCase() {

    @Mock
    private lateinit var qsHost: QSHost
    @Mock
    private lateinit var metricsLogger: MetricsLogger
    @Mock
    private lateinit var statusBarStateController: StatusBarStateController
    @Mock
    private lateinit var activityStarter: ActivityStarter
    @Mock
    private lateinit var qsLogger: QSLogger
    @Mock
    private lateinit var controlsComponent: ControlsComponent
    @Mock
    private lateinit var controlsUiController: ControlsUiController
    @Mock
    private lateinit var controlsListingController: ControlsListingController
    @Mock
    private lateinit var controlsController: ControlsController
    @Mock
    private lateinit var serviceInfo: ControlsServiceInfo
    @Mock
    private lateinit var uiEventLogger: QsEventLogger
    @Mock private lateinit var qsHost: QSHost
    @Mock private lateinit var metricsLogger: MetricsLogger
    @Mock private lateinit var statusBarStateController: StatusBarStateController
    @Mock private lateinit var activityStarter: ActivityStarter
    @Mock private lateinit var qsLogger: QSLogger
    @Mock private lateinit var controlsComponent: ControlsComponent
    @Mock private lateinit var controlsUiController: ControlsUiController
    @Mock private lateinit var controlsListingController: ControlsListingController
    @Mock private lateinit var controlsController: ControlsController
    @Mock private lateinit var serviceInfo: ControlsServiceInfo
    @Mock private lateinit var uiEventLogger: QsEventLogger
    @Captor
    private lateinit var listingCallbackCaptor:
        ArgumentCaptor<ControlsListingController.ControlsListingCallback>
    @Captor
    private lateinit var intentCaptor: ArgumentCaptor<Intent>
    @Captor private lateinit var intentCaptor: ArgumentCaptor<Intent>

    private lateinit var testableLooper: TestableLooper
    private lateinit var tile: DeviceControlsTile
@@ -120,8 +107,11 @@ class DeviceControlsTileTest : SysuiTestCase() {
        `when`(qsHost.context).thenReturn(spiedContext)
        `when`(controlsComponent.isEnabled()).thenReturn(true)
        `when`(controlsController.getPreferredSelection())
                .thenReturn(SelectedItem.StructureItem(
                        StructureInfo(ComponentName("pkg", "cls"), "structure", listOf())))
            .thenReturn(
                SelectedItem.StructureItem(
                    StructureInfo(ComponentName("pkg", "cls"), "structure", listOf())
                )
            )
        secureSettings.putInt(Settings.Secure.LOCKSCREEN_SHOW_CONTROLS, 1)

        setupControlsComponent()
@@ -182,9 +172,10 @@ class DeviceControlsTileTest : SysuiTestCase() {

    @Test
    fun testObservingCallback() {
        verify(controlsListingController).observe(
        verify(controlsListingController)
            .observe(
                any(LifecycleOwner::class.java),
                any(ControlsListingController.ControlsListingCallback::class.java)
                any(ControlsListingController.ControlsListingCallback::class.java),
            )
    }

@@ -205,10 +196,8 @@ class DeviceControlsTileTest : SysuiTestCase() {

    @Test
    fun testStateUnavailableIfNoListings() {
        verify(controlsListingController).observe(
                any(LifecycleOwner::class.java),
                capture(listingCallbackCaptor)
        )
        verify(controlsListingController)
            .observe(any(LifecycleOwner::class.java), capture(listingCallbackCaptor))

        listingCallbackCaptor.value.onServicesUpdated(emptyList())
        testableLooper.processAllMessages()
@@ -218,10 +207,8 @@ class DeviceControlsTileTest : SysuiTestCase() {

    @Test
    fun testStateUnavailableIfNotEnabled() {
        verify(controlsListingController).observe(
            any(LifecycleOwner::class.java),
            capture(listingCallbackCaptor)
        )
        verify(controlsListingController)
            .observe(any(LifecycleOwner::class.java), capture(listingCallbackCaptor))
        `when`(controlsComponent.isEnabled()).thenReturn(false)

        listingCallbackCaptor.value.onServicesUpdated(listOf(serviceInfo))
@@ -232,17 +219,18 @@ class DeviceControlsTileTest : SysuiTestCase() {

    @Test
    fun testStateActiveIfListingsHasControlsFavorited() {
        verify(controlsListingController).observe(
                any(LifecycleOwner::class.java),
                capture(listingCallbackCaptor)
        )
        verify(controlsListingController)
            .observe(any(LifecycleOwner::class.java), capture(listingCallbackCaptor))
        `when`(controlsComponent.getVisibility()).thenReturn(ControlsComponent.Visibility.AVAILABLE)
        `when`(controlsController.getPreferredSelection()).thenReturn(
            SelectedItem.StructureItem(StructureInfo(
        `when`(controlsController.getPreferredSelection())
            .thenReturn(
                SelectedItem.StructureItem(
                    StructureInfo(
                        ComponentName("pkg", "cls"),
                        "structure",
                listOf(ControlInfo("id", "title", "subtitle", 1))
            ))
                        listOf(ControlInfo("id", "title", "subtitle", 1)),
                    )
                )
            )

        listingCallbackCaptor.value.onServicesUpdated(listOf(serviceInfo))
@@ -253,14 +241,15 @@ class DeviceControlsTileTest : SysuiTestCase() {

    @Test
    fun testStateInactiveIfListingsHasNoControlsFavorited() {
        verify(controlsListingController).observe(
                any(LifecycleOwner::class.java),
                capture(listingCallbackCaptor)
        )
        verify(controlsListingController)
            .observe(any(LifecycleOwner::class.java), capture(listingCallbackCaptor))
        `when`(controlsComponent.getVisibility()).thenReturn(ControlsComponent.Visibility.AVAILABLE)
        `when`(controlsController.getPreferredSelection())
                .thenReturn(SelectedItem.StructureItem(
                        StructureInfo(ComponentName("pkg", "cls"), "structure", listOf())))
            .thenReturn(
                SelectedItem.StructureItem(
                    StructureInfo(ComponentName("pkg", "cls"), "structure", listOf())
                )
            )

        listingCallbackCaptor.value.onServicesUpdated(listOf(serviceInfo))
        testableLooper.processAllMessages()
@@ -270,10 +259,8 @@ class DeviceControlsTileTest : SysuiTestCase() {

    @Test
    fun testStateActiveIfPreferredIsPanel() {
        verify(controlsListingController).observe(
                any(LifecycleOwner::class.java),
                capture(listingCallbackCaptor)
        )
        verify(controlsListingController)
            .observe(any(LifecycleOwner::class.java), capture(listingCallbackCaptor))
        `when`(controlsComponent.getVisibility()).thenReturn(ControlsComponent.Visibility.AVAILABLE)
        `when`(controlsController.getPreferredSelection())
            .thenReturn(SelectedItem.PanelItem("appName", ComponentName("pkg", "cls")))
@@ -286,10 +273,8 @@ class DeviceControlsTileTest : SysuiTestCase() {

    @Test
    fun testStateInactiveIfLocked() {
        verify(controlsListingController).observe(
            any(LifecycleOwner::class.java),
            capture(listingCallbackCaptor)
        )
        verify(controlsListingController)
            .observe(any(LifecycleOwner::class.java), capture(listingCallbackCaptor))
        `when`(controlsComponent.getVisibility())
            .thenReturn(ControlsComponent.Visibility.AVAILABLE_AFTER_UNLOCK)

@@ -301,10 +286,8 @@ class DeviceControlsTileTest : SysuiTestCase() {

    @Test
    fun testMoveBetweenStates() {
        verify(controlsListingController).observe(
                any(LifecycleOwner::class.java),
                capture(listingCallbackCaptor)
        )
        verify(controlsListingController)
            .observe(any(LifecycleOwner::class.java), capture(listingCallbackCaptor))

        listingCallbackCaptor.value.onServicesUpdated(listOf(serviceInfo))
        testableLooper.processAllMessages()
@@ -325,18 +308,19 @@ class DeviceControlsTileTest : SysuiTestCase() {

    @Test
    fun handleClick_available_shownOverLockscreenWhenLocked() {
        verify(controlsListingController).observe(
                any(LifecycleOwner::class.java),
                capture(listingCallbackCaptor)
        )
        verify(controlsListingController)
            .observe(any(LifecycleOwner::class.java), capture(listingCallbackCaptor))
        `when`(controlsComponent.getVisibility()).thenReturn(ControlsComponent.Visibility.AVAILABLE)
        `when`(controlsUiController.resolveActivity()).thenReturn(ControlsActivity::class.java)
        `when`(controlsController.getPreferredSelection()).thenReturn(
            SelectedItem.StructureItem(StructureInfo(
        `when`(controlsController.getPreferredSelection())
            .thenReturn(
                SelectedItem.StructureItem(
                    StructureInfo(
                        ComponentName("pkg", "cls"),
                        "structure",
                    listOf(ControlInfo("id", "title", "subtitle", 1))
            ))
                        listOf(ControlInfo("id", "title", "subtitle", 1)),
                    )
                )
            )

        listingCallbackCaptor.value.onServicesUpdated(listOf(serviceInfo))
@@ -345,29 +329,32 @@ class DeviceControlsTileTest : SysuiTestCase() {
        tile.click(null /* view */)
        testableLooper.processAllMessages()

        verify(activityStarter).startActivity(
        verify(activityStarter)
            .startActivity(
                intentCaptor.capture(),
                eq(true) /* dismissShade */,
                nullable(ActivityTransitionAnimator.Controller::class.java),
                eq(true) /* showOverLockscreenWhenLocked */)
                eq(true), /* showOverLockscreenWhenLocked */
            )
        assertThat(intentCaptor.value.component?.className).isEqualTo(CONTROLS_ACTIVITY_CLASS_NAME)
    }

    @Test
    fun handleClick_availableAfterUnlock_notShownOverLockscreenWhenLocked() {
        verify(controlsListingController).observe(
            any(LifecycleOwner::class.java),
            capture(listingCallbackCaptor)
        )
        verify(controlsListingController)
            .observe(any(LifecycleOwner::class.java), capture(listingCallbackCaptor))
        `when`(controlsComponent.getVisibility())
            .thenReturn(ControlsComponent.Visibility.AVAILABLE_AFTER_UNLOCK)
        `when`(controlsUiController.resolveActivity()).thenReturn(ControlsActivity::class.java)
        `when`(controlsController.getPreferredSelection()).thenReturn(
            SelectedItem.StructureItem(StructureInfo(
        `when`(controlsController.getPreferredSelection())
            .thenReturn(
                SelectedItem.StructureItem(
                    StructureInfo(
                        ComponentName("pkg", "cls"),
                        "structure",
                listOf(ControlInfo("id", "title", "subtitle", 1))
            ))
                        listOf(ControlInfo("id", "title", "subtitle", 1)),
                    )
                )
            )

        listingCallbackCaptor.value.onServicesUpdated(listOf(serviceInfo))
@@ -376,26 +363,19 @@ class DeviceControlsTileTest : SysuiTestCase() {
        tile.click(null /* view */)
        testableLooper.processAllMessages()

        verify(activityStarter).startActivity(
        verify(activityStarter)
            .startActivity(
                intentCaptor.capture(),
                anyBoolean() /* dismissShade */,
                nullable(ActivityTransitionAnimator.Controller::class.java),
                eq(false) /* showOverLockscreenWhenLocked */)
                eq(false), /* showOverLockscreenWhenLocked */
            )
        assertThat(intentCaptor.value.component?.className).isEqualTo(CONTROLS_ACTIVITY_CLASS_NAME)
    }

    @Test
    fun verifyTileEqualsResourceFromComponent() {
        assertThat(tile.tileLabel)
            .isEqualTo(
                context.getText(
                    controlsComponent.getTileTitleId()))
    }

    @Test
    fun verifyTileImageEqualsResourceFromComponent() {
        assertThat(tile.icon)
            .isEqualTo(QSTileImpl.ResourceIcon.get(controlsComponent.getTileImageId()))
        assertThat(tile.tileLabel).isEqualTo(context.getText(controlsComponent.getTileTitleId()))
    }

    private fun createTile(): DeviceControlsTile {
@@ -409,8 +389,9 @@ class DeviceControlsTileTest : SysuiTestCase() {
                statusBarStateController,
                activityStarter,
                qsLogger,
                controlsComponent
        ).also {
                controlsComponent,
            )
            .also {
                it.initialize()
                testableLooper.processAllMessages()
            }
+1 −2
Original line number Diff line number Diff line
@@ -21,7 +21,6 @@ import android.graphics.drawable.AnimatedVectorDrawable
import android.graphics.drawable.Drawable
import android.text.TextUtils
import androidx.compose.animation.animateColorAsState
import androidx.compose.animation.graphics.ExperimentalAnimationGraphicsApi
import androidx.compose.animation.graphics.res.animatedVectorResource
import androidx.compose.animation.graphics.res.rememberAnimatedVectorPainter
import androidx.compose.animation.graphics.vector.AnimatedImageVector
@@ -192,7 +191,6 @@ fun LargeTileLabels(
    }
}

@OptIn(ExperimentalAnimationGraphicsApi::class)
@Composable
fun SmallTileContent(
    modifier: Modifier = Modifier,
@@ -229,6 +227,7 @@ fun SmallTileContent(
                        }
                    }
                }

                is Icon.Loaded -> {
                    LaunchedEffect(loadedDrawable) {
                        if (loadedDrawable is AnimatedVectorDrawable) {
+19 −0
Original line number Diff line number Diff line
@@ -31,6 +31,7 @@ import static com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;

import android.annotation.CallSuper;
import android.annotation.NonNull;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.Intent;
import android.graphics.drawable.Drawable;
@@ -68,6 +69,7 @@ import com.android.systemui.qs.QSEvent;
import com.android.systemui.qs.QSHost;
import com.android.systemui.qs.QsEventLogger;
import com.android.systemui.qs.SideLabelTileLayout;
import com.android.systemui.qs.flags.QsInCompose;
import com.android.systemui.qs.logging.QSLogger;

import java.io.PrintWriter;
@@ -535,6 +537,23 @@ public abstract class QSTileImpl<TState extends State> implements QSTile, Lifecy
        }
    }

    protected Icon maybeLoadResourceIcon(int id) {
        return maybeLoadResourceIcon(id, mContext);
    }

    /**
     * Returns the {@link QSTile.Icon} for the resource ID, optionally loading the drawable if
     * {@link QsInCompose#isEnabled()} is true.
     */
    @SuppressLint("UseCompatLoadingForDrawables")
    public static Icon maybeLoadResourceIcon(int id, Context context) {
        if (QsInCompose.isEnabled()) {
            return new DrawableIconWithRes(context.getDrawable(id), id);
        } else {
            return ResourceIcon.get(id);
        }
    }

    @Override
    public String getMetricsSpec() {
        return mTileSpec;
+1 −1
Original line number Diff line number Diff line
@@ -160,7 +160,7 @@ public class AirplaneModeTile extends QSTileImpl<BooleanState> {
        final boolean airplaneMode = value != 0;
        state.value = airplaneMode;
        state.label = mContext.getString(R.string.airplane_mode);
        state.icon = ResourceIcon.get(state.value
        state.icon = maybeLoadResourceIcon(state.value
                ? R.drawable.qs_airplane_icon_on : R.drawable.qs_airplane_icon_off);
        state.state = airplaneMode ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE;
        state.contentDescription = state.label;
+32 −25
Original line number Diff line number Diff line
@@ -42,8 +42,9 @@ constructor(
    activityStarter: ActivityStarter,
    qsLogger: QSLogger,
    private val userTracker: UserTracker,
    nextAlarmController: NextAlarmController
) : QSTileImpl<QSTile.State>(
    nextAlarmController: NextAlarmController,
) :
    QSTileImpl<QSTile.State>(
        host,
        uiEventLogger,
        backgroundLooper,
@@ -52,13 +53,14 @@ constructor(
        metricsLogger,
        statusBarStateController,
        activityStarter,
    qsLogger
        qsLogger,
    ) {

    private var lastAlarmInfo: AlarmManager.AlarmClockInfo? = null
    private val icon = ResourceIcon.get(R.drawable.ic_alarm)
    private var icon: QSTile.Icon? = null
    @VisibleForTesting internal val defaultIntent = Intent(AlarmClock.ACTION_SHOW_ALARMS)
    private val callback = NextAlarmController.NextAlarmChangeCallback { nextAlarm ->
    private val callback =
        NextAlarmController.NextAlarmChangeCallback { nextAlarm ->
            lastAlarmInfo = nextAlarm
            refreshState()
        }
@@ -68,9 +70,7 @@ constructor(
    }

    override fun newTileState(): QSTile.State {
        return QSTile.State().apply {
            handlesLongClick = false
        }
        return QSTile.State().apply { handlesLongClick = false }
    }

    override fun handleClick(expandable: Expandable?) {
@@ -82,18 +82,25 @@ constructor(
        if (pendingIntent != null) {
            mActivityStarter.postStartActivityDismissingKeyguard(pendingIntent, animationController)
        } else {
            mActivityStarter.postStartActivityDismissingKeyguard(defaultIntent, 0,
                    animationController)
            mActivityStarter.postStartActivityDismissingKeyguard(
                defaultIntent,
                0,
                animationController,
            )
        }
    }

    override fun handleUpdateState(state: QSTile.State, arg: Any?) {
        if (icon == null) {
            icon = maybeLoadResourceIcon(R.drawable.ic_alarm)
        }
        state.icon = icon
        state.label = tileLabel
        lastAlarmInfo?.let {
            state.secondaryLabel = formatNextAlarm(it)
            state.state = Tile.STATE_ACTIVE
        } ?: run {
        }
            ?: run {
                state.secondaryLabel = mContext.getString(R.string.qs_alarm_tile_no_alarm)
                state.state = Tile.STATE_INACTIVE
            }
Loading