summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/external/TileServiceRequestControllerTestComposeOn.kt276
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/external/ui/viewmodel/TileRequestDialogViewModelTest.kt150
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/external/TileData.kt36
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/external/TileRequestDialog.kt89
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/external/TileServiceRequestController.kt193
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/external/ui/dialog/TileRequestDialogComposeDelegate.kt128
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/external/ui/viewmodel/TileRequestDialogViewModel.kt83
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/Tile.kt23
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/external/TileRequestDialogTest.kt68
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceRequestControllerTestComposeOff.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceRequestControllerTest.kt)255
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/app/IUriGrantsManagerKosmos.kt23
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/qs/QSHostAdapterKosmos.kt35
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/TileServiceRequestControllerKosmos.kt50
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/ui/dialog/FakeTileRequestDialogComposeDelegateFactory.kt36
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/ui/dialog/TileRequestDialogComposeDelegateKosmos.kt43
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/ui/viewmodel/TileRequestDialogViewModelKosmos.kt40
16 files changed, 1224 insertions, 304 deletions
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/external/TileServiceRequestControllerTestComposeOn.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/external/TileServiceRequestControllerTestComposeOn.kt
new file mode 100644
index 000000000000..f02856c2f5ae
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/external/TileServiceRequestControllerTestComposeOn.kt
@@ -0,0 +1,276 @@
+/*
+ * Copyright (C) 2024 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.app.Dialog
+import android.app.StatusBarManager
+import android.content.ComponentName
+import android.content.DialogInterface
+import android.graphics.drawable.Icon
+import android.platform.test.annotations.EnableFlags
+import android.view.WindowManager
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.internal.statusbar.IAddTileResultCallback
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.runCurrent
+import com.android.systemui.kosmos.runTest
+import com.android.systemui.qs.external.ui.dialog.FakeTileRequestDialogComposeDelegateFactory
+import com.android.systemui.qs.external.ui.dialog.fake
+import com.android.systemui.qs.external.ui.dialog.tileRequestDialogComposeDelegateFactory
+import com.android.systemui.qs.flags.QSComposeFragment
+import com.android.systemui.qs.pipeline.data.repository.fakeInstalledTilesRepository
+import com.android.systemui.qs.pipeline.domain.interactor.currentTilesInteractor
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.runOnMainThreadAndWaitForIdleSync
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import java.util.function.Consumer
+import kotlin.test.Test
+import org.junit.Before
+import org.junit.runner.RunWith
+import org.mockito.kotlin.mock
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@EnableFlags(QSComposeFragment.FLAG_NAME)
+class TileServiceRequestControllerTestComposeOn : SysuiTestCase() {
+ private val kosmos = testKosmos()
+
+ private val userId: Int
+ get() = kosmos.currentTilesInteractor.userId.value
+
+ private val mockIcon: Icon
+ get() = mock()
+
+ private val Kosmos.underTest by Kosmos.Fixture { tileServiceRequestController }
+
+ @Before
+ fun setup() {
+ kosmos.fakeInstalledTilesRepository.setInstalledPackagesForUser(
+ userId,
+ setOf(TEST_COMPONENT),
+ )
+ // Start with some tiles, so adding tiles is possible (adding tiles waits until there's
+ // at least one tile, to wait for setup).
+ kosmos.currentTilesInteractor.setTiles(listOf(TileSpec.create("a")))
+ kosmos.runCurrent()
+ }
+
+ @Test
+ fun tileAlreadyAdded_correctResult() =
+ kosmos.runTest {
+ // An existing tile
+ currentTilesInteractor.setTiles(listOf(TILE_SPEC))
+ runCurrent()
+
+ val callback = Callback()
+ runOnMainThreadAndWaitForIdleSync {
+ val dialog =
+ underTest.requestTileAdd(
+ TEST_UID,
+ TEST_COMPONENT,
+ TEST_APP_NAME,
+ TEST_LABEL,
+ mockIcon,
+ callback,
+ )
+ assertThat(dialog).isNull()
+ }
+
+ assertThat(callback.lastAccepted).isEqualTo(TILE_ALREADY_ADDED)
+ assertThat(currentTilesInteractor.currentTilesSpecs.count { it == TILE_SPEC })
+ .isEqualTo(1)
+ }
+
+ @Test
+ fun cancelDialog_dismissResult_tileNotAdded() =
+ kosmos.runTest {
+ val callback = Callback()
+ val dialog = runOnMainThreadAndWaitForIdleSync {
+ underTest.requestTileAdd(
+ TEST_UID,
+ TEST_COMPONENT,
+ TEST_APP_NAME,
+ TEST_LABEL,
+ mockIcon,
+ callback,
+ )!!
+ }
+
+ runOnMainThreadAndWaitForIdleSync { dialog.cancel() }
+
+ assertThat(callback.lastAccepted).isEqualTo(DISMISSED)
+ assertThat(currentTilesInteractor.currentTilesSpecs).doesNotContain(TILE_SPEC)
+ }
+
+ @Test
+ fun cancelAndThenDismissSendsOnlyOnce() =
+ kosmos.runTest {
+ // After cancelling, the dialog is dismissed. This tests that only one response
+ // is sent.
+ val callback = Callback()
+ val dialog = runOnMainThreadAndWaitForIdleSync {
+ underTest.requestTileAdd(
+ TEST_UID,
+ TEST_COMPONENT,
+ TEST_APP_NAME,
+ TEST_LABEL,
+ mockIcon,
+ callback,
+ )!!
+ }
+
+ runOnMainThreadAndWaitForIdleSync {
+ dialog.cancel()
+ dialog.dismiss()
+ }
+
+ assertThat(callback.lastAccepted).isEqualTo(DISMISSED)
+ assertThat(callback.timesCalled).isEqualTo(1)
+ }
+
+ @Test
+ fun showAllUsers_set() =
+ kosmos.runTest {
+ val dialog = runOnMainThreadAndWaitForIdleSync {
+ underTest.requestTileAdd(
+ TEST_UID,
+ TEST_COMPONENT,
+ TEST_APP_NAME,
+ TEST_LABEL,
+ mockIcon,
+ Callback(),
+ )!!
+ }
+ onTeardown { dialog.cancel() }
+
+ assertThat(dialog.isShowForAllUsers).isTrue()
+ }
+
+ @Test
+ fun cancelOnTouchOutside_set() =
+ kosmos.runTest {
+ val dialog = runOnMainThreadAndWaitForIdleSync {
+ underTest.requestTileAdd(
+ TEST_UID,
+ TEST_COMPONENT,
+ TEST_APP_NAME,
+ TEST_LABEL,
+ mockIcon,
+ Callback(),
+ )!!
+ }
+ onTeardown { dialog.cancel() }
+
+ assertThat(dialog.isCancelOnTouchOutside).isTrue()
+ }
+
+ @Test
+ fun positiveAction_tileAdded() =
+ kosmos.runTest {
+ // Not using a real dialog
+ tileRequestDialogComposeDelegateFactory = FakeTileRequestDialogComposeDelegateFactory()
+
+ val callback = Callback()
+ val dialog =
+ underTest.requestTileAdd(
+ TEST_UID,
+ TEST_COMPONENT,
+ TEST_APP_NAME,
+ TEST_LABEL,
+ mockIcon,
+ callback,
+ )
+
+ tileRequestDialogComposeDelegateFactory.fake.clickListener.onClick(
+ dialog,
+ DialogInterface.BUTTON_POSITIVE,
+ )
+ runCurrent()
+
+ assertThat(callback.lastAccepted).isEqualTo(ADD_TILE)
+ assertThat(currentTilesInteractor.currentTilesSpecs).hasSize(2)
+ assertThat(currentTilesInteractor.currentTilesSpecs.last()).isEqualTo(TILE_SPEC)
+ }
+
+ @Test
+ fun negativeAction_tileNotAdded() =
+ kosmos.runTest {
+ // Not using a real dialog
+ tileRequestDialogComposeDelegateFactory = FakeTileRequestDialogComposeDelegateFactory()
+
+ val callback = Callback()
+ val dialog =
+ underTest.requestTileAdd(
+ TEST_UID,
+ TEST_COMPONENT,
+ TEST_APP_NAME,
+ TEST_LABEL,
+ mockIcon,
+ callback,
+ )
+
+ tileRequestDialogComposeDelegateFactory.fake.clickListener.onClick(
+ dialog,
+ DialogInterface.BUTTON_NEGATIVE,
+ )
+ runCurrent()
+
+ assertThat(callback.lastAccepted).isEqualTo(DONT_ADD_TILE)
+ assertThat(currentTilesInteractor.currentTilesSpecs).doesNotContain(TILE_SPEC)
+ }
+
+ companion object {
+ private val TEST_COMPONENT = ComponentName("test_pkg", "test_cls")
+ private val TILE_SPEC = TileSpec.create(TEST_COMPONENT)
+ private const val TEST_APP_NAME = "App"
+ private const val TEST_LABEL = "Label"
+ private const val TEST_UID = 12345
+
+ const val ADD_TILE = StatusBarManager.TILE_ADD_REQUEST_RESULT_TILE_ADDED
+ const val DONT_ADD_TILE = StatusBarManager.TILE_ADD_REQUEST_RESULT_TILE_NOT_ADDED
+ const val TILE_ALREADY_ADDED = StatusBarManager.TILE_ADD_REQUEST_RESULT_TILE_ALREADY_ADDED
+ const val DISMISSED = StatusBarManager.TILE_ADD_REQUEST_RESULT_DIALOG_DISMISSED
+ }
+
+ private class Callback : IAddTileResultCallback.Stub(), Consumer<Int> {
+ var lastAccepted: Int? = null
+ private set
+
+ var timesCalled = 0
+ private set
+
+ override fun accept(t: Int) {
+ lastAccepted = t
+ timesCalled++
+ }
+
+ override fun onTileRequest(r: Int) {
+ accept(r)
+ }
+ }
+}
+
+private val Dialog.isShowForAllUsers: Boolean
+ get() =
+ window!!.attributes.privateFlags and
+ WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS != 0
+
+private val Dialog.isCancelOnTouchOutside: Boolean
+ get() = window!!.shouldCloseOnTouchOutside()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/external/ui/viewmodel/TileRequestDialogViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/external/ui/viewmodel/TileRequestDialogViewModelTest.kt
new file mode 100644
index 000000000000..369975a95579
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/external/ui/viewmodel/TileRequestDialogViewModelTest.kt
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2024 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.ui.viewmodel
+
+import android.content.applicationContext
+import android.content.res.mainResources
+import android.graphics.drawable.Icon
+import android.graphics.drawable.TestStubDrawable
+import android.service.quicksettings.Tile
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.app.iUriGrantsManager
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.kosmos.Kosmos
+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.plugins.qs.QSTile
+import com.android.systemui.qs.external.TileData
+import com.android.systemui.qs.panels.ui.viewmodel.toUiState
+import com.android.systemui.qs.tileimpl.QSTileImpl
+import com.android.systemui.qs.tileimpl.QSTileImpl.ResourceIcon
+import com.android.systemui.res.R
+import com.android.systemui.testKosmos
+import com.google.common.truth.Expect
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.stub
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class TileRequestDialogViewModelTest : SysuiTestCase() {
+
+ @get:Rule val expect: Expect = Expect.create()
+
+ private val kosmos = testKosmos()
+
+ private val icon: Icon = mock {
+ on {
+ loadDrawableCheckingUriGrant(
+ kosmos.applicationContext,
+ kosmos.iUriGrantsManager,
+ TEST_UID,
+ TEST_PACKAGE,
+ )
+ } doReturn (loadedDrawable)
+ }
+
+ private val tileData = TileData(TEST_UID, TEST_APP_NAME, TEST_LABEL, icon, TEST_PACKAGE)
+
+ private val Kosmos.underTest by
+ Kosmos.Fixture { tileRequestDialogViewModelFactory.create(applicationContext, tileData) }
+
+ private val baseResultLegacyState =
+ QSTile.State().apply {
+ label = TEST_LABEL
+ state = Tile.STATE_ACTIVE
+ handlesLongClick = false
+ }
+
+ @Test
+ fun uiState_beforeActivation_hasDefaultIcon_andCorrectData() =
+ kosmos.runTest {
+ val expectedState =
+ baseResultLegacyState.apply { icon = defaultIcon }.toUiState(mainResources)
+
+ with(underTest.uiState) {
+ expect.that(label).isEqualTo(TEST_LABEL)
+ expect.that(secondaryLabel).isEmpty()
+ expect.that(state).isEqualTo(expectedState.state)
+ expect.that(handlesLongClick).isFalse()
+ expect.that(handlesSecondaryClick).isFalse()
+ expect.that(icon.get()).isEqualTo(defaultIcon)
+ expect.that(sideDrawable).isNull()
+ expect.that(accessibilityUiState).isEqualTo(expectedState.accessibilityUiState)
+ }
+ }
+
+ @Test
+ fun uiState_afterActivation_hasCorrectIcon_andCorrectData() =
+ kosmos.runTest {
+ val expectedState =
+ baseResultLegacyState
+ .apply { icon = QSTileImpl.DrawableIcon(loadedDrawable) }
+ .toUiState(mainResources)
+
+ underTest.activateIn(testScope)
+ runCurrent()
+
+ with(underTest.uiState) {
+ expect.that(label).isEqualTo(TEST_LABEL)
+ expect.that(secondaryLabel).isEmpty()
+ expect.that(state).isEqualTo(expectedState.state)
+ expect.that(handlesLongClick).isFalse()
+ expect.that(handlesSecondaryClick).isFalse()
+ expect.that(icon.get()).isEqualTo(QSTileImpl.DrawableIcon(loadedDrawable))
+ expect.that(sideDrawable).isNull()
+ expect.that(accessibilityUiState).isEqualTo(expectedState.accessibilityUiState)
+ }
+ }
+
+ @Test
+ fun uiState_afterActivation_iconNotLoaded_usesDefault() =
+ kosmos.runTest {
+ icon.stub {
+ on {
+ loadDrawableCheckingUriGrant(
+ kosmos.applicationContext,
+ kosmos.iUriGrantsManager,
+ TEST_UID,
+ TEST_PACKAGE,
+ )
+ } doReturn (null)
+ }
+
+ underTest.activateIn(testScope)
+ runCurrent()
+
+ assertThat(underTest.uiState.icon.get()).isEqualTo(defaultIcon)
+ }
+
+ companion object {
+ private val defaultIcon: QSTile.Icon = ResourceIcon.get(R.drawable.android)
+ private val loadedDrawable = TestStubDrawable("loaded")
+
+ private const val TEST_PACKAGE = "test_pkg"
+ private const val TEST_APP_NAME = "App"
+ private const val TEST_LABEL = "Label"
+ private const val TEST_UID = 12345
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileData.kt b/packages/SystemUI/src/com/android/systemui/qs/external/TileData.kt
new file mode 100644
index 000000000000..de759687e012
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileData.kt
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2024 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.graphics.drawable.Icon
+import androidx.compose.runtime.Immutable
+
+/**
+ * Data bundle of information to show the user when requesting to add a TileService
+ *
+ * @property appName Name of the app requesting their [TileService] to be added.
+ * @property label Label of the tile.
+ * @property icon Icon for the tile.
+ */
+@Immutable
+data class TileData(
+ val callingUid: Int,
+ val appName: CharSequence,
+ val label: CharSequence,
+ val icon: Icon?,
+ val packageName: String,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileRequestDialog.kt b/packages/SystemUI/src/com/android/systemui/qs/external/TileRequestDialog.kt
index c3c587de5a24..5597f288e122 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/TileRequestDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileRequestDialog.kt
@@ -18,73 +18,73 @@ package com.android.systemui.qs.external
import android.app.IUriGrantsManager
import android.content.Context
-import android.graphics.drawable.Icon
import android.view.ContextThemeWrapper
import android.view.LayoutInflater
import android.view.ViewGroup
import android.widget.TextView
-import com.android.systemui.res.R
import com.android.systemui.plugins.qs.QSTile
import com.android.systemui.plugins.qs.QSTileView
import com.android.systemui.qs.tileimpl.QSTileImpl
import com.android.systemui.qs.tileimpl.QSTileImpl.ResourceIcon
import com.android.systemui.qs.tileimpl.QSTileViewImpl
+import com.android.systemui.res.R
import com.android.systemui.statusbar.phone.SystemUIDialog
-/**
- * Dialog to present to the user to ask for authorization to add a [TileService].
- */
-class TileRequestDialog(
- context: Context,
-) : SystemUIDialog(context) {
+/** Dialog to present to the user to ask for authorization to add a [TileService]. */
+class TileRequestDialog(context: Context) : SystemUIDialog(context) {
companion object {
internal val CONTENT_ID = R.id.content
}
- /**
- * Set the data of the tile to add, to show the user.
- */
+ /** Set the data of the tile to add, to show the user. */
fun setTileData(tileData: TileData, iUriGrantsManager: IUriGrantsManager) {
- val ll = (LayoutInflater
- .from(context)
- .inflate(R.layout.tile_service_request_dialog, null)
- as ViewGroup).apply {
+ val ll =
+ (LayoutInflater.from(context).inflate(R.layout.tile_service_request_dialog, null)
+ as ViewGroup)
+ .apply {
requireViewById<TextView>(R.id.text).apply {
- text = context
- .getString(R.string.qs_tile_request_dialog_text, tileData.appName)
+ text =
+ context.getString(
+ R.string.qs_tile_request_dialog_text,
+ tileData.appName,
+ )
}
addView(
- createTileView(tileData, iUriGrantsManager),
- context.resources.getDimensionPixelSize(
- R.dimen.qs_tile_service_request_tile_width),
- context.resources.getDimensionPixelSize(R.dimen.qs_quick_tile_size)
+ createTileView(tileData, iUriGrantsManager),
+ context.resources.getDimensionPixelSize(
+ R.dimen.qs_tile_service_request_tile_width
+ ),
+ context.resources.getDimensionPixelSize(R.dimen.qs_quick_tile_size),
)
isSelected = true
- }
+ }
val spacing = 0
setView(ll, spacing, spacing, spacing, spacing / 2)
}
private fun createTileView(
- tileData: TileData,
- iUriGrantsManager: IUriGrantsManager,
+ tileData: TileData,
+ iUriGrantsManager: IUriGrantsManager,
): QSTileView {
val themedContext = ContextThemeWrapper(context, R.style.Theme_SystemUI_QuickSettings)
val tile = QSTileViewImpl(themedContext, true)
- val state = QSTile.BooleanState().apply {
- label = tileData.label
- handlesLongClick = false
- icon = tileData.icon?.loadDrawableCheckingUriGrant(
- context,
- iUriGrantsManager,
- tileData.callingUid,
- tileData.packageName,
- )?.let {
- QSTileImpl.DrawableIcon(it)
- } ?: ResourceIcon.get(R.drawable.android)
- contentDescription = label
- }
+ val state =
+ QSTile.BooleanState().apply {
+ label = tileData.label
+ handlesLongClick = false
+ icon =
+ tileData.icon
+ ?.loadDrawableCheckingUriGrant(
+ context,
+ iUriGrantsManager,
+ tileData.callingUid,
+ tileData.packageName,
+ )
+ ?.let { QSTileImpl.DrawableIcon(it) }
+ ?: ResourceIcon.get(R.drawable.android)
+ contentDescription = label
+ }
tile.onStateChanged(state)
tile.post {
tile.stateDescription = ""
@@ -93,19 +93,4 @@ class TileRequestDialog(
}
return tile
}
-
- /**
- * Data bundle of information to show the user.
- *
- * @property appName Name of the app requesting their [TileService] to be added.
- * @property label Label of the tile.
- * @property icon Icon for the tile.
- */
- data class TileData(
- val callingUid: Int,
- val appName: CharSequence,
- val label: CharSequence,
- val icon: Icon?,
- val packageName: String,
- )
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceRequestController.kt b/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceRequestController.kt
index 08567afd729e..33e059074a81 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceRequestController.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceRequestController.kt
@@ -26,9 +26,11 @@ import android.os.RemoteException
import android.util.Log
import androidx.annotation.VisibleForTesting
import com.android.internal.statusbar.IAddTileResultCallback
-import com.android.systemui.res.R
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.qs.QSHost
+import com.android.systemui.qs.external.ui.dialog.TileRequestDialogComposeDelegate
+import com.android.systemui.qs.flags.QsInCompose
+import com.android.systemui.res.R
import com.android.systemui.statusbar.CommandQueue
import com.android.systemui.statusbar.commandline.Command
import com.android.systemui.statusbar.commandline.CommandRegistry
@@ -40,50 +42,50 @@ import javax.inject.Inject
private const val TAG = "TileServiceRequestController"
-/**
- * Controller to interface between [TileRequestDialog] and [QSHost].
- */
+/** Controller to interface between [TileRequestDialog] and [QSHost]. */
class TileServiceRequestController(
- private val qsHost: QSHost,
- private val commandQueue: CommandQueue,
- private val commandRegistry: CommandRegistry,
- private val eventLogger: TileRequestDialogEventLogger,
- private val iUriGrantsManager: IUriGrantsManager,
- private val dialogCreator: () -> TileRequestDialog = { TileRequestDialog(qsHost.context) }
+ private val qsHost: QSHost,
+ private val commandQueue: CommandQueue,
+ private val commandRegistry: CommandRegistry,
+ private val eventLogger: TileRequestDialogEventLogger,
+ private val iUriGrantsManager: IUriGrantsManager,
+ private val tileRequestDialogComposeDelegateFactory: TileRequestDialogComposeDelegate.Factory,
+ private val dialogCreator: () -> TileRequestDialog = { TileRequestDialog(qsHost.context) },
) {
companion object {
- internal const val ADD_TILE = StatusBarManager.TILE_ADD_REQUEST_RESULT_TILE_ADDED
- internal const val DONT_ADD_TILE = StatusBarManager.TILE_ADD_REQUEST_RESULT_TILE_NOT_ADDED
- internal const val TILE_ALREADY_ADDED =
- StatusBarManager.TILE_ADD_REQUEST_RESULT_TILE_ALREADY_ADDED
- internal const val DISMISSED = StatusBarManager.TILE_ADD_REQUEST_RESULT_DIALOG_DISMISSED
+ const val ADD_TILE = StatusBarManager.TILE_ADD_REQUEST_RESULT_TILE_ADDED
+ const val DONT_ADD_TILE = StatusBarManager.TILE_ADD_REQUEST_RESULT_TILE_NOT_ADDED
+ const val TILE_ALREADY_ADDED =
+ StatusBarManager.TILE_ADD_REQUEST_RESULT_TILE_ALREADY_ADDED
+ const val DISMISSED = StatusBarManager.TILE_ADD_REQUEST_RESULT_DIALOG_DISMISSED
}
private var dialogCanceller: ((String) -> Unit)? = null
- private val commandQueueCallback = object : CommandQueue.Callbacks {
- override fun requestAddTile(
- callingUid: Int,
- componentName: ComponentName,
- appName: CharSequence,
- label: CharSequence,
- icon: Icon,
- callback: IAddTileResultCallback
- ) {
- requestTileAdd(callingUid, componentName, appName, label, icon) {
- try {
- callback.onTileRequest(it)
- } catch (e: RemoteException) {
- Log.e(TAG, "Couldn't respond to request", e)
+ private val commandQueueCallback =
+ object : CommandQueue.Callbacks {
+ override fun requestAddTile(
+ callingUid: Int,
+ componentName: ComponentName,
+ appName: CharSequence,
+ label: CharSequence,
+ icon: Icon,
+ callback: IAddTileResultCallback,
+ ) {
+ requestTileAdd(callingUid, componentName, appName, label, icon) {
+ try {
+ callback.onTileRequest(it)
+ } catch (e: RemoteException) {
+ Log.e(TAG, "Couldn't respond to request", e)
+ }
}
}
- }
- override fun cancelRequestAddTile(packageName: String) {
- dialogCanceller?.invoke(packageName)
+ override fun cancelRequestAddTile(packageName: String) {
+ dialogCanceller?.invoke(packageName)
+ }
}
- }
fun init() {
commandRegistry.registerCommand("tile-service-add") { TileServiceRequestCommand() }
@@ -100,58 +102,87 @@ class TileServiceRequestController(
}
@VisibleForTesting
- internal fun requestTileAdd(
+ fun requestTileAdd(
callingUid: Int,
componentName: ComponentName,
appName: CharSequence,
label: CharSequence,
icon: Icon?,
- callback: Consumer<Int>
- ) {
+ callback: Consumer<Int>,
+ ): SystemUIDialog? {
val instanceId = eventLogger.newInstanceId()
val packageName = componentName.packageName
if (isTileAlreadyAdded(componentName)) {
callback.accept(TILE_ALREADY_ADDED)
eventLogger.logTileAlreadyAdded(packageName, instanceId)
- return
+ return null
}
- val dialogResponse = SingleShotConsumer<Int> { response ->
- if (response == ADD_TILE) {
- addTile(componentName)
- }
- dialogCanceller = null
- eventLogger.logUserResponse(response, packageName, instanceId)
- callback.accept(response)
- }
- val tileData = TileRequestDialog.TileData(
- callingUid,
- appName,
- label,
- icon,
- componentName.packageName,
- )
- createDialog(tileData, dialogResponse).also { dialog ->
- dialogCanceller = {
- if (packageName == it) {
- dialog.cancel()
+ val dialogResponse =
+ SingleShotConsumer<Int> { response ->
+ if (response == ADD_TILE) {
+ addTile(componentName)
}
dialogCanceller = null
+ eventLogger.logUserResponse(response, packageName, instanceId)
+ callback.accept(response)
+ }
+ val tileData = TileData(callingUid, appName, label, icon, componentName.packageName)
+ return if (QsInCompose.isEnabled) {
+ createComposeDialog(tileData, dialogResponse)
+ } else {
+ createDialog(tileData, dialogResponse)
+ }
+ .also { dialog ->
+ dialogCanceller = {
+ if (packageName == it) {
+ dialog.cancel()
+ }
+ dialogCanceller = null
+ }
+ dialog.show()
+ eventLogger.logDialogShown(packageName, instanceId)
+ }
+ }
+
+ private fun createComposeDialog(
+ tileData: TileData,
+ responseHandler: SingleShotConsumer<Int>,
+ ): SystemUIDialog {
+ val dialogClickListener =
+ DialogInterface.OnClickListener { _, which ->
+ if (which == Dialog.BUTTON_POSITIVE) {
+ responseHandler.accept(ADD_TILE)
+ } else {
+ responseHandler.accept(DONT_ADD_TILE)
+ }
+ }
+ return tileRequestDialogComposeDelegateFactory
+ .create(dialogListener = dialogClickListener, tiledata = tileData)
+ .createDialog()
+ .apply {
+ setShowForAllUsers(true)
+ setCanceledOnTouchOutside(true)
+ setOnCancelListener { responseHandler.accept(DISMISSED) }
+ // We want this in case the dialog is dismissed without it being cancelled (for
+ // example
+ // by going home or locking the device). We use a SingleShotConsumer so the response
+ // is only sent once, with the first value.
+ setOnDismissListener { responseHandler.accept(DISMISSED) }
}
- }.show()
- eventLogger.logDialogShown(packageName, instanceId)
}
private fun createDialog(
- tileData: TileRequestDialog.TileData,
- responseHandler: SingleShotConsumer<Int>
+ tileData: TileData,
+ responseHandler: SingleShotConsumer<Int>,
): SystemUIDialog {
- val dialogClickListener = DialogInterface.OnClickListener { _, which ->
- if (which == Dialog.BUTTON_POSITIVE) {
- responseHandler.accept(ADD_TILE)
- } else {
- responseHandler.accept(DONT_ADD_TILE)
+ val dialogClickListener =
+ DialogInterface.OnClickListener { _, which ->
+ if (which == Dialog.BUTTON_POSITIVE) {
+ responseHandler.accept(ADD_TILE)
+ } else {
+ responseHandler.accept(DONT_ADD_TILE)
+ }
}
- }
return dialogCreator().apply {
setTileData(tileData, iUriGrantsManager)
setShowForAllUsers(true)
@@ -173,19 +204,20 @@ class TileServiceRequestController(
inner class TileServiceRequestCommand : Command {
override fun execute(pw: PrintWriter, args: List<String>) {
- val componentName: ComponentName = ComponentName.unflattenFromString(args[0])
+ val componentName: ComponentName =
+ ComponentName.unflattenFromString(args[0])
?: run {
Log.w(TAG, "Malformed componentName ${args[0]}")
return
}
- requestTileAdd(0, componentName, args[1], args[2], null) {
- Log.d(TAG, "Response: $it")
- }
+ requestTileAdd(0, componentName, args[1], args[2], null) { Log.d(TAG, "Response: $it") }
}
override fun help(pw: PrintWriter) {
- pw.println("Usage: adb shell cmd statusbar tile-service-add " +
- "<componentName> <appName> <label>")
+ pw.println(
+ "Usage: adb shell cmd statusbar tile-service-add " +
+ "<componentName> <appName> <label>"
+ )
}
}
@@ -200,18 +232,23 @@ class TileServiceRequestController(
}
@SysUISingleton
- class Builder @Inject constructor(
+ class Builder
+ @Inject
+ constructor(
private val commandQueue: CommandQueue,
private val commandRegistry: CommandRegistry,
private val iUriGrantsManager: IUriGrantsManager,
+ private val tileRequestDialogComposeDelegateFactory:
+ TileRequestDialogComposeDelegate.Factory,
) {
fun create(qsHost: QSHost): TileServiceRequestController {
return TileServiceRequestController(
- qsHost,
- commandQueue,
- commandRegistry,
- TileRequestDialogEventLogger(),
- iUriGrantsManager,
+ qsHost,
+ commandQueue,
+ commandRegistry,
+ TileRequestDialogEventLogger(),
+ iUriGrantsManager,
+ tileRequestDialogComposeDelegateFactory,
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/ui/dialog/TileRequestDialogComposeDelegate.kt b/packages/SystemUI/src/com/android/systemui/qs/external/ui/dialog/TileRequestDialogComposeDelegate.kt
new file mode 100644
index 000000000000..446be9b9ebcb
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/ui/dialog/TileRequestDialogComposeDelegate.kt
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2024 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.ui.dialog
+
+import android.content.DialogInterface.BUTTON_NEGATIVE
+import android.content.DialogInterface.BUTTON_POSITIVE
+import android.content.DialogInterface.OnClickListener
+import androidx.compose.foundation.layout.Arrangement.spacedBy
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.width
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.dimensionResource
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.unit.dp
+import com.android.compose.PlatformButton
+import com.android.compose.PlatformOutlinedButton
+import com.android.compose.theme.PlatformTheme
+import com.android.systemui.dialog.ui.composable.AlertDialogContent
+import com.android.systemui.lifecycle.rememberViewModel
+import com.android.systemui.qs.external.TileData
+import com.android.systemui.qs.external.ui.viewmodel.TileRequestDialogViewModel
+import com.android.systemui.qs.panels.ui.compose.infinitegrid.LargeStaticTile
+import com.android.systemui.res.R
+import com.android.systemui.statusbar.phone.SystemUIDialog
+import com.android.systemui.statusbar.phone.SystemUIDialogFactory
+import com.android.systemui.statusbar.phone.create
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+
+class TileRequestDialogComposeDelegate
+@AssistedInject
+constructor(
+ private val sysuiDialogFactory: SystemUIDialogFactory,
+ private val tileRequestDialogViewModelFactory: TileRequestDialogViewModel.Factory,
+ @Assisted private val tileData: TileData,
+ @Assisted private val dialogListener: OnClickListener,
+) : SystemUIDialog.Delegate {
+
+ override fun createDialog(): SystemUIDialog {
+ return sysuiDialogFactory.create { TileRequestDialogContent(it) }
+ }
+
+ @Composable
+ private fun TileRequestDialogContent(dialog: SystemUIDialog) {
+ PlatformTheme {
+ AlertDialogContent(
+ title = {},
+ content = {
+ Column(
+ horizontalAlignment = Alignment.CenterHorizontally,
+ verticalArrangement = spacedBy(16.dp),
+ ) {
+ val viewModel =
+ rememberViewModel(traceName = "TileRequestDialog", key = tileData) {
+ tileRequestDialogViewModelFactory.create(dialog.context, tileData)
+ }
+
+ Text(
+ text =
+ stringResource(
+ R.string.qs_tile_request_dialog_text,
+ tileData.appName,
+ ),
+ textAlign = TextAlign.Start,
+ )
+
+ LargeStaticTile(
+ uiState = viewModel.uiState,
+ modifier =
+ Modifier.width(
+ dimensionResource(
+ id = R.dimen.qs_tile_service_request_tile_width
+ )
+ ),
+ )
+ }
+ },
+ positiveButton = {
+ PlatformButton(
+ onClick = {
+ dialogListener.onClick(dialog, BUTTON_POSITIVE)
+ dialog.dismiss()
+ }
+ ) {
+ Text(stringResource(R.string.qs_tile_request_dialog_add))
+ }
+ },
+ negativeButton = {
+ PlatformOutlinedButton(
+ onClick = {
+ dialogListener.onClick(dialog, BUTTON_NEGATIVE)
+ dialog.dismiss()
+ }
+ ) {
+ Text(stringResource(R.string.qs_tile_request_dialog_not_add))
+ }
+ },
+ )
+ }
+ }
+
+ @AssistedFactory
+ interface Factory {
+ fun create(
+ tiledata: TileData,
+ dialogListener: OnClickListener,
+ ): TileRequestDialogComposeDelegate
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/ui/viewmodel/TileRequestDialogViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/external/ui/viewmodel/TileRequestDialogViewModel.kt
new file mode 100644
index 000000000000..c756adc07ba4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/ui/viewmodel/TileRequestDialogViewModel.kt
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2024 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.ui.viewmodel
+
+import android.app.IUriGrantsManager
+import android.content.Context
+import androidx.compose.runtime.derivedStateOf
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.setValue
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.lifecycle.ExclusiveActivatable
+import com.android.systemui.plugins.qs.QSTile
+import com.android.systemui.qs.external.TileData
+import com.android.systemui.qs.panels.ui.viewmodel.toUiState
+import com.android.systemui.qs.tileimpl.QSTileImpl.DrawableIcon
+import com.android.systemui.qs.tileimpl.QSTileImpl.ResourceIcon
+import com.android.systemui.res.R
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.awaitCancellation
+import kotlinx.coroutines.withContext
+
+class TileRequestDialogViewModel
+@AssistedInject
+constructor(
+ private val iUriGrantsManager: IUriGrantsManager,
+ @Background private val backgroundDispatcher: CoroutineDispatcher,
+ @Assisted private val dialogContext: Context,
+ @Assisted private val tileData: TileData,
+) : ExclusiveActivatable() {
+
+ private var _icon by mutableStateOf(defaultIcon)
+
+ private val state: QSTile.State
+ get() =
+ QSTile.State().apply {
+ label = tileData.label
+ handlesLongClick = false
+ this.icon = _icon
+ }
+
+ val uiState by derivedStateOf { state.toUiState(dialogContext.resources) }
+
+ override suspend fun onActivated(): Nothing {
+ withContext(backgroundDispatcher) {
+ tileData.icon
+ ?.loadDrawableCheckingUriGrant(
+ dialogContext,
+ iUriGrantsManager,
+ tileData.callingUid,
+ tileData.packageName,
+ )
+ ?.run { _icon = DrawableIcon(this) }
+ }
+ awaitCancellation()
+ }
+
+ @AssistedFactory
+ interface Factory {
+ fun create(dialogContext: Context, tileData: TileData): TileRequestDialogViewModel
+ }
+
+ companion object {
+ private val defaultIcon: QSTile.Icon = ResourceIcon.get(R.drawable.android)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/Tile.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/Tile.kt
index abdf923ebe73..c798e5bb6dc7 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/Tile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/Tile.kt
@@ -24,6 +24,7 @@ import android.service.quicksettings.Tile.STATE_INACTIVE
import androidx.compose.animation.animateColorAsState
import androidx.compose.animation.core.animateDpAsState
import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.background
import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.layout.Arrangement
@@ -263,6 +264,28 @@ fun TileContainer(
}
@Composable
+fun LargeStaticTile(uiState: TileUiState, modifier: Modifier = Modifier) {
+ val colors = TileDefaults.getColorForState(uiState = uiState, iconOnly = false)
+
+ Box(
+ modifier
+ .clip(TileDefaults.animateTileShape(state = uiState.state))
+ .background(colors.background)
+ .height(TileHeight)
+ .tilePadding()
+ ) {
+ LargeTileContent(
+ label = uiState.label,
+ secondaryLabel = "",
+ icon = getTileIcon(icon = uiState.icon),
+ sideDrawable = null,
+ colors = colors,
+ squishiness = { 1f },
+ )
+ }
+}
+
+@Composable
private fun getTileIcon(icon: Supplier<QSTile.Icon?>): Icon {
val context = LocalContext.current
return icon.get()?.let {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileRequestDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileRequestDialogTest.kt
index 2db5e83cf185..d058484de204 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileRequestDialogTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileRequestDialogTest.kt
@@ -28,13 +28,14 @@ import android.widget.ImageView
import android.widget.TextView
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
-import com.android.systemui.res.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.plugins.qs.QSTileView
+import com.android.systemui.res.R
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
+import java.util.Arrays
import org.junit.After
import org.junit.Before
import org.junit.Test
@@ -45,7 +46,6 @@ import org.mockito.Mock
import org.mockito.Mockito
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
-import java.util.Arrays
@SmallTest
@RunWith(AndroidJUnit4::class)
@@ -62,16 +62,13 @@ class TileRequestDialogTest : SysuiTestCase() {
private lateinit var dialog: TileRequestDialog
- @Mock
- private lateinit var ugm: IUriGrantsManager
+ @Mock private lateinit var ugm: IUriGrantsManager
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
// Create in looper so we can make sure that the tile is fully updated
- TestableLooper.get(this).runWithLooper {
- dialog = TileRequestDialog(mContext)
- }
+ TestableLooper.get(this).runWithLooper { dialog = TileRequestDialog(mContext) }
}
@After
@@ -84,7 +81,7 @@ class TileRequestDialogTest : SysuiTestCase() {
@Test
fun setTileData_hasCorrectViews() {
val icon = Icon.createWithResource(mContext, R.drawable.cloud)
- val tileData = TileRequestDialog.TileData(UID, APP_NAME, LABEL, icon, PACKAGE)
+ val tileData = TileData(UID, APP_NAME, LABEL, icon, PACKAGE)
dialog.setTileData(tileData, ugm)
dialog.show()
@@ -99,7 +96,7 @@ class TileRequestDialogTest : SysuiTestCase() {
@Test
fun setTileData_hasCorrectAppName() {
val icon = Icon.createWithResource(mContext, R.drawable.cloud)
- val tileData = TileRequestDialog.TileData(UID, APP_NAME, LABEL, icon, PACKAGE)
+ val tileData = TileData(UID, APP_NAME, LABEL, icon, PACKAGE)
dialog.setTileData(tileData, ugm)
dialog.show()
@@ -112,7 +109,7 @@ class TileRequestDialogTest : SysuiTestCase() {
@Test
fun setTileData_hasCorrectLabel() {
val icon = Icon.createWithResource(mContext, R.drawable.cloud)
- val tileData = TileRequestDialog.TileData(UID, APP_NAME, LABEL, icon, PACKAGE)
+ val tileData = TileData(UID, APP_NAME, LABEL, icon, PACKAGE)
dialog.setTileData(tileData, ugm)
dialog.show()
@@ -127,7 +124,7 @@ class TileRequestDialogTest : SysuiTestCase() {
@Test
fun setTileData_hasIcon() {
val icon = Icon.createWithResource(mContext, R.drawable.cloud)
- val tileData = TileRequestDialog.TileData(UID, APP_NAME, LABEL, icon, PACKAGE)
+ val tileData = TileData(UID, APP_NAME, LABEL, icon, PACKAGE)
dialog.setTileData(tileData, ugm)
dialog.show()
@@ -141,7 +138,7 @@ class TileRequestDialogTest : SysuiTestCase() {
@Test
fun setTileData_nullIcon_hasIcon() {
- val tileData = TileRequestDialog.TileData(UID, APP_NAME, LABEL, null, PACKAGE)
+ val tileData = TileData(UID, APP_NAME, LABEL, null, PACKAGE)
dialog.setTileData(tileData, ugm)
dialog.show()
@@ -156,7 +153,7 @@ class TileRequestDialogTest : SysuiTestCase() {
@Test
fun setTileData_hasNoStateDescription() {
val icon = Icon.createWithResource(mContext, R.drawable.cloud)
- val tileData = TileRequestDialog.TileData(UID, APP_NAME, LABEL, icon, PACKAGE)
+ val tileData = TileData(UID, APP_NAME, LABEL, icon, PACKAGE)
dialog.setTileData(tileData, ugm)
dialog.show()
@@ -172,7 +169,7 @@ class TileRequestDialogTest : SysuiTestCase() {
@Test
fun setTileData_tileNotClickable() {
val icon = Icon.createWithResource(mContext, R.drawable.cloud)
- val tileData = TileRequestDialog.TileData(UID, APP_NAME, LABEL, icon, PACKAGE)
+ val tileData = TileData(UID, APP_NAME, LABEL, icon, PACKAGE)
dialog.setTileData(tileData, ugm)
dialog.show()
@@ -189,7 +186,7 @@ class TileRequestDialogTest : SysuiTestCase() {
@Test
fun setTileData_tileHasCorrectContentDescription() {
val icon = Icon.createWithResource(mContext, R.drawable.cloud)
- val tileData = TileRequestDialog.TileData(UID, APP_NAME, LABEL, icon, PACKAGE)
+ val tileData = TileData(UID, APP_NAME, LABEL, icon, PACKAGE)
dialog.setTileData(tileData, ugm)
dialog.show()
@@ -206,20 +203,14 @@ class TileRequestDialogTest : SysuiTestCase() {
fun uriIconLoadSuccess_correctIcon() {
val tintColor = Color.BLACK
val icon = Mockito.mock(Icon::class.java)
- val drawable = context.getDrawable(R.drawable.cloud)!!.apply {
- setTint(tintColor)
- }
+ val drawable = context.getDrawable(R.drawable.cloud)!!.apply { setTint(tintColor) }
whenever(icon.loadDrawable(any())).thenReturn(drawable)
- whenever(icon.loadDrawableCheckingUriGrant(
- any(),
- eq(ugm),
- anyInt(),
- anyString())
- ).thenReturn(drawable)
+ whenever(icon.loadDrawableCheckingUriGrant(any(), eq(ugm), anyInt(), anyString()))
+ .thenReturn(drawable)
val size = 100
- val tileData = TileRequestDialog.TileData(UID, APP_NAME, LABEL, icon, PACKAGE)
+ val tileData = TileData(UID, APP_NAME, LABEL, icon, PACKAGE)
dialog.setTileData(tileData, ugm)
dialog.show()
@@ -231,9 +222,7 @@ class TileRequestDialogTest : SysuiTestCase() {
val content = dialog.requireViewById<ViewGroup>(TileRequestDialog.CONTENT_ID)
val tile = content.getChildAt(1) as QSTileView
- val iconDrawable = (tile.icon.iconView as ImageView).drawable.apply {
- setTint(tintColor)
- }
+ val iconDrawable = (tile.icon.iconView as ImageView).drawable.apply { setTint(tintColor) }
assertThat(areDrawablesEqual(iconDrawable, drawable, size)).isTrue()
}
@@ -242,20 +231,14 @@ class TileRequestDialogTest : SysuiTestCase() {
fun uriIconLoadFail_defaultIcon() {
val tintColor = Color.BLACK
val icon = Mockito.mock(Icon::class.java)
- val drawable = context.getDrawable(R.drawable.cloud)!!.apply {
- setTint(tintColor)
- }
+ val drawable = context.getDrawable(R.drawable.cloud)!!.apply { setTint(tintColor) }
whenever(icon.loadDrawable(any())).thenReturn(drawable)
- whenever(icon.loadDrawableCheckingUriGrant(
- any(),
- eq(ugm),
- anyInt(),
- anyString())
- ).thenReturn(null)
+ whenever(icon.loadDrawableCheckingUriGrant(any(), eq(ugm), anyInt(), anyString()))
+ .thenReturn(null)
val size = 100
- val tileData = TileRequestDialog.TileData(UID, APP_NAME, LABEL, icon, PACKAGE)
+ val tileData = TileData(UID, APP_NAME, LABEL, icon, PACKAGE)
dialog.setTileData(tileData, ugm)
dialog.show()
@@ -267,13 +250,9 @@ class TileRequestDialogTest : SysuiTestCase() {
val content = dialog.requireViewById<ViewGroup>(TileRequestDialog.CONTENT_ID)
val tile = content.getChildAt(1) as QSTileView
- val iconDrawable = (tile.icon.iconView as ImageView).drawable.apply {
- setTint(tintColor)
- }
+ val iconDrawable = (tile.icon.iconView as ImageView).drawable.apply { setTint(tintColor) }
- val defaultIcon = context.getDrawable(DEFAULT_ICON)!!.apply {
- setTint(tintColor)
- }
+ val defaultIcon = context.getDrawable(DEFAULT_ICON)!!.apply { setTint(tintColor) }
assertThat(areDrawablesEqual(iconDrawable, defaultIcon, size)).isTrue()
}
@@ -308,4 +287,3 @@ private fun equalBitmaps(a: Bitmap, b: Bitmap): Boolean {
b.getPixels(bPix, 0, w, 0, 0, w, h)
return Arrays.equals(aPix, bPix)
}
-
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceRequestControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceRequestControllerTestComposeOff.kt
index 89ec687ad123..82e247714794 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceRequestControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceRequestControllerTestComposeOff.kt
@@ -22,6 +22,7 @@ import android.content.ComponentName
import android.content.DialogInterface
import android.graphics.drawable.Icon
import android.os.RemoteException
+import android.platform.test.annotations.DisableFlags
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.internal.logging.InstanceId
@@ -29,8 +30,12 @@ import com.android.internal.statusbar.IAddTileResultCallback
import com.android.systemui.InstanceIdSequenceFake
import com.android.systemui.SysuiTestCase
import com.android.systemui.qs.QSHost
+import com.android.systemui.qs.external.ui.dialog.tileRequestDialogComposeDelegateFactory
+import com.android.systemui.qs.flags.QSComposeFragment
+import com.android.systemui.shade.shared.flag.DualShade
import com.android.systemui.statusbar.CommandQueue
import com.android.systemui.statusbar.commandline.CommandRegistry
+import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.capture
import com.android.systemui.util.mockito.eq
@@ -52,7 +57,8 @@ import org.mockito.MockitoAnnotations
@SmallTest
@RunWith(AndroidJUnit4::class)
-class TileServiceRequestControllerTest : SysuiTestCase() {
+@DisableFlags(value = [QSComposeFragment.FLAG_NAME, DualShade.FLAG_NAME])
+class TileServiceRequestControllerTestComposeOff : SysuiTestCase() {
companion object {
private val TEST_COMPONENT = ComponentName("test_pkg", "test_cls")
@@ -61,20 +67,15 @@ class TileServiceRequestControllerTest : SysuiTestCase() {
private const val TEST_UID = 12345
}
- @Mock
- private lateinit var tileRequestDialog: TileRequestDialog
- @Mock
- private lateinit var qsHost: QSHost
- @Mock
- private lateinit var commandRegistry: CommandRegistry
- @Mock
- private lateinit var commandQueue: CommandQueue
- @Mock
- private lateinit var logger: TileRequestDialogEventLogger
- @Mock
- private lateinit var icon: Icon
- @Mock
- private lateinit var ugm: IUriGrantsManager
+ private val kosmos = testKosmos()
+
+ @Mock private lateinit var tileRequestDialog: TileRequestDialog
+ @Mock private lateinit var qsHost: QSHost
+ @Mock private lateinit var commandRegistry: CommandRegistry
+ @Mock private lateinit var commandQueue: CommandQueue
+ @Mock private lateinit var logger: TileRequestDialogEventLogger
+ @Mock private lateinit var icon: Icon
+ @Mock private lateinit var ugm: IUriGrantsManager
private val instanceIdSequence = InstanceIdSequenceFake(1_000)
private lateinit var controller: TileServiceRequestController
@@ -88,15 +89,17 @@ class TileServiceRequestControllerTest : SysuiTestCase() {
// Tile not present by default
`when`(qsHost.indexOf(anyString())).thenReturn(-1)
- controller = TileServiceRequestController(
+ controller =
+ TileServiceRequestController(
qsHost,
commandQueue,
commandRegistry,
logger,
ugm,
- ) {
- tileRequestDialog
- }
+ kosmos.tileRequestDialogComposeDelegateFactory,
+ ) {
+ tileRequestDialog
+ }
controller.init()
}
@@ -104,24 +107,19 @@ class TileServiceRequestControllerTest : SysuiTestCase() {
@Test
fun requestTileAdd_dataIsPassedToDialog() {
controller.requestTileAdd(
- TEST_UID,
- TEST_COMPONENT,
- TEST_APP_NAME,
- TEST_LABEL,
- icon,
- Callback(),
+ TEST_UID,
+ TEST_COMPONENT,
+ TEST_APP_NAME,
+ TEST_LABEL,
+ icon,
+ Callback(),
)
- verify(tileRequestDialog).setTileData(
- TileRequestDialog.TileData(
- TEST_UID,
- TEST_APP_NAME,
- TEST_LABEL,
- icon,
- TEST_COMPONENT.packageName,
- ),
+ verify(tileRequestDialog)
+ .setTileData(
+ TileData(TEST_UID, TEST_APP_NAME, TEST_LABEL, icon, TEST_COMPONENT.packageName),
ugm,
- )
+ )
}
@Test
@@ -130,12 +128,12 @@ class TileServiceRequestControllerTest : SysuiTestCase() {
val callback = Callback()
controller.requestTileAdd(
- TEST_UID,
- TEST_COMPONENT,
- TEST_APP_NAME,
- TEST_LABEL,
- icon,
- callback,
+ TEST_UID,
+ TEST_COMPONENT,
+ TEST_APP_NAME,
+ TEST_LABEL,
+ icon,
+ callback,
)
assertThat(callback.lastAccepted).isEqualTo(TileServiceRequestController.TILE_ALREADY_ADDED)
@@ -156,12 +154,12 @@ class TileServiceRequestControllerTest : SysuiTestCase() {
@Test
fun showAllUsers_set() {
controller.requestTileAdd(
- TEST_UID,
- TEST_COMPONENT,
- TEST_APP_NAME,
- TEST_LABEL,
- icon,
- Callback(),
+ TEST_UID,
+ TEST_COMPONENT,
+ TEST_APP_NAME,
+ TEST_LABEL,
+ icon,
+ Callback(),
)
verify(tileRequestDialog).setShowForAllUsers(true)
}
@@ -169,12 +167,12 @@ class TileServiceRequestControllerTest : SysuiTestCase() {
@Test
fun cancelOnTouchOutside_set() {
controller.requestTileAdd(
- TEST_UID,
- TEST_COMPONENT,
- TEST_APP_NAME,
- TEST_LABEL,
- icon,
- Callback(),
+ TEST_UID,
+ TEST_COMPONENT,
+ TEST_APP_NAME,
+ TEST_LABEL,
+ icon,
+ Callback(),
)
verify(tileRequestDialog).setCanceledOnTouchOutside(true)
}
@@ -189,16 +187,16 @@ class TileServiceRequestControllerTest : SysuiTestCase() {
@Test
fun cancelListener_dismissResult() {
val cancelListenerCaptor =
- ArgumentCaptor.forClass(DialogInterface.OnCancelListener::class.java)
+ ArgumentCaptor.forClass(DialogInterface.OnCancelListener::class.java)
val callback = Callback()
controller.requestTileAdd(
- TEST_UID,
- TEST_COMPONENT,
- TEST_APP_NAME,
- TEST_LABEL,
- icon,
- callback,
+ TEST_UID,
+ TEST_COMPONENT,
+ TEST_APP_NAME,
+ TEST_LABEL,
+ icon,
+ callback,
)
verify(tileRequestDialog).setOnCancelListener(capture(cancelListenerCaptor))
@@ -210,7 +208,7 @@ class TileServiceRequestControllerTest : SysuiTestCase() {
@Test
fun dialogCancelled_logged() {
val cancelListenerCaptor =
- ArgumentCaptor.forClass(DialogInterface.OnCancelListener::class.java)
+ ArgumentCaptor.forClass(DialogInterface.OnCancelListener::class.java)
controller.requestTileAdd(TEST_UID, TEST_COMPONENT, TEST_APP_NAME, TEST_LABEL, icon) {}
val instanceId = InstanceId.fakeInstanceId(instanceIdSequence.lastInstanceId)
@@ -219,26 +217,27 @@ class TileServiceRequestControllerTest : SysuiTestCase() {
verify(logger).logDialogShown(TEST_COMPONENT.packageName, instanceId)
cancelListenerCaptor.value.onCancel(tileRequestDialog)
- verify(logger).logUserResponse(
+ verify(logger)
+ .logUserResponse(
StatusBarManager.TILE_ADD_REQUEST_RESULT_DIALOG_DISMISSED,
TEST_COMPONENT.packageName,
- instanceId
- )
+ instanceId,
+ )
}
@Test
fun positiveActionListener_tileAddedResult() {
val clickListenerCaptor =
- ArgumentCaptor.forClass(DialogInterface.OnClickListener::class.java)
+ ArgumentCaptor.forClass(DialogInterface.OnClickListener::class.java)
val callback = Callback()
controller.requestTileAdd(
- TEST_UID,
- TEST_COMPONENT,
- TEST_APP_NAME,
- TEST_LABEL,
- icon,
- callback,
+ TEST_UID,
+ TEST_COMPONENT,
+ TEST_APP_NAME,
+ TEST_LABEL,
+ icon,
+ callback,
)
verify(tileRequestDialog).setPositiveButton(anyInt(), capture(clickListenerCaptor))
@@ -251,7 +250,7 @@ class TileServiceRequestControllerTest : SysuiTestCase() {
@Test
fun tileAdded_logged() {
val clickListenerCaptor =
- ArgumentCaptor.forClass(DialogInterface.OnClickListener::class.java)
+ ArgumentCaptor.forClass(DialogInterface.OnClickListener::class.java)
controller.requestTileAdd(TEST_UID, TEST_COMPONENT, TEST_APP_NAME, TEST_LABEL, icon) {}
val instanceId = InstanceId.fakeInstanceId(instanceIdSequence.lastInstanceId)
@@ -260,26 +259,27 @@ class TileServiceRequestControllerTest : SysuiTestCase() {
verify(logger).logDialogShown(TEST_COMPONENT.packageName, instanceId)
clickListenerCaptor.value.onClick(tileRequestDialog, DialogInterface.BUTTON_POSITIVE)
- verify(logger).logUserResponse(
+ verify(logger)
+ .logUserResponse(
StatusBarManager.TILE_ADD_REQUEST_RESULT_TILE_ADDED,
TEST_COMPONENT.packageName,
- instanceId
- )
+ instanceId,
+ )
}
@Test
fun negativeActionListener_tileNotAddedResult() {
val clickListenerCaptor =
- ArgumentCaptor.forClass(DialogInterface.OnClickListener::class.java)
+ ArgumentCaptor.forClass(DialogInterface.OnClickListener::class.java)
val callback = Callback()
controller.requestTileAdd(
- TEST_UID,
- TEST_COMPONENT,
- TEST_APP_NAME,
- TEST_LABEL,
- icon,
- callback,
+ TEST_UID,
+ TEST_COMPONENT,
+ TEST_APP_NAME,
+ TEST_LABEL,
+ icon,
+ callback,
)
verify(tileRequestDialog).setNegativeButton(anyInt(), capture(clickListenerCaptor))
@@ -292,7 +292,7 @@ class TileServiceRequestControllerTest : SysuiTestCase() {
@Test
fun tileNotAdded_logged() {
val clickListenerCaptor =
- ArgumentCaptor.forClass(DialogInterface.OnClickListener::class.java)
+ ArgumentCaptor.forClass(DialogInterface.OnClickListener::class.java)
controller.requestTileAdd(TEST_UID, TEST_COMPONENT, TEST_APP_NAME, TEST_LABEL, icon) {}
val instanceId = InstanceId.fakeInstanceId(instanceIdSequence.lastInstanceId)
@@ -301,11 +301,12 @@ class TileServiceRequestControllerTest : SysuiTestCase() {
verify(logger).logDialogShown(TEST_COMPONENT.packageName, instanceId)
clickListenerCaptor.value.onClick(tileRequestDialog, DialogInterface.BUTTON_NEGATIVE)
- verify(logger).logUserResponse(
+ verify(logger)
+ .logUserResponse(
StatusBarManager.TILE_ADD_REQUEST_RESULT_TILE_NOT_ADDED,
TEST_COMPONENT.packageName,
- instanceId
- )
+ instanceId,
+ )
}
@Test
@@ -319,24 +320,19 @@ class TileServiceRequestControllerTest : SysuiTestCase() {
verify(commandQueue, atLeastOnce()).addCallback(capture(captor))
captor.value.requestAddTile(
- TEST_UID,
- TEST_COMPONENT,
- TEST_APP_NAME,
- TEST_LABEL,
- icon,
- Callback(),
+ TEST_UID,
+ TEST_COMPONENT,
+ TEST_APP_NAME,
+ TEST_LABEL,
+ icon,
+ Callback(),
)
- verify(tileRequestDialog).setTileData(
- TileRequestDialog.TileData(
- TEST_UID,
- TEST_APP_NAME,
- TEST_LABEL,
- icon,
- TEST_COMPONENT.packageName,
- ),
+ verify(tileRequestDialog)
+ .setTileData(
+ TileData(TEST_UID, TEST_APP_NAME, TEST_LABEL, icon, TEST_COMPONENT.packageName),
ugm,
- )
+ )
}
@Test
@@ -354,22 +350,23 @@ class TileServiceRequestControllerTest : SysuiTestCase() {
@Test
fun interfaceThrowsRemoteException_doesntCrash() {
val cancelListenerCaptor =
- ArgumentCaptor.forClass(DialogInterface.OnCancelListener::class.java)
+ ArgumentCaptor.forClass(DialogInterface.OnCancelListener::class.java)
val captor = ArgumentCaptor.forClass(CommandQueue.Callbacks::class.java)
verify(commandQueue, atLeastOnce()).addCallback(capture(captor))
- val callback = object : IAddTileResultCallback.Stub() {
- override fun onTileRequest(p0: Int) {
- throw RemoteException()
+ val callback =
+ object : IAddTileResultCallback.Stub() {
+ override fun onTileRequest(p0: Int) {
+ throw RemoteException()
+ }
}
- }
captor.value.requestAddTile(
- TEST_UID,
- TEST_COMPONENT,
- TEST_APP_NAME,
- TEST_LABEL,
- icon,
- callback,
+ TEST_UID,
+ TEST_COMPONENT,
+ TEST_APP_NAME,
+ TEST_LABEL,
+ icon,
+ callback,
)
verify(tileRequestDialog).setOnCancelListener(capture(cancelListenerCaptor))
@@ -383,12 +380,12 @@ class TileServiceRequestControllerTest : SysuiTestCase() {
val callback = Callback()
controller.requestTileAdd(
- TEST_UID,
- TEST_COMPONENT,
- TEST_APP_NAME,
- TEST_LABEL,
- icon,
- callback,
+ TEST_UID,
+ TEST_COMPONENT,
+ TEST_APP_NAME,
+ TEST_LABEL,
+ icon,
+ callback,
)
verify(tileRequestDialog).setOnDismissListener(capture(dismissListenerCaptor))
@@ -407,12 +404,12 @@ class TileServiceRequestControllerTest : SysuiTestCase() {
val callback = Callback()
controller.requestTileAdd(
- TEST_UID,
- TEST_COMPONENT,
- TEST_APP_NAME,
- TEST_LABEL,
- icon,
- callback,
+ TEST_UID,
+ TEST_COMPONENT,
+ TEST_APP_NAME,
+ TEST_LABEL,
+ icon,
+ callback,
)
verify(tileRequestDialog).setPositiveButton(anyInt(), capture(clickListenerCaptor))
verify(tileRequestDialog).setOnDismissListener(capture(dismissListenerCaptor))
@@ -435,12 +432,12 @@ class TileServiceRequestControllerTest : SysuiTestCase() {
val callback = Callback()
controller.requestTileAdd(
- TEST_UID,
- TEST_COMPONENT,
- TEST_APP_NAME,
- TEST_LABEL,
- icon,
- callback,
+ TEST_UID,
+ TEST_COMPONENT,
+ TEST_APP_NAME,
+ TEST_LABEL,
+ icon,
+ callback,
)
verify(tileRequestDialog).setOnCancelListener(capture(cancelListenerCaptor))
verify(tileRequestDialog).setOnDismissListener(capture(dismissListenerCaptor))
diff --git a/packages/SystemUI/tests/utils/src/com/android/app/IUriGrantsManagerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/app/IUriGrantsManagerKosmos.kt
new file mode 100644
index 000000000000..003777aca687
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/app/IUriGrantsManagerKosmos.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2024 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.app
+
+import android.app.IUriGrantsManager
+import com.android.systemui.kosmos.Kosmos
+import org.mockito.kotlin.mock
+
+val Kosmos.iUriGrantsManager by Kosmos.Fixture { mock<IUriGrantsManager>() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/QSHostAdapterKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/QSHostAdapterKosmos.kt
new file mode 100644
index 000000000000..0bf801b35ad1
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/QSHostAdapterKosmos.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2024 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
+
+import android.content.applicationContext
+import com.android.systemui.dump.dumpManager
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.qs.external.tileServiceRequestControllerBuilder
+import com.android.systemui.qs.pipeline.domain.interactor.currentTilesInteractor
+
+val Kosmos.qsHostAdapter by
+ Kosmos.Fixture {
+ QSHostAdapter(
+ currentTilesInteractor,
+ applicationContext,
+ tileServiceRequestControllerBuilder,
+ applicationCoroutineScope,
+ dumpManager,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/TileServiceRequestControllerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/TileServiceRequestControllerKosmos.kt
new file mode 100644
index 000000000000..296623462a54
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/TileServiceRequestControllerKosmos.kt
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2024 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 com.android.app.iUriGrantsManager
+import com.android.internal.logging.uiEventLoggerFake
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.qs.external.ui.dialog.tileRequestDialogComposeDelegateFactory
+import com.android.systemui.qs.instanceIdSequenceFake
+import com.android.systemui.qs.qsHostAdapter
+import com.android.systemui.statusbar.commandQueue
+import com.android.systemui.statusbar.commandline.commandRegistry
+import org.mockito.kotlin.mock
+
+val Kosmos.tileServiceRequestControllerBuilder by
+ Kosmos.Fixture {
+ TileServiceRequestController.Builder(
+ commandQueue,
+ commandRegistry,
+ iUriGrantsManager,
+ tileRequestDialogComposeDelegateFactory,
+ )
+ }
+
+val Kosmos.tileServiceRequestController by
+ Kosmos.Fixture {
+ TileServiceRequestController(
+ qsHostAdapter,
+ commandQueue,
+ commandRegistry,
+ TileRequestDialogEventLogger(uiEventLoggerFake, instanceIdSequenceFake),
+ iUriGrantsManager,
+ tileRequestDialogComposeDelegateFactory,
+ { mock() },
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/ui/dialog/FakeTileRequestDialogComposeDelegateFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/ui/dialog/FakeTileRequestDialogComposeDelegateFactory.kt
new file mode 100644
index 000000000000..1e0ebe44ba2a
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/ui/dialog/FakeTileRequestDialogComposeDelegateFactory.kt
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2024 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.ui.dialog
+
+import android.content.DialogInterface
+import com.android.systemui.qs.external.TileData
+import org.mockito.Answers
+import org.mockito.kotlin.mock
+
+class FakeTileRequestDialogComposeDelegateFactory : TileRequestDialogComposeDelegate.Factory {
+ lateinit var tileData: TileData
+ lateinit var clickListener: DialogInterface.OnClickListener
+
+ override fun create(
+ tileData: TileData,
+ dialogListener: DialogInterface.OnClickListener,
+ ): TileRequestDialogComposeDelegate {
+ this.tileData = tileData
+ this.clickListener = dialogListener
+ return mock(defaultAnswer = Answers.RETURNS_MOCKS)
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/ui/dialog/TileRequestDialogComposeDelegateKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/ui/dialog/TileRequestDialogComposeDelegateKosmos.kt
new file mode 100644
index 000000000000..030af61e5569
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/ui/dialog/TileRequestDialogComposeDelegateKosmos.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2024 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.ui.dialog
+
+import android.content.DialogInterface
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.qs.external.TileData
+import com.android.systemui.qs.external.ui.viewmodel.tileRequestDialogViewModelFactory
+import com.android.systemui.statusbar.phone.systemUIDialogFactory
+
+var Kosmos.tileRequestDialogComposeDelegateFactory by
+ Kosmos.Fixture<TileRequestDialogComposeDelegate.Factory> {
+ object : TileRequestDialogComposeDelegate.Factory {
+ override fun create(
+ tiledata: TileData,
+ dialogListener: DialogInterface.OnClickListener,
+ ): TileRequestDialogComposeDelegate {
+ return TileRequestDialogComposeDelegate(
+ systemUIDialogFactory,
+ tileRequestDialogViewModelFactory,
+ tiledata,
+ dialogListener,
+ )
+ }
+ }
+ }
+
+val TileRequestDialogComposeDelegate.Factory.fake: FakeTileRequestDialogComposeDelegateFactory
+ get() = this as FakeTileRequestDialogComposeDelegateFactory
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/ui/viewmodel/TileRequestDialogViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/ui/viewmodel/TileRequestDialogViewModelKosmos.kt
new file mode 100644
index 000000000000..7b1797db24f7
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/ui/viewmodel/TileRequestDialogViewModelKosmos.kt
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2024 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.ui.viewmodel
+
+import android.content.Context
+import com.android.app.iUriGrantsManager
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.qs.external.TileData
+
+val Kosmos.tileRequestDialogViewModelFactory by
+ Kosmos.Fixture {
+ object : TileRequestDialogViewModel.Factory {
+ override fun create(
+ dialogContext: Context,
+ tileData: TileData,
+ ): TileRequestDialogViewModel {
+ return TileRequestDialogViewModel(
+ iUriGrantsManager,
+ testDispatcher,
+ dialogContext,
+ tileData,
+ )
+ }
+ }
+ }