summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Anton Potapov <apotapov@google.com> 2024-01-03 15:58:35 +0000
committer Anton Potapov <apotapov@google.com> 2024-02-20 18:48:29 +0000
commit4a0351795693e6c166b96aa3e19a43ded8cc24eb (patch)
treef7c6272f30c2b1d42790fb700e65e5f2b9f20393
parent3fdca447365094bd8a5011226b3e4bd290fb057e (diff)
Add Custom Tile tests
Flag: aconfig qs_new_tiles DISABLED Test: atest CustomTileDataInteractorTest Test: atest CustomTileMapperTest Test: atest CustomTileUserActionInteractorTest Test: atest CustomTileInteractorTest Bug: 301055700 Change-Id: I43626613fbebe240e8dc16b081a4cda3778c095d
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileDataInteractorTest.kt241
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileInteractorTest.kt143
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileMapperTest.kt265
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileUserActionInteractorTest.kt283
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileDataInteractor.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileInteractor.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileServiceInteractor.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileUserActionInteractor.kt5
-rw-r--r--packages/SystemUI/tests/utils/src/android/graphics/drawable/TestStubDrawable.kt19
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/FakeIQSTileService.kt61
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/FakeTileServiceManagerFacade.kt59
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/FakeTileServicesFacade.kt (renamed from packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/TileLifecycleManagerFactoryKosmos.kt)23
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/TilesExternalKosmos.kt37
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/CustomTileKosmos.kt56
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/QSTileStateSubject.kt4
15 files changed, 1161 insertions, 55 deletions
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileDataInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileDataInteractorTest.kt
new file mode 100644
index 000000000000..a5c554406848
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileDataInteractorTest.kt
@@ -0,0 +1,241 @@
+/*
+ * 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.qs.tiles.impl.custom.domain.interactor
+
+import android.content.ComponentName
+import android.content.pm.UserInfo
+import android.graphics.drawable.Icon
+import android.service.quicksettings.Tile
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.coroutines.collectValues
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.qs.external.componentName
+import com.android.systemui.qs.external.iQSTileService
+import com.android.systemui.qs.external.tileServiceManagerFacade
+import com.android.systemui.qs.external.tileServicesFacade
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger
+import com.android.systemui.qs.tiles.impl.custom.TileSubject.Companion.assertThat
+import com.android.systemui.qs.tiles.impl.custom.customTileDefaultsRepository
+import com.android.systemui.qs.tiles.impl.custom.customTileInteractor
+import com.android.systemui.qs.tiles.impl.custom.customTilePackagesUpdatesRepository
+import com.android.systemui.qs.tiles.impl.custom.customTileRepository
+import com.android.systemui.qs.tiles.impl.custom.customTileServiceInteractor
+import com.android.systemui.qs.tiles.impl.custom.data.entity.CustomTileDefaults
+import com.android.systemui.qs.tiles.impl.custom.tileSpec
+import com.android.systemui.testKosmos
+import com.android.systemui.user.data.repository.fakeUserRepository
+import com.android.systemui.user.data.repository.userRepository
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@OptIn(ExperimentalCoroutinesApi::class)
+class CustomTileDataInteractorTest : SysuiTestCase() {
+
+ private val kosmos =
+ testKosmos().apply {
+ componentName = TEST_COMPONENT
+ tileSpec = TileSpec.create(componentName)
+ }
+ private val underTest =
+ with(kosmos) {
+ CustomTileDataInteractor(
+ tileSpec = tileSpec,
+ defaultsRepository = customTileDefaultsRepository,
+ serviceInteractor = customTileServiceInteractor,
+ customTileInteractor = customTileInteractor,
+ packageUpdatesRepository = customTilePackagesUpdatesRepository,
+ userRepository = userRepository,
+ tileScope = testScope.backgroundScope,
+ )
+ }
+
+ private suspend fun setup() {
+ with(kosmos) {
+ fakeUserRepository.setUserInfos(listOf(TEST_USER_1))
+ fakeUserRepository.setSelectedUserInfo(TEST_USER_1)
+ }
+ }
+
+ @Test
+ fun activeTileIsNotBoundUntilDataCollected() =
+ with(kosmos) {
+ testScope.runTest {
+ setup()
+ customTileRepository.setTileActive(true)
+
+ runCurrent()
+
+ assertThat(iQSTileService.isTileListening).isFalse()
+ assertThat(tileServiceManagerFacade.isBound).isFalse()
+ }
+ }
+
+ @Test
+ fun notActiveTileIsNotBoundUntilDataCollected() =
+ with(kosmos) {
+ testScope.runTest {
+ setup()
+ customTileRepository.setTileActive(false)
+
+ runCurrent()
+
+ assertThat(iQSTileService.isTileListening).isFalse()
+ assertThat(tileServiceManagerFacade.isBound).isFalse()
+ }
+ }
+
+ @Test
+ fun tileIsUnboundWhenDataIsNotListened() =
+ with(kosmos) {
+ testScope.runTest {
+ setup()
+ customTileRepository.setTileActive(false)
+ customTileDefaultsRepository.putDefaults(
+ TEST_USER_1.userHandle,
+ componentName,
+ CustomTileDefaults.Result(TEST_TILE.icon, TEST_TILE.label),
+ )
+ val dataJob =
+ underTest
+ .tileData(TEST_USER_1.userHandle, flowOf(DataUpdateTrigger.InitialRequest))
+ .launchIn(backgroundScope)
+ runCurrent()
+ tileServiceManagerFacade.processPendingBind()
+ assertThat(iQSTileService.isTileListening).isTrue()
+ assertThat(tileServiceManagerFacade.isBound).isTrue()
+
+ dataJob.cancel()
+ runCurrent()
+
+ assertThat(iQSTileService.isTileListening).isFalse()
+ assertThat(tileServiceManagerFacade.isBound).isFalse()
+ }
+ }
+
+ @Test
+ fun tileDataCollection() =
+ with(kosmos) {
+ testScope.runTest {
+ setup()
+ customTileDefaultsRepository.putDefaults(
+ TEST_USER_1.userHandle,
+ componentName,
+ CustomTileDefaults.Result(TEST_TILE.icon, TEST_TILE.label),
+ )
+ val tileData by
+ collectLastValue(
+ underTest.tileData(
+ TEST_USER_1.userHandle,
+ flowOf(DataUpdateTrigger.InitialRequest)
+ )
+ )
+ runCurrent()
+ tileServicesFacade.customTileInterface!!.updateTileState(TEST_TILE, 1)
+
+ runCurrent()
+
+ with(tileData!!) {
+ assertThat(user.identifier).isEqualTo(TEST_USER_1.id)
+ assertThat(componentName).isEqualTo(componentName)
+ assertThat(tile).isEqualTo(TEST_TILE)
+ assertThat(callingAppUid).isEqualTo(1)
+ assertThat(hasPendingBind).isEqualTo(true)
+ assertThat(isToggleable).isEqualTo(false)
+ assertThat(defaultTileIcon).isEqualTo(TEST_TILE.icon)
+ assertThat(defaultTileLabel).isEqualTo(TEST_TILE.label)
+ }
+ }
+ }
+
+ @Test
+ fun tileAvailableWhenDefaultsAreLoaded() =
+ with(kosmos) {
+ testScope.runTest {
+ setup()
+ customTileDefaultsRepository.putDefaults(
+ TEST_USER_1.userHandle,
+ tileSpec.componentName,
+ CustomTileDefaults.Result(TEST_TILE.icon, TEST_TILE.label),
+ )
+
+ val isAvailable by collectValues(underTest.availability(TEST_USER_1.userHandle))
+ runCurrent()
+
+ assertThat(isAvailable).containsExactlyElementsIn(arrayOf(true)).inOrder()
+ }
+ }
+
+ @Test
+ fun tileUnavailableWhenDefaultsAreNotLoaded() =
+ with(kosmos) {
+ testScope.runTest {
+ setup()
+ customTileDefaultsRepository.putDefaults(
+ TEST_USER_1.userHandle,
+ tileSpec.componentName,
+ CustomTileDefaults.Error,
+ )
+
+ val isAvailable by collectValues(underTest.availability(TEST_USER_1.userHandle))
+ runCurrent()
+
+ assertThat(isAvailable).containsExactlyElementsIn(arrayOf(false)).inOrder()
+ }
+ }
+
+ @Test
+ fun tileAvailabilityUndefinedWhenDefaultsAreLoadedForAnotherUser() =
+ with(kosmos) {
+ testScope.runTest {
+ setup()
+ customTileDefaultsRepository.putDefaults(
+ TEST_USER_2.userHandle,
+ tileSpec.componentName,
+ CustomTileDefaults.Error,
+ )
+
+ val isAvailable by collectValues(underTest.availability(TEST_USER_1.userHandle))
+ runCurrent()
+
+ assertThat(isAvailable).containsExactlyElementsIn(arrayOf()).inOrder()
+ }
+ }
+
+ private companion object {
+
+ val TEST_COMPONENT = ComponentName("test.pkg", "test.cls")
+ val TEST_USER_1 = UserInfo(1, "first user", UserInfo.FLAG_MAIN)
+ val TEST_USER_2 = UserInfo(2, "second user", UserInfo.FLAG_MAIN)
+ val TEST_TILE =
+ Tile().apply {
+ label = "test_tile_1"
+ icon = Icon.createWithContentUri("file://test_1")
+ }
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileInteractorTest.kt
index 995d6ac66137..9546a32e2a06 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileInteractorTest.kt
@@ -25,7 +25,6 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectValues
-import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.testScope
import com.android.systemui.qs.external.TileServiceKey
import com.android.systemui.qs.pipeline.shared.TileSpec
@@ -35,6 +34,7 @@ import com.android.systemui.qs.tiles.impl.custom.customTileRepository
import com.android.systemui.qs.tiles.impl.custom.customTileStatePersister
import com.android.systemui.qs.tiles.impl.custom.data.entity.CustomTileDefaults
import com.android.systemui.qs.tiles.impl.custom.tileSpec
+import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.first
@@ -50,16 +50,16 @@ import org.junit.runner.RunWith
@OptIn(ExperimentalCoroutinesApi::class)
class CustomTileInteractorTest : SysuiTestCase() {
- private val kosmos = Kosmos().apply { tileSpec = TileSpec.create(TEST_COMPONENT) }
+ private val kosmos = testKosmos().apply { tileSpec = TileSpec.create(TEST_COMPONENT) }
private val underTest: CustomTileInteractor =
with(kosmos) {
CustomTileInteractor(
- tileSpec,
- customTileDefaultsRepository,
- customTileRepository,
- testScope.backgroundScope,
- testScope.testScheduler,
+ tileSpec = tileSpec,
+ defaultsRepository = customTileDefaultsRepository,
+ customTileRepository = customTileRepository,
+ tileScope = testScope.backgroundScope,
+ backgroundContext = testScope.testScheduler,
)
}
@@ -69,14 +69,14 @@ class CustomTileInteractorTest : SysuiTestCase() {
testScope.runTest {
customTileRepository.setTileActive(true)
customTileStatePersister.persistState(
- TileServiceKey(TEST_COMPONENT, TEST_USER.identifier),
- TEST_TILE,
+ TileServiceKey(TEST_COMPONENT, TEST_USER_1.identifier),
+ TEST_TILE_1,
)
- underTest.initForUser(TEST_USER)
+ underTest.initForUser(TEST_USER_1)
- assertThat(underTest.getTile(TEST_USER)).isEqualTo(TEST_TILE)
- assertThat(underTest.getTiles(TEST_USER).first()).isEqualTo(TEST_TILE)
+ assertThat(underTest.getTile(TEST_USER_1)).isEqualTo(TEST_TILE_1)
+ assertThat(underTest.getTiles(TEST_USER_1).first()).isEqualTo(TEST_TILE_1)
}
}
@@ -86,18 +86,18 @@ class CustomTileInteractorTest : SysuiTestCase() {
testScope.runTest {
customTileRepository.setTileActive(false)
customTileStatePersister.persistState(
- TileServiceKey(TEST_COMPONENT, TEST_USER.identifier),
- TEST_TILE,
+ TileServiceKey(TEST_COMPONENT, TEST_USER_1.identifier),
+ TEST_TILE_1,
)
- val tiles = collectValues(underTest.getTiles(TEST_USER))
- val initJob = launch { underTest.initForUser(TEST_USER) }
+ val tiles = collectValues(underTest.getTiles(TEST_USER_1))
+ val initJob = launch { underTest.initForUser(TEST_USER_1) }
- underTest.updateTile(TEST_TILE)
+ underTest.updateTile(TEST_TILE_1)
runCurrent()
initJob.join()
assertThat(tiles()).hasSize(1)
- assertThat(tiles().last()).isEqualTo(TEST_TILE)
+ assertThat(tiles().last()).isEqualTo(TEST_TILE_1)
}
}
@@ -107,34 +107,34 @@ class CustomTileInteractorTest : SysuiTestCase() {
testScope.runTest {
customTileRepository.setTileActive(false)
customTileStatePersister.persistState(
- TileServiceKey(TEST_COMPONENT, TEST_USER.identifier),
- TEST_TILE,
+ TileServiceKey(TEST_COMPONENT, TEST_USER_1.identifier),
+ TEST_TILE_1,
)
- val tiles = collectValues(underTest.getTiles(TEST_USER))
- val initJob = launch { underTest.initForUser(TEST_USER) }
+ val tiles = collectValues(underTest.getTiles(TEST_USER_1))
+ val initJob = launch { underTest.initForUser(TEST_USER_1) }
- customTileDefaultsRepository.putDefaults(TEST_USER, TEST_COMPONENT, TEST_DEFAULTS)
- customTileDefaultsRepository.requestNewDefaults(TEST_USER, TEST_COMPONENT)
+ customTileDefaultsRepository.putDefaults(TEST_USER_1, TEST_COMPONENT, TEST_DEFAULTS)
+ customTileDefaultsRepository.requestNewDefaults(TEST_USER_1, TEST_COMPONENT)
runCurrent()
initJob.join()
assertThat(tiles()).hasSize(1)
- assertThat(tiles().last()).isEqualTo(TEST_TILE)
+ assertThat(tiles().last()).isEqualTo(TEST_TILE_1)
}
}
@Test(expected = IllegalStateException::class)
fun getTileBeforeInitThrows() =
- with(kosmos) { testScope.runTest { underTest.getTile(TEST_USER) } }
+ with(kosmos) { testScope.runTest { underTest.getTile(TEST_USER_1) } }
@Test
fun initSuspendsForActiveTileNotRestoredAndNotUpdated() =
with(kosmos) {
testScope.runTest {
customTileRepository.setTileActive(true)
- val tiles = collectValues(underTest.getTiles(TEST_USER))
+ val tiles = collectValues(underTest.getTiles(TEST_USER_1))
- val initJob = backgroundScope.launch { underTest.initForUser(TEST_USER) }
+ val initJob = backgroundScope.launch { underTest.initForUser(TEST_USER_1) }
advanceTimeBy(1 * DateUtils.DAY_IN_MILLIS)
// Is still suspended
@@ -149,12 +149,12 @@ class CustomTileInteractorTest : SysuiTestCase() {
testScope.runTest {
customTileRepository.setTileActive(false)
customTileStatePersister.persistState(
- TileServiceKey(TEST_COMPONENT, TEST_USER.identifier),
- TEST_TILE,
+ TileServiceKey(TEST_COMPONENT, TEST_USER_1.identifier),
+ TEST_TILE_1,
)
- val tiles = collectValues(underTest.getTiles(TEST_USER))
+ val tiles = collectValues(underTest.getTiles(TEST_USER_1))
- val initJob = backgroundScope.launch { underTest.initForUser(TEST_USER) }
+ val initJob = backgroundScope.launch { underTest.initForUser(TEST_USER_1) }
advanceTimeBy(1 * DateUtils.DAY_IN_MILLIS)
// Is still suspended
@@ -176,18 +176,89 @@ class CustomTileInteractorTest : SysuiTestCase() {
}
}
+ @Test
+ fun activeFollowsTheRepository() {
+ with(kosmos) {
+ testScope.runTest {
+ customTileRepository.setTileActive(false)
+ assertThat(underTest.isTileActive()).isFalse()
+
+ customTileRepository.setTileActive(true)
+ assertThat(underTest.isTileActive()).isTrue()
+ }
+ }
+ }
+
+ @Test
+ fun initForTheSameUserProcessedOnce() =
+ with(kosmos) {
+ testScope.runTest {
+ customTileRepository.setTileActive(false)
+ customTileStatePersister.persistState(
+ TileServiceKey(TEST_COMPONENT, TEST_USER_1.identifier),
+ TEST_TILE_1,
+ )
+ val tiles = collectValues(underTest.getTiles(TEST_USER_1))
+ val initJob = launch {
+ underTest.initForUser(TEST_USER_1)
+ underTest.initForUser(TEST_USER_1)
+ }
+
+ underTest.updateTile(TEST_TILE_1)
+ runCurrent()
+ initJob.join()
+
+ assertThat(tiles()).hasSize(1)
+ assertThat(tiles().last()).isEqualTo(TEST_TILE_1)
+ }
+ }
+
+ @Test
+ fun initForDifferentUsersProcessedOnce() =
+ with(kosmos) {
+ testScope.runTest {
+ customTileRepository.setTileActive(true)
+ customTileStatePersister.persistState(
+ TileServiceKey(TEST_COMPONENT, TEST_USER_1.identifier),
+ TEST_TILE_1,
+ )
+ customTileStatePersister.persistState(
+ TileServiceKey(TEST_COMPONENT, TEST_USER_2.identifier),
+ TEST_TILE_2,
+ )
+ val tiles1 by collectValues(underTest.getTiles(TEST_USER_1))
+ val tiles2 by collectValues(underTest.getTiles(TEST_USER_2))
+
+ val initJob = launch {
+ underTest.initForUser(TEST_USER_1)
+ underTest.initForUser(TEST_USER_2)
+ }
+ runCurrent()
+ initJob.join()
+
+ assertThat(tiles1).isEmpty()
+ assertThat(tiles2).hasSize(1)
+ assertThat(tiles2.last()).isEqualTo(TEST_TILE_2)
+ }
+ }
+
private companion object {
val TEST_COMPONENT = ComponentName("test.pkg", "test.cls")
- val TEST_USER = UserHandle.of(1)!!
- val TEST_TILE by lazy {
+ val TEST_USER_1 = UserHandle.of(1)!!
+ val TEST_USER_2 = UserHandle.of(2)!!
+ val TEST_TILE_1 by lazy {
Tile().apply {
label = "test_tile_1"
icon = Icon.createWithContentUri("file://test_1")
}
}
- val TEST_DEFAULTS by lazy {
- CustomTileDefaults.Result(TEST_TILE.icon, TEST_TILE.label)
+ val TEST_TILE_2 by lazy {
+ Tile().apply {
+ label = "test_tile_2"
+ icon = Icon.createWithContentUri("file://test_2")
+ }
}
+ val TEST_DEFAULTS by lazy { CustomTileDefaults.Result(TEST_TILE_1.icon, TEST_TILE_1.label) }
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileMapperTest.kt
new file mode 100644
index 000000000000..a2127a4717ce
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileMapperTest.kt
@@ -0,0 +1,265 @@
+/*
+ * 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.qs.tiles.impl.custom.domain.interactor
+
+import android.app.IUriGrantsManager
+import android.content.ComponentName
+import android.graphics.drawable.Drawable
+import android.graphics.drawable.Icon
+import android.graphics.drawable.TestStubDrawable
+import android.os.UserHandle
+import android.service.quicksettings.Tile
+import android.widget.Button
+import android.widget.Switch
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.tiles.impl.custom.QSTileStateSubject.Companion.assertThat
+import com.android.systemui.qs.tiles.impl.custom.customTileQsTileConfig
+import com.android.systemui.qs.tiles.impl.custom.domain.CustomTileMapper
+import com.android.systemui.qs.tiles.impl.custom.domain.entity.CustomTileDataModel
+import com.android.systemui.qs.tiles.impl.custom.tileSpec
+import com.android.systemui.qs.tiles.viewmodel.QSTileState
+import com.android.systemui.testKosmos
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class CustomTileMapperTest : SysuiTestCase() {
+
+ private val uriGrantsManager: IUriGrantsManager = mock {}
+ private val kosmos = testKosmos().apply { tileSpec = TileSpec.Companion.create(TEST_COMPONENT) }
+ private val underTest by lazy {
+ CustomTileMapper(
+ context = mock { whenever(createContextAsUser(any(), any())).thenReturn(context) },
+ uriGrantsManager = uriGrantsManager,
+ )
+ }
+
+ @Test
+ fun stateHasPendingBinding() =
+ with(kosmos) {
+ testScope.runTest {
+ val actual =
+ underTest.map(
+ customTileQsTileConfig,
+ createModel(hasPendingBind = true),
+ )
+ val expected =
+ createTileState(
+ activationState = QSTileState.ActivationState.UNAVAILABLE,
+ actions = setOf(QSTileState.UserAction.LONG_CLICK),
+ )
+
+ assertThat(actual).isEqualTo(expected)
+ }
+ }
+
+ @Test
+ fun stateActive() =
+ with(kosmos) {
+ testScope.runTest {
+ val actual =
+ underTest.map(
+ customTileQsTileConfig,
+ createModel(tileState = Tile.STATE_ACTIVE),
+ )
+ val expected =
+ createTileState(
+ activationState = QSTileState.ActivationState.ACTIVE,
+ )
+
+ assertThat(actual).isEqualTo(expected)
+ }
+ }
+
+ @Test
+ fun stateInactive() =
+ with(kosmos) {
+ testScope.runTest {
+ val actual =
+ underTest.map(
+ customTileQsTileConfig,
+ createModel(tileState = Tile.STATE_INACTIVE),
+ )
+ val expected =
+ createTileState(
+ activationState = QSTileState.ActivationState.INACTIVE,
+ )
+
+ assertThat(actual).isEqualTo(expected)
+ }
+ }
+
+ @Test
+ fun stateUnavailable() =
+ with(kosmos) {
+ testScope.runTest {
+ val actual =
+ underTest.map(
+ customTileQsTileConfig,
+ createModel(tileState = Tile.STATE_UNAVAILABLE),
+ )
+ val expected =
+ createTileState(
+ activationState = QSTileState.ActivationState.UNAVAILABLE,
+ actions = setOf(QSTileState.UserAction.LONG_CLICK),
+ )
+
+ assertThat(actual).isEqualTo(expected)
+ }
+ }
+
+ @Test
+ fun tileWithChevron() =
+ with(kosmos) {
+ testScope.runTest {
+ val actual =
+ underTest.map(
+ customTileQsTileConfig,
+ createModel(isToggleable = false),
+ )
+ val expected =
+ createTileState(
+ sideIcon = QSTileState.SideViewIcon.Chevron,
+ a11yClass = Button::class.qualifiedName,
+ )
+
+ assertThat(actual).isEqualTo(expected)
+ }
+ }
+
+ @Test
+ fun defaultIconFallback() =
+ with(kosmos) {
+ testScope.runTest {
+ val actual =
+ underTest.map(
+ customTileQsTileConfig,
+ createModel(tileIcon = createIcon(RuntimeException(), false)),
+ )
+ val expected =
+ createTileState(
+ activationState = QSTileState.ActivationState.INACTIVE,
+ icon = DEFAULT_DRAWABLE,
+ )
+
+ assertThat(actual).isEqualTo(expected)
+ }
+ }
+
+ @Test
+ fun failedToLoadIconTileIsInactive() =
+ with(kosmos) {
+ testScope.runTest {
+ val actual =
+ underTest.map(
+ customTileQsTileConfig,
+ createModel(
+ tileIcon = createIcon(RuntimeException(), false),
+ defaultTileIcon = createIcon(null, true)
+ ),
+ )
+ val expected =
+ createTileState(
+ icon = null,
+ activationState = QSTileState.ActivationState.INACTIVE,
+ )
+
+ assertThat(actual).isEqualTo(expected)
+ }
+ }
+
+ private fun Kosmos.createModel(
+ tileState: Int = Tile.STATE_ACTIVE,
+ tileIcon: Icon = createIcon(DRAWABLE, false),
+ hasPendingBind: Boolean = false,
+ isToggleable: Boolean = true,
+ defaultTileIcon: Icon = createIcon(DEFAULT_DRAWABLE, true),
+ ) =
+ CustomTileDataModel(
+ UserHandle.of(1),
+ tileSpec.componentName,
+ Tile().apply {
+ state = tileState
+ label = "test label"
+ subtitle = "test subtitle"
+ icon = tileIcon
+ contentDescription = "test content description"
+ },
+ callingAppUid = 0,
+ hasPendingBind = hasPendingBind,
+ isToggleable = isToggleable,
+ defaultTileLabel = "test default tile label",
+ defaultTileIcon = defaultTileIcon,
+ )
+
+ private fun createIcon(drawable: Drawable?, isDefault: Boolean): Icon = mock {
+ if (isDefault) {
+ whenever(loadDrawable(any())).thenReturn(drawable)
+ } else {
+ whenever(loadDrawableCheckingUriGrant(any(), any(), any(), any())).thenReturn(drawable)
+ }
+ }
+
+ private fun createIcon(exception: RuntimeException, isDefault: Boolean): Icon = mock {
+ if (isDefault) {
+ whenever(loadDrawable(any())).thenThrow(exception)
+ } else {
+ whenever(loadDrawableCheckingUriGrant(any(), eq(uriGrantsManager), any(), any()))
+ .thenThrow(exception)
+ }
+ }
+
+ private fun createTileState(
+ activationState: QSTileState.ActivationState = QSTileState.ActivationState.ACTIVE,
+ icon: Drawable? = DRAWABLE,
+ sideIcon: QSTileState.SideViewIcon = QSTileState.SideViewIcon.None,
+ actions: Set<QSTileState.UserAction> =
+ setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK),
+ a11yClass: String? = Switch::class.qualifiedName,
+ ): QSTileState {
+ return QSTileState(
+ { icon?.let { com.android.systemui.common.shared.model.Icon.Loaded(icon, null) } },
+ "test label",
+ activationState,
+ "test subtitle",
+ actions,
+ "test content description",
+ null,
+ sideIcon,
+ QSTileState.EnabledState.ENABLED,
+ a11yClass,
+ )
+ }
+
+ private companion object {
+ val TEST_COMPONENT = ComponentName("test.pkg", "test.cls")
+
+ val DEFAULT_DRAWABLE = TestStubDrawable("default_icon_drawable")
+ val DRAWABLE = TestStubDrawable("icon_drawable")
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileUserActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileUserActionInteractorTest.kt
new file mode 100644
index 000000000000..c709f16c3213
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileUserActionInteractorTest.kt
@@ -0,0 +1,283 @@
+/*
+ * 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.qs.tiles.impl.custom.domain.interactor
+
+import android.app.PendingIntent
+import android.content.ComponentName
+import android.content.Intent
+import android.content.pm.ActivityInfo
+import android.content.pm.PackageManager
+import android.content.pm.ResolveInfo
+import android.content.pm.UserInfo
+import android.graphics.drawable.Icon
+import android.provider.Settings
+import android.service.quicksettings.Tile
+import android.service.quicksettings.TileService
+import android.view.IWindowManager
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testCase
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.qs.external.componentName
+import com.android.systemui.qs.external.iQSTileService
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.tiles.base.actions.FakeQSTileIntentUserInputHandler
+import com.android.systemui.qs.tiles.base.actions.intentInputs
+import com.android.systemui.qs.tiles.base.actions.pendingIntentInputs
+import com.android.systemui.qs.tiles.base.interactor.QSTileInputTestKtx.click
+import com.android.systemui.qs.tiles.base.interactor.QSTileInputTestKtx.longClick
+import com.android.systemui.qs.tiles.impl.custom.customTileServiceInteractor
+import com.android.systemui.qs.tiles.impl.custom.domain.entity.CustomTileDataModel
+import com.android.systemui.qs.tiles.impl.custom.qsTileLogger
+import com.android.systemui.qs.tiles.impl.custom.tileSpec
+import com.android.systemui.testKosmos
+import com.android.systemui.user.data.repository.fakeUserRepository
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.nullable
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class CustomTileUserActionInteractorTest : SysuiTestCase() {
+
+ private val inputHandler = FakeQSTileIntentUserInputHandler()
+ private val packageManagerFacade = FakePackageManagerFacade()
+ private val windowManagerFacade = FakeWindowManagerFacade()
+ private val kosmos =
+ testKosmos().apply {
+ componentName = TEST_COMPONENT
+ tileSpec = TileSpec.create(componentName)
+ testCase = this@CustomTileUserActionInteractorTest
+ }
+
+ private val underTest =
+ with(kosmos) {
+ CustomTileUserActionInteractor(
+ context =
+ mock {
+ whenever(packageManager).thenReturn(packageManagerFacade.packageManager)
+ },
+ tileSpec = tileSpec,
+ qsTileLogger = qsTileLogger,
+ windowManager = windowManagerFacade.windowManager,
+ displayTracker = mock {},
+ qsTileIntentUserInputHandler = inputHandler,
+ backgroundContext = testDispatcher,
+ serviceInteractor = customTileServiceInteractor,
+ )
+ }
+
+ private suspend fun setup() {
+ with(kosmos) {
+ fakeUserRepository.setUserInfos(listOf(TEST_USER_1))
+ fakeUserRepository.setSelectedUserInfo(TEST_USER_1)
+ }
+ }
+
+ @Test
+ fun clickStartsActivityWhenPossible() =
+ with(kosmos) {
+ testScope.runTest {
+ setup()
+ underTest.handleInput(
+ click(customTileModel(activityLaunchForClick = pendingIntent()))
+ )
+
+ assertThat(windowManagerFacade.isTokenGranted).isTrue()
+ assertThat(inputHandler.pendingIntentInputs).hasSize(1)
+ assertThat(iQSTileService.clicks).hasSize(0)
+ }
+ }
+
+ @Test
+ fun clickPassedToTheServiceWhenNoActivity() =
+ with(kosmos) {
+ testScope.runTest {
+ setup()
+ packageManagerFacade.resolutionResult = null
+ underTest.handleInput(click(customTileModel(activityLaunchForClick = null)))
+
+ assertThat(windowManagerFacade.isTokenGranted).isTrue()
+ assertThat(inputHandler.pendingIntentInputs).hasSize(0)
+ assertThat(iQSTileService.clicks).hasSize(1)
+ }
+ }
+
+ @Test
+ fun longClickOpensResolvedIntent() =
+ with(kosmos) {
+ testScope.runTest {
+ setup()
+ packageManagerFacade.resolutionResult =
+ ActivityInfo().apply {
+ packageName = "resolved.pkg"
+ name = "Test"
+ }
+ underTest.handleInput(longClick(customTileModel()))
+
+ assertThat(inputHandler.intentInputs).hasSize(1)
+ with(inputHandler.intentInputs.first()) {
+ assertThat(intent.action).isEqualTo(TileService.ACTION_QS_TILE_PREFERENCES)
+ assertThat(intent.component).isEqualTo(ComponentName("resolved.pkg", "Test"))
+ assertThat(
+ intent.getParcelableExtra(
+ Intent.EXTRA_COMPONENT_NAME,
+ ComponentName::class.java
+ )
+ )
+ .isEqualTo(componentName)
+ assertThat(intent.getIntExtra(TileService.EXTRA_STATE, Int.MAX_VALUE))
+ .isEqualTo(111)
+ }
+ }
+ }
+
+ @Test
+ fun longClickOpensDefaultIntentWhenNoResolved() =
+ with(kosmos) {
+ testScope.runTest {
+ setup()
+ underTest.handleInput(longClick(customTileModel()))
+
+ assertThat(inputHandler.intentInputs).hasSize(1)
+ with(inputHandler.intentInputs.first()) {
+ assertThat(intent.action)
+ .isEqualTo(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
+ assertThat(intent.data.toString()).isEqualTo("package:test.pkg")
+ }
+ }
+ }
+
+ @Test
+ fun revokeTokenDoesntRevokeWhenShowingDialog() =
+ with(kosmos) {
+ testScope.runTest {
+ setup()
+ underTest.handleInput(click(customTileModel()))
+ underTest.setShowingDialog(true)
+
+ underTest.revokeToken(false)
+
+ assertThat(windowManagerFacade.isTokenGranted).isTrue()
+ }
+ }
+
+ @Test
+ fun forceRevokeTokenRevokesWhenShowingDialog() =
+ with(kosmos) {
+ testScope.runTest {
+ setup()
+ underTest.handleInput(click(customTileModel()))
+ underTest.setShowingDialog(true)
+
+ underTest.revokeToken(true)
+
+ assertThat(windowManagerFacade.isTokenGranted).isFalse()
+ }
+ }
+
+ @Test
+ fun revokeTokenRevokesWhenNotShowingDialog() =
+ with(kosmos) {
+ testScope.runTest {
+ setup()
+ underTest.handleInput(click(customTileModel()))
+ underTest.setShowingDialog(false)
+
+ underTest.revokeToken(false)
+
+ assertThat(windowManagerFacade.isTokenGranted).isFalse()
+ }
+ }
+
+ @Test
+ fun startActivityDoesntStartWithNoToken() =
+ with(kosmos) {
+ testScope.runTest {
+ setup()
+ underTest.startActivityAndCollapse(mock())
+
+ // Checking all types of inputs
+ assertThat(inputHandler.handledInputs).isEmpty()
+ }
+ }
+
+ private fun pendingIntent(): PendingIntent = mock { whenever(isActivity).thenReturn(true) }
+
+ private fun Kosmos.customTileModel(
+ componentName: ComponentName = tileSpec.componentName,
+ activityLaunchForClick: PendingIntent? = null,
+ tileState: Int = 111,
+ ) =
+ CustomTileDataModel(
+ TEST_USER_1.userHandle,
+ componentName,
+ Tile().also {
+ it.activityLaunchForClick = activityLaunchForClick
+ it.state = tileState
+ },
+ callingAppUid = 0,
+ hasPendingBind = false,
+ isToggleable = false,
+ defaultTileLabel = "default_label",
+ defaultTileIcon = Icon.createWithContentUri("default_icon"),
+ )
+
+ private class FakePackageManagerFacade(val packageManager: PackageManager = mock()) {
+
+ var resolutionResult: ActivityInfo? = null
+
+ init {
+ whenever(packageManager.resolveActivityAsUser(any(), any<Int>(), any())).then {
+ ResolveInfo().apply { activityInfo = resolutionResult }
+ }
+ }
+ }
+
+ private class FakeWindowManagerFacade(val windowManager: IWindowManager = mock()) {
+
+ var isTokenGranted: Boolean = false
+ private set
+
+ init {
+ with(windowManager) {
+ whenever(removeWindowToken(any(), any())).then {
+ isTokenGranted = false
+ Unit
+ }
+ whenever(addWindowToken(any(), any(), any(), nullable())).then {
+ isTokenGranted = true
+ Unit
+ }
+ }
+ }
+ }
+
+ private companion object {
+
+ val TEST_COMPONENT = ComponentName("test.pkg", "test.cls")
+ val TEST_USER_1 = UserInfo(1, "first user", UserInfo.FLAG_MAIN)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileDataInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileDataInteractor.kt
index cff95d8368a2..1b3e58524815 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileDataInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileDataInteractor.kt
@@ -68,8 +68,7 @@ constructor(
serviceInteractor.setUser(user)
// Wait for the CustomTileInteractor to become initialized first, because
- // binding
- // the service might access it
+ // binding the service might access it
customTileInteractor.initForUser(user)
// Bind the TileService for not active tile
serviceInteractor.bindOnStart()
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileInteractor.kt
index fd96fc5b6693..3e507cda4805 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileInteractor.kt
@@ -77,6 +77,13 @@ constructor(
suspend fun isTileToggleable(): Boolean = customTileRepository.isTileToggleable()
/**
+ * True if the tile is active and false the otherwise. This effectively is a value of the
+ * [android.service.quicksettings.TileService.META_DATA_ACTIVE_TILE]. This is not the same as
+ * [Tile.STATE_ACTIVE].
+ */
+ suspend fun isTileActive(): Boolean = customTileRepository.isTileActive()
+
+ /**
* Initializes the repository for the current user. Suspends until it's safe to call [getTile]
* which needs at least one of the following:
* - defaults are loaded;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileServiceInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileServiceInteractor.kt
index acff40f816a9..79e903c7bce9 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileServiceInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileServiceInteractor.kt
@@ -58,7 +58,7 @@ constructor(
private val activityStarter: ActivityStarter,
private val userActionInteractor: Lazy<CustomTileUserActionInteractor>,
private val customTileInteractor: CustomTileInteractor,
- private val userRepository: UserRepository,
+ userRepository: UserRepository,
private val qsTileLogger: QSTileLogger,
private val tileServices: TileServices,
@QSTileScope private val tileScope: CoroutineScope,
@@ -78,10 +78,10 @@ constructor(
get() = tileReceivingInterface.mutableRefreshEvents
/** Clears all pending binding for an active tile and binds not active one. */
- fun bindOnStart() {
+ suspend fun bindOnStart() {
try {
with(getTileServiceManager()) {
- if (isActiveTile) {
+ if (customTileInteractor.isTileActive()) {
clearPendingBind()
} else {
setBindRequested(true)
@@ -94,10 +94,10 @@ constructor(
}
/** Binds active tile WITHOUT CLEARING pending binds. */
- fun bindOnClick() {
+ suspend fun bindOnClick() {
try {
with(getTileServiceManager()) {
- if (isActiveTile) {
+ if (customTileInteractor.isTileActive()) {
setBindRequested(true)
tileServiceInterface.onStartListening()
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileUserActionInteractor.kt
index c3e1feaa6dfe..a16ac360e7e4 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileUserActionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileUserActionInteractor.kt
@@ -77,7 +77,7 @@ constructor(
qsTileLogger.logCustomTileUserActionDelivered(tileSpec)
}
- private fun click(
+ private suspend fun click(
view: View?,
activityLaunchForClick: PendingIntent?,
) {
@@ -114,9 +114,6 @@ constructor(
}
fun startActivityAndCollapse(pendingIntent: PendingIntent) {
- if (!pendingIntent.isActivity) {
- return
- }
if (!isTokenGranted) {
return
}
diff --git a/packages/SystemUI/tests/utils/src/android/graphics/drawable/TestStubDrawable.kt b/packages/SystemUI/tests/utils/src/android/graphics/drawable/TestStubDrawable.kt
index b88f302cdfdd..1a9f4b40c179 100644
--- a/packages/SystemUI/tests/utils/src/android/graphics/drawable/TestStubDrawable.kt
+++ b/packages/SystemUI/tests/utils/src/android/graphics/drawable/TestStubDrawable.kt
@@ -24,12 +24,27 @@ import android.graphics.PixelFormat
* Stub drawable that does nothing. It's to be used in tests as a mock drawable and checked for the
* same instance
*/
-class TestStubDrawable : Drawable() {
+class TestStubDrawable(private val name: String? = null) : Drawable() {
override fun draw(canvas: Canvas) = Unit
override fun setAlpha(alpha: Int) = Unit
override fun setColorFilter(colorFilter: ColorFilter?) = Unit
override fun getOpacity(): Int = PixelFormat.UNKNOWN
- override fun equals(other: Any?): Boolean = this === other
+ override fun toString(): String {
+ return name ?: super.toString()
+ }
+
+ override fun getConstantState(): ConstantState =
+ TestStubConstantState(this, changingConfigurations)
+
+ private class TestStubConstantState(
+ private val drawable: Drawable,
+ private val changingConfigurations: Int,
+ ) : ConstantState() {
+
+ override fun newDrawable(): Drawable = drawable
+
+ override fun getChangingConfigurations(): Int = changingConfigurations
+ }
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/FakeIQSTileService.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/FakeIQSTileService.kt
new file mode 100644
index 000000000000..cff59807e00f
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/FakeIQSTileService.kt
@@ -0,0 +1,61 @@
+/*
+ * 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.qs.external
+
+import android.os.Binder
+import android.os.IBinder
+import android.service.quicksettings.IQSTileService
+
+class FakeIQSTileService : IQSTileService {
+
+ var isTileAdded: Boolean = false
+ private set
+ var isTileListening: Boolean = false
+ private set
+ var isUnlockComplete: Boolean = false
+ val clicks: List<IBinder?>
+ get() = mutableClicks
+
+ private val mutableClicks: MutableList<IBinder?> = mutableListOf()
+ private val binder = Binder()
+
+ override fun asBinder(): IBinder = binder
+
+ override fun onTileAdded() {
+ isTileAdded = true
+ }
+
+ override fun onTileRemoved() {
+ isTileAdded = false
+ }
+
+ override fun onStartListening() {
+ isTileListening = true
+ }
+
+ override fun onStopListening() {
+ isTileListening = false
+ }
+
+ override fun onClick(wtoken: IBinder?) {
+ mutableClicks.add(wtoken)
+ }
+
+ override fun onUnlockComplete() {
+ isUnlockComplete = true
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/FakeTileServiceManagerFacade.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/FakeTileServiceManagerFacade.kt
new file mode 100644
index 000000000000..101335f38531
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/FakeTileServiceManagerFacade.kt
@@ -0,0 +1,59 @@
+/*
+ * 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.qs.external
+
+import android.service.quicksettings.IQSTileService
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+
+// TODO(b/299909989) Make a fake instead
+class FakeTileServiceManagerFacade(
+ private val iQSTileService: IQSTileService,
+ val tileServiceManager: TileServiceManager = mock {},
+) {
+
+ private var hasPendingBind: Boolean = false
+
+ var isBound: Boolean = false
+ private set
+
+ init {
+ with(tileServiceManager) {
+ whenever(tileService).thenReturn(iQSTileService)
+ whenever(setBindRequested(any())).then {
+ val isRequested: Boolean = it.getArgument(0)
+ hasPendingBind = isRequested
+ if (!isRequested) {
+ isBound = false
+ }
+ Unit
+ }
+ whenever(clearPendingBind()).then {
+ hasPendingBind = false
+ Unit
+ }
+ whenever(hasPendingBind()).then { hasPendingBind }
+ }
+ }
+
+ fun processPendingBind() {
+ if (hasPendingBind) {
+ isBound = true
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/TileLifecycleManagerFactoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/FakeTileServicesFacade.kt
index f8ce707b0bb2..0975e55295e4 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/TileLifecycleManagerFactoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/FakeTileServicesFacade.kt
@@ -16,9 +16,24 @@
package com.android.systemui.qs.external
-import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
-/** Returns mocks */
-var Kosmos.tileLifecycleManagerFactory: TileLifecycleManager.Factory by
- Kosmos.Fixture { TileLifecycleManager.Factory { _, _ -> mock<TileLifecycleManager>() } }
+class FakeTileServicesFacade(
+ private val TileServiceManager: TileServiceManager,
+ val tileServices: TileServices = mock {}
+) {
+
+ var customTileInterface: CustomTileInterface? = null
+ private set
+
+ init {
+ with(tileServices) {
+ whenever(getTileWrapper(any())).then {
+ customTileInterface = it.getArgument(0)
+ TileServiceManager
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/TilesExternalKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/TilesExternalKosmos.kt
new file mode 100644
index 000000000000..36c2c2b6eb23
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/TilesExternalKosmos.kt
@@ -0,0 +1,37 @@
+/*
+ * 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.qs.external
+
+import android.content.ComponentName
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.util.mockito.mock
+
+var Kosmos.componentName: ComponentName by Kosmos.Fixture()
+
+/** Returns mocks */
+var Kosmos.tileLifecycleManagerFactory: TileLifecycleManager.Factory by Kosmos.Fixture { mock {} }
+
+val Kosmos.iQSTileService: FakeIQSTileService by Kosmos.Fixture { FakeIQSTileService() }
+val Kosmos.tileServiceManagerFacade: FakeTileServiceManagerFacade by
+ Kosmos.Fixture { FakeTileServiceManagerFacade(iQSTileService) }
+
+val Kosmos.tileServiceManager: TileServiceManager by
+ Kosmos.Fixture { tileServiceManagerFacade.tileServiceManager }
+
+val Kosmos.tileServicesFacade: FakeTileServicesFacade by
+ Kosmos.Fixture { (FakeTileServicesFacade(tileServiceManager)) }
+val Kosmos.tileServices: TileServices by Kosmos.Fixture { tileServicesFacade.tileServices }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/CustomTileKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/CustomTileKosmos.kt
index 14f28fedc5e4..561e2540d465 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/CustomTileKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/CustomTileKosmos.kt
@@ -17,19 +17,47 @@
package com.android.systemui.qs.tiles.impl.custom
import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testCase
+import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.kosmos.testScope
+import com.android.systemui.plugins.activityStarter
import com.android.systemui.qs.external.FakeCustomTileStatePersister
+import com.android.systemui.qs.external.tileServices
import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.tiles.base.actions.FakeQSTileIntentUserInputHandler
+import com.android.systemui.qs.tiles.base.logging.QSTileLogger
import com.android.systemui.qs.tiles.impl.custom.data.repository.FakeCustomTileDefaultsRepository
import com.android.systemui.qs.tiles.impl.custom.data.repository.FakeCustomTilePackageUpdatesRepository
import com.android.systemui.qs.tiles.impl.custom.data.repository.FakeCustomTileRepository
import com.android.systemui.qs.tiles.impl.custom.data.repository.FakePackageManagerAdapterFacade
+import com.android.systemui.qs.tiles.impl.custom.domain.interactor.CustomTileInteractor
+import com.android.systemui.qs.tiles.impl.custom.domain.interactor.CustomTileServiceInteractor
+import com.android.systemui.qs.tiles.impl.custom.domain.interactor.CustomTileUserActionInteractor
+import com.android.systemui.qs.tiles.viewmodel.QSTileConfig
+import com.android.systemui.qs.tiles.viewmodel.QSTileConfigTestBuilder
+import com.android.systemui.user.data.repository.userRepository
+import com.android.systemui.util.mockito.mock
var Kosmos.tileSpec: TileSpec.CustomTileSpec by Kosmos.Fixture()
+var Kosmos.customTileQsTileConfig: QSTileConfig by
+ Kosmos.Fixture { QSTileConfigTestBuilder.build { tileSpec = this@Fixture.tileSpec } }
+val Kosmos.qsTileLogger: QSTileLogger by Kosmos.Fixture { mock {} }
+
val Kosmos.customTileStatePersister: FakeCustomTileStatePersister by
Kosmos.Fixture { FakeCustomTileStatePersister() }
+val Kosmos.customTileInteractor: CustomTileInteractor by
+ Kosmos.Fixture {
+ CustomTileInteractor(
+ tileSpec,
+ customTileDefaultsRepository,
+ customTileRepository,
+ testScope.backgroundScope,
+ testScope.testScheduler,
+ )
+ }
+
val Kosmos.customTileRepository: FakeCustomTileRepository by
Kosmos.Fixture {
FakeCustomTileRepository(
@@ -48,3 +76,31 @@ val Kosmos.customTilePackagesUpdatesRepository: FakeCustomTilePackageUpdatesRepo
val Kosmos.packageManagerAdapterFacade: FakePackageManagerAdapterFacade by
Kosmos.Fixture { FakePackageManagerAdapterFacade(tileSpec.componentName) }
+
+val Kosmos.customTileServiceInteractor: CustomTileServiceInteractor by
+ Kosmos.Fixture {
+ CustomTileServiceInteractor(
+ tileSpec,
+ activityStarter,
+ { customTileUserActionInteractor },
+ customTileInteractor,
+ userRepository,
+ qsTileLogger,
+ tileServices,
+ testScope.backgroundScope,
+ )
+ }
+
+val Kosmos.customTileUserActionInteractor: CustomTileUserActionInteractor by
+ Kosmos.Fixture {
+ CustomTileUserActionInteractor(
+ testCase.context,
+ tileSpec,
+ qsTileLogger,
+ mock {},
+ mock {},
+ FakeQSTileIntentUserInputHandler(),
+ testDispatcher,
+ customTileServiceInteractor,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/QSTileStateSubject.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/QSTileStateSubject.kt
index 9d0faca94fb4..4f5c9b48690d 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/QSTileStateSubject.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/QSTileStateSubject.kt
@@ -70,7 +70,7 @@ private constructor(failureMetadata: FailureMetadata, subject: QSTileState?) :
}
/** Shortcut for `Truth.assertAbout(states()).that(state)`. */
- fun assertThat(state: QSTileState?): QSTileStateSubject =
- Truth.assertAbout(states()).that(state)
+ fun assertThat(actual: QSTileState?): QSTileStateSubject =
+ Truth.assertAbout(states()).that(actual)
}
}