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

Commit 7eba77e6 authored by Fabián Kozynski's avatar Fabián Kozynski
Browse files

Text feedback when clicking on small toggle tiles

When clicking on a small (no text) toggle tile, we will show a text
feedback with the default name and icon of the tile for 2s:

* As part of footer actions (replace security footer) on single shade.
  If there are fgs, it displaces it to a number icon in the same way as
  the security footer.
* On the Toolbar in dual shade.

The colors in FooterActions are not ready yet, but they are tracked in
b/403193068.

Fixes: 380840571
Test: manual, with security footer on/off and fgs on/off
Test: atest com.android.systemui.qs
Test: atest FooterActionsScreenshotTest
Test: atest
android.platform.test.scenario.sysui.quicksettings.composetiles.qs.toggle
Flag: com.android.systemui.qs_ui_refactor_compose_fragment

Change-Id: I9dcabb076d3adfcef67d2863f39886cbc35ef3ca
parent f592160b
Loading
Loading
Loading
Loading
+33 −2
Original line number Diff line number Diff line
@@ -87,6 +87,8 @@ import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsButtonViewModel
import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsForegroundServicesButtonViewModel
import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsSecurityButtonViewModel
import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
import com.android.systemui.qs.panels.ui.compose.toolbar.TextFeedback.tag
import com.android.systemui.qs.panels.ui.viewmodel.TextFeedbackViewModel
import com.android.systemui.qs.ui.composable.QuickSettings
import com.android.systemui.qs.ui.composable.QuickSettingsTheme
import com.android.systemui.qs.ui.compose.borderOnFocus
@@ -145,6 +147,10 @@ fun FooterActions(
    var userSwitcher by remember { mutableStateOf<FooterActionsButtonViewModel?>(null) }
    var power by remember { mutableStateOf(viewModel.initialPower()) }

    var textFeedback by remember {
        mutableStateOf<TextFeedbackViewModel>(TextFeedbackViewModel.NoFeedback)
    }

    LaunchedEffect(
        context,
        qsVisibilityLifecycleOwner,
@@ -152,6 +158,7 @@ fun FooterActions(
        viewModel.security,
        viewModel.foregroundServices,
        viewModel.userSwitcher,
        viewModel.textFeedback,
    ) {
        launch {
            // Listen for dialog requests as soon as we are composed, even when not visible.
@@ -164,6 +171,7 @@ fun FooterActions(
            launch { viewModel.foregroundServices.collect { foregroundServices = it } }
            launch { viewModel.userSwitcher.collect { userSwitcher = it } }
            launch { viewModel.power.collect { power = it } }
            launch { viewModel.textFeedback.collect { textFeedback = it } }
        }
    }

@@ -215,12 +223,20 @@ fun FooterActions(
        verticalAlignment = Alignment.CenterVertically,
    ) {
        CompositionLocalProvider(LocalContentColor provides contentColor) {
            if (security == null && foregroundServices == null) {
            if (
                security == null &&
                    foregroundServices == null &&
                    textFeedback == TextFeedbackViewModel.NoFeedback
            ) {
                Spacer(Modifier.weight(1f))
            }

            val useModifierBasedExpandable = remember { QSComposeFragment.isEnabled }
            if (textFeedback != TextFeedbackViewModel.NoFeedback) {
                TextFeedback({ textFeedback }, Modifier.weight(1f))
            } else {
                SecurityButton({ security }, useModifierBasedExpandable, Modifier.weight(1f))
            }
            ForegroundServicesButton({ foregroundServices }, useModifierBasedExpandable)
            IconButton(
                { userSwitcher },
@@ -261,6 +277,21 @@ private fun SecurityButton(
    )
}

@Composable
private fun TextFeedback(model: () -> TextFeedbackViewModel, modifier: Modifier = Modifier) {
    val model = model()
    if (model is TextFeedbackViewModel.LoadedTextFeedback) {
        TextButton(
            model.icon,
            model.label,
            showNewDot = false,
            useModifierBasedExpandable = false,
            onClick = null,
            modifier = modifier.tag(),
        )
    }
}

/** The foreground services button. */
@Composable
private fun RowScope.ForegroundServicesButton(
+11 −2
Original line number Diff line number Diff line
@@ -39,7 +39,8 @@ import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.nullable
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.test.TestCoroutineScheduler
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestScope
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -53,11 +54,19 @@ import org.mockito.kotlin.eq
@RunWith(AndroidJUnit4::class)
@TestableLooper.RunWithLooper
class FooterActionsInteractorTest : SysuiTestCase() {
    private val testDispatcher = StandardTestDispatcher()
    private val testScope = TestScope(testDispatcher)
    private lateinit var utils: FooterActionsTestUtils

    @Before
    fun setUp() {
        utils = FooterActionsTestUtils(context, TestableLooper.get(this), TestCoroutineScheduler())
        utils =
            FooterActionsTestUtils(
                context,
                TestableLooper.get(this),
                testScope.testScheduler,
                testScope.backgroundScope,
            )
    }

    @Test
+121 −1
Original line number Diff line number Diff line
@@ -18,25 +18,38 @@ package com.android.systemui.qs.footer.ui.viewmodel

import android.graphics.drawable.Drawable
import android.os.UserManager
import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
import android.testing.TestableLooper
import android.testing.TestableLooper.RunWithLooper
import android.view.ContextThemeWrapper
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.internal.logging.testing.UiEventLoggerFake
import com.android.settingslib.Utils
import com.android.settingslib.drawable.UserIconDrawable
import com.android.systemui.InstanceIdSequenceFake
import com.android.systemui.SysuiTestCase
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.common.shared.model.ContentDescription
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.flags.DisableSceneContainer
import com.android.systemui.flags.EnableSceneContainer
import com.android.systemui.qs.FakeFgsManagerController
import com.android.systemui.qs.QSSecurityFooterUtils
import com.android.systemui.qs.QsEventLoggerFake
import com.android.systemui.qs.flags.QSComposeFragment
import com.android.systemui.qs.footer.FooterActionsTestUtils
import com.android.systemui.qs.footer.domain.model.SecurityButtonConfig
import com.android.systemui.qs.panels.ui.viewmodel.TextFeedbackViewModel
import com.android.systemui.qs.pipeline.shared.TileSpec
import com.android.systemui.qs.tiles.base.shared.model.FakeQSTileConfigProvider
import com.android.systemui.qs.tiles.base.shared.model.QSTileConfigProvider
import com.android.systemui.res.R
import com.android.systemui.security.data.model.SecurityModel
import com.android.systemui.shade.shared.model.ShadeMode
import com.android.systemui.statusbar.connectivity.ConnectivityModule
import com.android.systemui.statusbar.policy.FakeSecurityController
import com.android.systemui.statusbar.policy.FakeUserInfoController
import com.android.systemui.statusbar.policy.FakeUserInfoController.FakeInfo
@@ -72,7 +85,13 @@ class FooterActionsViewModelTest : SysuiTestCase() {

    @Before
    fun setUp() {
        utils = FooterActionsTestUtils(context, TestableLooper.get(this), testScope.testScheduler)
        utils =
            FooterActionsTestUtils(
                context,
                TestableLooper.get(this),
                testScope.testScheduler,
                testScope.backgroundScope,
            )
    }

    private fun runTest(block: suspend TestScope.() -> Unit) {
@@ -440,4 +459,105 @@ class FooterActionsViewModelTest : SysuiTestCase() {
        underTest.onQuickSettingsExpansionChanged(1f, isInSplitShade = false)
        assertThat(underTest.backgroundAlpha.value).isEqualTo(1f)
    }

    @Test
    @DisableFlags(QSComposeFragment.FLAG_NAME)
    @DisableSceneContainer
    fun textFeedback_neverFeedback() = runTest {
        val qsTileConfigProvider = createAndPopulateQsTileConfigProvider()
        val textFeedbackInteractor =
            utils.textFeedbackInteractor(qsTileConfigProvider = qsTileConfigProvider)
        val underTest =
            utils.footerActionsViewModel(
                textFeedbackInteractor = textFeedbackInteractor,
                shadeMode = ShadeMode.Single,
            )

        val textFeedback by collectLastValue(underTest.textFeedback)

        assertThat(textFeedback).isEqualTo(TextFeedbackViewModel.NoFeedback)

        textFeedbackInteractor.requestShowFeedback(AIRPLANE_MODE_TILE_SPEC)

        assertThat(textFeedback).isEqualTo(TextFeedbackViewModel.NoFeedback)
    }

    @Test
    @EnableFlags(QSComposeFragment.FLAG_NAME)
    fun textFeedback_composeFragmentEnabled() = runTest { textFeedback_newComposeUI() }

    @Test
    @EnableSceneContainer
    fun textFeedback_sceneContainerEnabled() = runTest { textFeedback_newComposeUI() }

    private fun TestScope.textFeedback_newComposeUI() {
        val qsTileConfigProvider = createAndPopulateQsTileConfigProvider()
        val textFeedbackInteractor =
            utils.textFeedbackInteractor(qsTileConfigProvider = qsTileConfigProvider)
        val securityController = FakeSecurityController()

        // We need Security so the combined flow will have at least one value
        val qsSecurityFooterUtils = mock<QSSecurityFooterUtils>()

        // Mock QSSecurityFooter to map a SecurityModel into a SecurityButtonConfig using the
        // logic in securityToConfig.
        val securityToConfig: (SecurityModel) -> SecurityButtonConfig? = { null }
        whenever(qsSecurityFooterUtils.getButtonConfig(any())).thenAnswer {
            securityToConfig(it.arguments.first() as SecurityModel)
        }
        val fgsManagerController =
            FakeFgsManagerController(showFooterDot = false, numRunningPackages = 1)

        val underTest =
            utils.footerActionsViewModel(
                textFeedbackInteractor = textFeedbackInteractor,
                footerActionsInteractor =
                    utils.footerActionsInteractor(
                        foregroundServicesRepository =
                            utils.foregroundServicesRepository(fgsManagerController),
                        securityRepository = utils.securityRepository(securityController),
                        qsSecurityFooterUtils = qsSecurityFooterUtils,
                    ),
                shadeMode = ShadeMode.Single,
            )

        val textFeedback by collectLastValue(underTest.textFeedback)
        val foregroundServices by collectLastValue(underTest.foregroundServices)

        assertThat(textFeedback).isEqualTo(TextFeedbackViewModel.NoFeedback)
        assertThat(foregroundServices!!.displayText).isTrue()

        textFeedbackInteractor.requestShowFeedback(AIRPLANE_MODE_TILE_SPEC)

        val config = qsTileConfigProvider.getConfig(AIRPLANE_MODE_TILE_SPEC.spec)
        assertThat(textFeedback)
            .isEqualTo(
                TextFeedbackViewModel.LoadedTextFeedback(
                    label = context.getString(config.uiConfig.labelRes),
                    icon =
                        Icon.Loaded(
                            drawable = context.getDrawable(config.uiConfig.iconRes)!!,
                            contentDescription = null,
                            res = config.uiConfig.iconRes,
                        ),
                )
            )
        assertThat(foregroundServices!!.displayText).isFalse()
    }

    companion object {
        val AIRPLANE_MODE_TILE_SPEC = TileSpec.create(ConnectivityModule.AIRPLANE_MODE_TILE_SPEC)

        private fun createAndPopulateQsTileConfigProvider(): QSTileConfigProvider {
            val logger =
                QsEventLoggerFake(UiEventLoggerFake(), InstanceIdSequenceFake(Int.MAX_VALUE))

            return FakeQSTileConfigProvider().apply {
                putConfig(
                    AIRPLANE_MODE_TILE_SPEC,
                    ConnectivityModule.provideAirplaneModeTileConfig(logger),
                )
            }
        }
    }
}
+96 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2025 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.qs.panels.data.repository

import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.kosmos.collectLastValue
import com.android.systemui.kosmos.runCurrent
import com.android.systemui.kosmos.runTest
import com.android.systemui.kosmos.testScope
import com.android.systemui.lifecycle.activateIn
import com.android.systemui.qs.panels.data.model.TextFeedbackRequestModel
import com.android.systemui.qs.pipeline.shared.TileSpec
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlin.test.Test
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.cancelAndJoin
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch
import org.junit.Before
import org.junit.runner.RunWith

@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
class ToggleTextFeedbackRepositoryTest : SysuiTestCase() {
    private val kosmos = testKosmos()

    private val underTest = kosmos.toggleTextFeedbackRepository

    @Before
    fun setUp() {
        underTest.activateIn(kosmos.testScope)
    }

    @Test
    fun noFeedbackOnStart() =
        with(kosmos) {
            runTest {
                val feedbackRequest by collectLastValue(underTest.textFeedback)
                assertThat(feedbackRequest).isEqualTo(TextFeedbackRequestModel.NoFeedback)
            }
        }

    @Test
    fun feedbackRequestShown() =
        with(kosmos) {
            runTest {
                val feedbackRequest by collectLastValue(underTest.textFeedback)

                underTest.setTextFeedback(TILE_SPEC)

                assertThat(feedbackRequest)
                    .isEqualTo(TextFeedbackRequestModel.FeedbackForTile(TILE_SPEC))
            }
        }

    @Test
    fun feedbackRequest_noFeedbackIfNoCollectors_firstIsAlwaysNoFeedback() =
        with(kosmos) {
            runTest {
                val collectionJob =
                    testScope.backgroundScope.launch { underTest.textFeedback.collect {} }

                underTest.setTextFeedback(TILE_SPEC)
                runCurrent()

                collectionJob.cancelAndJoin()
                runCurrent()

                assertThat(underTest.textFeedback.first())
                    .isEqualTo(TextFeedbackRequestModel.NoFeedback)
            }
        }

    companion object {
        private val TILE_SPEC = TileSpec.create("a")
        private val OTHER_TILE_SPEC = TileSpec.create("b")
    }
}
+254 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2025 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.qs.panels.domain.interactor

import android.content.ComponentName
import android.graphics.drawable.TestStubDrawable
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.kosmos.collectLastValue
import com.android.systemui.kosmos.runCurrent
import com.android.systemui.kosmos.runTest
import com.android.systemui.kosmos.testScope
import com.android.systemui.qs.panels.domain.model.TextFeedbackModel
import com.android.systemui.qs.pipeline.data.repository.FakeInstalledTilesComponentRepository
import com.android.systemui.qs.pipeline.data.repository.fakeInstalledTilesRepository
import com.android.systemui.qs.pipeline.shared.TileSpec
import com.android.systemui.qs.tiles.base.shared.model.QSTileConfig
import com.android.systemui.qs.tiles.base.shared.model.populateQsTileConfigProvider
import com.android.systemui.qs.tiles.impl.airplane.qsAirplaneModeTileConfig
import com.android.systemui.qs.tiles.impl.flashlight.qsFlashlightTileConfig
import com.android.systemui.settings.fakeUserTracker
import com.android.systemui.settings.userTracker
import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.mock
import com.google.common.truth.Truth.assertThat
import kotlin.test.Test
import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.advanceTimeBy
import org.junit.runner.RunWith
import org.mockito.kotlin.whenever

@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
class TextFeedbackInteractorTest : SysuiTestCase() {
    private val kosmos =
        testKosmos().apply {
            populateQsTileConfigProvider()
            whenever(fakeUserTracker.userContext.packageManager).thenReturn(mock())
        }

    private val underTest = kosmos.textFeedbackInteractor

    @Test
    fun noFeedback() =
        with(kosmos) {
            runTest {
                val textFeedback by collectLastValue(underTest.textFeedback)

                assertThat(textFeedback).isEqualTo(TextFeedbackModel.NoFeedback)
            }
        }

    @Test
    fun knownTileTextFeedback() =
        with(kosmos) {
            runTest {
                val textFeedback by collectLastValue(underTest.textFeedback)

                underTest.requestShowFeedback(qsAirplaneModeTileConfig.tileSpec)
                runCurrent()

                assertThat(textFeedback).isEqualTo(qsAirplaneModeTileConfig.toTextFeedbackModel())
            }
        }

    @Test
    fun unknownTileTextFeedback_noFeedback() =
        with(kosmos) {
            runTest {
                val textFeedback by collectLastValue(underTest.textFeedback)

                underTest.requestShowFeedback(TileSpec.create("unknown"))
                runCurrent()

                assertThat(textFeedback).isEqualTo(TextFeedbackModel.NoFeedback)
            }
        }

    @Test
    fun customTile_notInstalled_NoFeedback() =
        with(kosmos) {
            runTest {
                val textFeedback by collectLastValue(underTest.textFeedback)

                underTest.requestShowFeedback(CUSTOM_TILE_SPEC)
                runCurrent()

                assertThat(textFeedback).isEqualTo(TextFeedbackModel.NoFeedback)
            }
        }

    @Test
    fun customTile_installed_loadedIconAndLabel() =
        with(kosmos) {
            runTest {
                val textFeedback by collectLastValue(underTest.textFeedback)
                fakeInstalledTilesRepository.setInstalledServicesForUser(
                    userTracker.userId,
                    listOf(
                        FakeInstalledTilesComponentRepository.ServiceInfo(
                            CUSTOM_TILE_SPEC.componentName,
                            serviceName = SERVICE_NAME,
                            serviceIcon = SERVICE_ICON,
                        )
                    ),
                )
                runCurrent()

                underTest.requestShowFeedback(CUSTOM_TILE_SPEC)
                runCurrent()

                assertThat(textFeedback)
                    .isEqualTo(
                        TextFeedbackModel.LoadedTextFeedback(
                            label = SERVICE_NAME,
                            icon = Icon.Loaded(SERVICE_ICON, null),
                        )
                    )
            }
        }

    @Test
    fun customTile_installed_differentUser_noFeedback() =
        with(kosmos) {
            runTest {
                val textFeedback by collectLastValue(underTest.textFeedback)
                fakeInstalledTilesRepository.setInstalledServicesForUser(
                    userTracker.userId + 1,
                    listOf(
                        FakeInstalledTilesComponentRepository.ServiceInfo(
                            CUSTOM_TILE_SPEC.componentName,
                            serviceName = SERVICE_NAME,
                            serviceIcon = SERVICE_ICON,
                        )
                    ),
                )
                runCurrent()

                underTest.requestShowFeedback(CUSTOM_TILE_SPEC)
                runCurrent()

                assertThat(textFeedback).isEqualTo(TextFeedbackModel.NoFeedback)
            }
        }

    @Test
    fun feedbackRequestRemainsVisibleForSomeTime() =
        with(kosmos) {
            runTest {
                val feedbackRequest by collectLastValue(underTest.textFeedback)

                underTest.requestShowFeedback(qsAirplaneModeTileConfig.tileSpec)
                runCurrent()

                testScope.advanceTimeBy(TextFeedbackInteractor.CLEAR_DELAY - 1.milliseconds)

                assertThat(feedbackRequest)
                    .isEqualTo(qsAirplaneModeTileConfig.toTextFeedbackModel())
            }
        }

    @Test
    fun feedbackRequest_afterClearDelay_noFeedback() =
        with(kosmos) {
            runTest {
                val feedbackRequest by collectLastValue(underTest.textFeedback)

                underTest.requestShowFeedback(qsAirplaneModeTileConfig.tileSpec)
                runCurrent()

                testScope.advanceTimeBy(TextFeedbackInteractor.CLEAR_DELAY + 1.milliseconds)
                runCurrent()

                assertThat(feedbackRequest).isEqualTo(TextFeedbackModel.NoFeedback)
            }
        }

    @Test
    fun feedbackRequest_thenAnotherFeedbackRequest_timerRestarted() =
        with(kosmos) {
            runTest {
                val textFeedback by collectLastValue(underTest.textFeedback)

                underTest.requestShowFeedback(qsAirplaneModeTileConfig.tileSpec)
                runCurrent()

                testScope.advanceTimeBy(TextFeedbackInteractor.CLEAR_DELAY - 1.milliseconds)
                runCurrent()

                underTest.requestShowFeedback(qsFlashlightTileConfig.tileSpec)

                assertThat(textFeedback).isEqualTo(qsFlashlightTileConfig.toTextFeedbackModel())

                testScope.advanceTimeBy(TextFeedbackInteractor.CLEAR_DELAY - 1.milliseconds)

                assertThat(textFeedback).isEqualTo(qsFlashlightTileConfig.toTextFeedbackModel())

                testScope.advanceTimeBy(2.milliseconds)

                assertThat(textFeedback).isEqualTo(TextFeedbackModel.NoFeedback)
            }
        }

    @Test
    fun feedbackRequest_thenSameTileRequest_timerRestarted() =
        with(kosmos) {
            runTest {
                val textFeedback by collectLastValue(underTest.textFeedback)

                underTest.requestShowFeedback(qsAirplaneModeTileConfig.tileSpec)
                runCurrent()

                testScope.advanceTimeBy(TextFeedbackInteractor.CLEAR_DELAY - 1.milliseconds)
                runCurrent()

                underTest.requestShowFeedback(qsAirplaneModeTileConfig.tileSpec)
                testScope.advanceTimeBy(TextFeedbackInteractor.CLEAR_DELAY - 1.milliseconds)

                assertThat(textFeedback).isEqualTo(qsAirplaneModeTileConfig.toTextFeedbackModel())

                testScope.advanceTimeBy(2.milliseconds)

                assertThat(textFeedback).isEqualTo(TextFeedbackModel.NoFeedback)
            }
        }

    companion object {
        private fun QSTileConfig.toTextFeedbackModel(): TextFeedbackModel.TextFeedback {
            return TextFeedbackModel.TextFeedback(uiConfig.labelRes, uiConfig.iconRes)
        }

        private val CUSTOM_TILE_SPEC = TileSpec.create(ComponentName("pkg", "srv"))
        private val SERVICE_NAME = "TileService"
        private val SERVICE_ICON = TestStubDrawable("tile_service_icon")
    }
}
Loading