summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserInputHandlerTest.kt9
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/qr/domain/interactor/QRCodeScannerTileDataInteractorTest.kt112
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/qr/domain/interactor/QRCodeScannerTileUserActionInteractorTest.kt81
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/qr/ui/QRCodeScannerTileMapperTest.kt114
-rw-r--r--packages/SystemUI/src/com/android/systemui/qrcodescanner/dagger/QRCodeScannerModule.kt27
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserInputHandler.kt25
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/impl/qr/domain/interactor/QRCodeScannerTileDataInteractor.kt85
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/impl/qr/domain/interactor/QRCodeScannerTileUserActionInteractor.kt50
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/impl/qr/domain/model/QRCodeScannerTileModel.kt25
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/impl/qr/ui/QRCodeScannerTileMapper.kt59
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/qrcodescanner/QRCodeScannerControllerKosmos.kt23
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/actions/FakeQSTileIntentUserInputHandler.kt6
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserInputHandlerKosmos.kt21
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/analytics/QSTileAnalyticsKosmos.kt22
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/DisabledByPolicyInteractorKosmos.kt21
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/qr/QRCodeScannerTileKosmos.kt74
16 files changed, 749 insertions, 5 deletions
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserInputHandlerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserInputHandlerTest.kt
index bf48784407b8..02a81419ea78 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserInputHandlerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserInputHandlerTest.kt
@@ -69,6 +69,15 @@ class QSTileIntentUserInputHandlerTest : SysuiTestCase() {
}
@Test
+ fun testPassesIntentToStarter_dismissShadeAndShowOverLockScreenWhenLocked() {
+ val intent = Intent("test.ACTION")
+
+ underTest.handle(null, intent, true)
+
+ verify(activityStarter).startActivity(eq(intent), eq(true), any(), eq(true))
+ }
+
+ @Test
fun testPassesActivityPendingIntentToStarterAsPendingIntent() {
val pendingIntent = mock<PendingIntent> { whenever(isActivity).thenReturn(true) }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/qr/domain/interactor/QRCodeScannerTileDataInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/qr/domain/interactor/QRCodeScannerTileDataInteractorTest.kt
new file mode 100644
index 000000000000..c41ce2f7854c
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/qr/domain/interactor/QRCodeScannerTileDataInteractorTest.kt
@@ -0,0 +1,112 @@
+/*
+ * 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.tiles.impl.qr.domain.interactor
+
+import android.content.Intent
+import android.os.UserHandle
+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.qrcodescanner.controller.QRCodeScannerController
+import com.android.systemui.qrcodescanner.controller.QRCodeScannerController.Callback
+import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger
+import com.android.systemui.qs.tiles.impl.qr.domain.model.QRCodeScannerTileModel
+import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.verify
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class QRCodeScannerTileDataInteractorTest : SysuiTestCase() {
+
+ private val testUser = UserHandle.of(1)!!
+ private val testDispatcher = StandardTestDispatcher()
+ private val scope = TestScope(testDispatcher)
+ private val testIntent = mock<Intent>()
+ private val qrCodeScannerController =
+ mock<QRCodeScannerController> {
+ whenever(intent).thenReturn(testIntent)
+ whenever(isAbleToLaunchScannerActivity).thenReturn(false)
+ }
+ private val testAvailableModel = QRCodeScannerTileModel.Available(testIntent)
+ private val testUnavailableModel = QRCodeScannerTileModel.TemporarilyUnavailable
+
+ private val underTest: QRCodeScannerTileDataInteractor =
+ QRCodeScannerTileDataInteractor(
+ testDispatcher,
+ scope.backgroundScope,
+ qrCodeScannerController,
+ )
+
+ @Test
+ fun availability_matchesController_cameraNotAvailable() =
+ scope.runTest {
+ val expectedAvailability = false
+ whenever(qrCodeScannerController.isCameraAvailable).thenReturn(false)
+
+ val availability by collectLastValue(underTest.availability(testUser))
+
+ assertThat(availability).isEqualTo(expectedAvailability)
+ }
+
+ @Test
+ fun availability_matchesController_cameraIsAvailable() =
+ scope.runTest {
+ val expectedAvailability = true
+ whenever(qrCodeScannerController.isCameraAvailable).thenReturn(true)
+
+ val availability by collectLastValue(underTest.availability(testUser))
+
+ assertThat(availability).isEqualTo(expectedAvailability)
+ }
+
+ @Test
+ fun data_matchesController() =
+ scope.runTest {
+ val captor = argumentCaptor<Callback>()
+ val lastData by
+ collectLastValue(
+ underTest.tileData(testUser, flowOf(DataUpdateTrigger.InitialRequest))
+ )
+ runCurrent()
+
+ verify(qrCodeScannerController).addCallback(captor.capture())
+ val callback = captor.value
+
+ assertThat(lastData!!).isEqualTo(testUnavailableModel)
+
+ whenever(qrCodeScannerController.isAbleToLaunchScannerActivity).thenReturn(true)
+ callback.onQRCodeScannerActivityChanged()
+ runCurrent()
+ assertThat(lastData!!).isEqualTo(testAvailableModel)
+
+ whenever(qrCodeScannerController.isAbleToLaunchScannerActivity).thenReturn(false)
+ callback.onQRCodeScannerActivityChanged()
+ runCurrent()
+ assertThat(lastData!!).isEqualTo(testUnavailableModel)
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/qr/domain/interactor/QRCodeScannerTileUserActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/qr/domain/interactor/QRCodeScannerTileUserActionInteractorTest.kt
new file mode 100644
index 000000000000..312f18029570
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/qr/domain/interactor/QRCodeScannerTileUserActionInteractorTest.kt
@@ -0,0 +1,81 @@
+/*
+ * 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.tiles.impl.qr.domain.interactor
+
+import android.content.Intent
+import android.platform.test.annotations.EnabledOnRavenwood
+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.qs.tiles.base.actions.QSTileIntentUserInputHandlerSubject
+import com.android.systemui.qs.tiles.base.actions.qsTileIntentUserInputHandler
+import com.android.systemui.qs.tiles.base.interactor.QSTileInputTestKtx
+import com.android.systemui.qs.tiles.impl.qr.domain.model.QRCodeScannerTileModel
+import com.android.systemui.qs.tiles.impl.qr.qrCodeScannerTileUserActionInteractor
+import com.android.systemui.util.mockito.mock
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@EnabledOnRavenwood
+@RunWith(AndroidJUnit4::class)
+class QRCodeScannerTileUserActionInteractorTest : SysuiTestCase() {
+ val kosmos = Kosmos()
+ private val inputHandler = kosmos.qsTileIntentUserInputHandler
+ private val underTest = kosmos.qrCodeScannerTileUserActionInteractor
+ private val intent = mock<Intent>()
+
+ @Test
+ fun handleClick_available() = runTest {
+ val inputModel = QRCodeScannerTileModel.Available(intent)
+
+ underTest.handleInput(QSTileInputTestKtx.click(inputModel))
+
+ QSTileIntentUserInputHandlerSubject.assertThat(inputHandler).handledOneIntentInput {
+ intent
+ }
+ }
+
+ @Test
+ fun handleClick_temporarilyUnavailable() = runTest {
+ val inputModel = QRCodeScannerTileModel.TemporarilyUnavailable
+
+ underTest.handleInput(QSTileInputTestKtx.click(inputModel))
+
+ QSTileIntentUserInputHandlerSubject.assertThat(inputHandler).handledNoInputs()
+ }
+
+ @Test
+ fun handleLongClick_available() = runTest {
+ val inputModel = QRCodeScannerTileModel.Available(intent)
+
+ underTest.handleInput(QSTileInputTestKtx.longClick(inputModel))
+
+ QSTileIntentUserInputHandlerSubject.assertThat(inputHandler).handledNoInputs()
+ }
+
+ @Test
+ fun handleLongClick_temporarilyUnavailable() = runTest {
+ val inputModel = QRCodeScannerTileModel.TemporarilyUnavailable
+
+ underTest.handleInput(QSTileInputTestKtx.longClick(inputModel))
+
+ QSTileIntentUserInputHandlerSubject.assertThat(inputHandler).handledNoInputs()
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/qr/ui/QRCodeScannerTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/qr/ui/QRCodeScannerTileMapperTest.kt
new file mode 100644
index 000000000000..d26a21365f54
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/qr/ui/QRCodeScannerTileMapperTest.kt
@@ -0,0 +1,114 @@
+/*
+ * 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.tiles.impl.qr.ui
+
+import android.content.Intent
+import android.graphics.drawable.TestStubDrawable
+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.common.shared.model.Icon
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.qs.tiles.impl.custom.QSTileStateSubject
+import com.android.systemui.qs.tiles.impl.qr.domain.model.QRCodeScannerTileModel
+import com.android.systemui.qs.tiles.impl.qr.qsQRCodeScannerTileConfig
+import com.android.systemui.qs.tiles.viewmodel.QSTileState
+import com.android.systemui.util.mockito.mock
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class QRCodeScannerTileMapperTest : SysuiTestCase() {
+ private val kosmos = Kosmos()
+ private val config = kosmos.qsQRCodeScannerTileConfig
+
+ private lateinit var mapper: QRCodeScannerTileMapper
+
+ @Before
+ fun setup() {
+ mapper =
+ QRCodeScannerTileMapper(
+ context.orCreateTestableResources
+ .apply {
+ addOverride(
+ com.android.systemui.res.R.drawable.ic_qr_code_scanner,
+ TestStubDrawable()
+ )
+ }
+ .resources,
+ context.theme
+ )
+ }
+
+ @Test
+ fun availableModel() {
+ val mockIntent = mock<Intent>()
+ val inputModel = QRCodeScannerTileModel.Available(mockIntent)
+
+ val outputState = mapper.map(config, inputModel)
+
+ val expectedState =
+ createQRCodeScannerTileState(
+ QSTileState.ActivationState.INACTIVE,
+ null,
+ )
+ QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+ }
+
+ @Test
+ fun temporarilyUnavailableModel() {
+ val inputModel = QRCodeScannerTileModel.TemporarilyUnavailable
+
+ val outputState = mapper.map(config, inputModel)
+
+ val expectedState =
+ createQRCodeScannerTileState(
+ QSTileState.ActivationState.UNAVAILABLE,
+ context.getString(
+ com.android.systemui.res.R.string.qr_code_scanner_updating_secondary_label
+ )
+ )
+ QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+ }
+
+ private fun createQRCodeScannerTileState(
+ activationState: QSTileState.ActivationState,
+ secondaryLabel: String?,
+ ): QSTileState {
+ val label = context.getString(com.android.systemui.res.R.string.qr_code_scanner_title)
+ return QSTileState(
+ {
+ Icon.Loaded(
+ context.getDrawable(com.android.systemui.res.R.drawable.ic_qr_code_scanner)!!,
+ null
+ )
+ },
+ label,
+ activationState,
+ secondaryLabel,
+ setOf(QSTileState.UserAction.CLICK),
+ label,
+ null,
+ QSTileState.SideViewIcon.Chevron,
+ QSTileState.EnabledState.ENABLED,
+ Switch::class.qualifiedName
+ )
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qrcodescanner/dagger/QRCodeScannerModule.kt b/packages/SystemUI/src/com/android/systemui/qrcodescanner/dagger/QRCodeScannerModule.kt
index 3907a7240258..5e6ee4d3c700 100644
--- a/packages/SystemUI/src/com/android/systemui/qrcodescanner/dagger/QRCodeScannerModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/qrcodescanner/dagger/QRCodeScannerModule.kt
@@ -16,12 +16,20 @@
package com.android.systemui.qrcodescanner.dagger
+import com.android.systemui.Flags
import com.android.systemui.qs.QsEventLogger
import com.android.systemui.qs.pipeline.shared.TileSpec
import com.android.systemui.qs.tileimpl.QSTileImpl
import com.android.systemui.qs.tiles.QRCodeScannerTile
+import com.android.systemui.qs.tiles.base.viewmodel.QSTileViewModelFactory
+import com.android.systemui.qs.tiles.impl.qr.domain.interactor.QRCodeScannerTileDataInteractor
+import com.android.systemui.qs.tiles.impl.qr.domain.interactor.QRCodeScannerTileUserActionInteractor
+import com.android.systemui.qs.tiles.impl.qr.domain.model.QRCodeScannerTileModel
+import com.android.systemui.qs.tiles.impl.qr.ui.QRCodeScannerTileMapper
import com.android.systemui.qs.tiles.viewmodel.QSTileConfig
import com.android.systemui.qs.tiles.viewmodel.QSTileUIConfig
+import com.android.systemui.qs.tiles.viewmodel.QSTileViewModel
+import com.android.systemui.qs.tiles.viewmodel.StubQSTileViewModel
import com.android.systemui.res.R
import dagger.Binds
import dagger.Module
@@ -54,5 +62,24 @@ interface QRCodeScannerModule {
),
instanceId = uiEventLogger.getNewInstanceId(),
)
+
+ /** Inject QR Code Scanner Tile into tileViewModelMap in QSModule. */
+ @Provides
+ @IntoMap
+ @StringKey(QR_CODE_SCANNER_TILE_SPEC)
+ fun provideQRCodeScannerTileViewModel(
+ factory: QSTileViewModelFactory.Static<QRCodeScannerTileModel>,
+ mapper: QRCodeScannerTileMapper,
+ stateInteractor: QRCodeScannerTileDataInteractor,
+ userActionInteractor: QRCodeScannerTileUserActionInteractor
+ ): QSTileViewModel =
+ if (Flags.qsNewTilesFuture())
+ factory.create(
+ TileSpec.create(QR_CODE_SCANNER_TILE_SPEC),
+ userActionInteractor,
+ stateInteractor,
+ mapper,
+ )
+ else StubQSTileViewModel
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserInputHandler.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserInputHandler.kt
index 2d3120a1dcce..972b20e138d9 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserInputHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserInputHandler.kt
@@ -29,11 +29,15 @@ import javax.inject.Inject
/**
* Provides a shortcut to start an activity from [QSTileUserActionInteractor]. It supports keyguard
- * dismissing and tile from-view animations.
+ * dismissing and tile from-view animations, as well as the option to show over lockscreen.
*/
interface QSTileIntentUserInputHandler {
- fun handle(expandable: Expandable?, intent: Intent)
+ fun handle(
+ expandable: Expandable?,
+ intent: Intent,
+ dismissShadeShowOverLockScreenWhenLocked: Boolean = false
+ )
/** @param requestLaunchingDefaultActivity used in case !pendingIndent.isActivity */
fun handle(
@@ -52,12 +56,25 @@ constructor(
private val userHandle: UserHandle,
) : QSTileIntentUserInputHandler {
- override fun handle(expandable: Expandable?, intent: Intent) {
+ override fun handle(
+ expandable: Expandable?,
+ intent: Intent,
+ dismissShadeShowOverLockScreenWhenLocked: Boolean
+ ) {
val animationController: ActivityTransitionAnimator.Controller? =
expandable?.activityTransitionController(
InteractionJankMonitor.CUJ_SHADE_APP_LAUNCH_FROM_QS_TILE
)
- activityStarter.postStartActivityDismissingKeyguard(intent, 0, animationController)
+ if (dismissShadeShowOverLockScreenWhenLocked) {
+ activityStarter.startActivity(
+ intent,
+ true /* dismissShade */,
+ animationController,
+ true /* showOverLockscreenWhenLocked */
+ )
+ } else {
+ activityStarter.postStartActivityDismissingKeyguard(intent, 0, animationController)
+ }
}
// TODO(b/249804373): make sure to allow showing activities over the lockscreen. See b/292112939
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/qr/domain/interactor/QRCodeScannerTileDataInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/qr/domain/interactor/QRCodeScannerTileDataInteractor.kt
new file mode 100644
index 000000000000..1e8ce588b4e0
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/qr/domain/interactor/QRCodeScannerTileDataInteractor.kt
@@ -0,0 +1,85 @@
+/*
+ * 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.tiles.impl.qr.domain.interactor
+
+import android.os.UserHandle
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.qrcodescanner.controller.QRCodeScannerController
+import com.android.systemui.qrcodescanner.controller.QRCodeScannerController.DEFAULT_QR_CODE_SCANNER_CHANGE
+import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger
+import com.android.systemui.qs.tiles.base.interactor.QSTileDataInteractor
+import com.android.systemui.qs.tiles.impl.qr.domain.model.QRCodeScannerTileModel
+import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
+import javax.inject.Inject
+import kotlin.coroutines.CoroutineContext
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.flow.stateIn
+
+/** Observes one qr scanner state changes providing the [QRCodeScannerTileModel]. */
+class QRCodeScannerTileDataInteractor
+@Inject
+constructor(
+ @Background private val bgCoroutineContext: CoroutineContext,
+ @Application private val scope: CoroutineScope,
+ private val qrController: QRCodeScannerController,
+) : QSTileDataInteractor<QRCodeScannerTileModel> {
+ override fun tileData(
+ user: UserHandle,
+ triggers: Flow<DataUpdateTrigger>
+ ): Flow<QRCodeScannerTileModel> =
+ conflatedCallbackFlow {
+ qrController.registerQRCodeScannerChangeObservers(DEFAULT_QR_CODE_SCANNER_CHANGE)
+ val callback =
+ object : QRCodeScannerController.Callback {
+ override fun onQRCodeScannerActivityChanged() {
+ trySend(generateModel())
+ }
+ }
+ qrController.addCallback(callback)
+ awaitClose {
+ qrController.removeCallback(callback)
+ qrController.unregisterQRCodeScannerChangeObservers(
+ DEFAULT_QR_CODE_SCANNER_CHANGE
+ )
+ }
+ }
+ .onStart { emit(generateModel()) }
+ .flowOn(bgCoroutineContext)
+ .stateIn(
+ scope,
+ SharingStarted.WhileSubscribed(),
+ QRCodeScannerTileModel.TemporarilyUnavailable
+ )
+
+ override fun availability(user: UserHandle): Flow<Boolean> =
+ flowOf(qrController.isCameraAvailable)
+
+ private fun generateModel(): QRCodeScannerTileModel {
+ val intent = qrController.intent
+
+ return if (qrController.isAbleToLaunchScannerActivity && intent != null)
+ QRCodeScannerTileModel.Available(intent)
+ else QRCodeScannerTileModel.TemporarilyUnavailable
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/qr/domain/interactor/QRCodeScannerTileUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/qr/domain/interactor/QRCodeScannerTileUserActionInteractor.kt
new file mode 100644
index 000000000000..7c0c41eca4bc
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/qr/domain/interactor/QRCodeScannerTileUserActionInteractor.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.tiles.impl.qr.domain.interactor
+
+import com.android.systemui.qs.tiles.base.actions.QSTileIntentUserInputHandler
+import com.android.systemui.qs.tiles.base.interactor.QSTileInput
+import com.android.systemui.qs.tiles.base.interactor.QSTileUserActionInteractor
+import com.android.systemui.qs.tiles.impl.qr.domain.model.QRCodeScannerTileModel
+import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction
+import javax.inject.Inject
+
+/** Handles qr tile clicks. */
+class QRCodeScannerTileUserActionInteractor
+@Inject
+constructor(
+ private val qsTileIntentUserActionHandler: QSTileIntentUserInputHandler,
+) : QSTileUserActionInteractor<QRCodeScannerTileModel> {
+
+ override suspend fun handleInput(input: QSTileInput<QRCodeScannerTileModel>): Unit =
+ with(input) {
+ when (action) {
+ is QSTileUserAction.Click -> {
+ when (data) {
+ is QRCodeScannerTileModel.Available ->
+ qsTileIntentUserActionHandler.handle(
+ action.expandable,
+ data.intent,
+ true
+ )
+ is QRCodeScannerTileModel.TemporarilyUnavailable -> {} // no-op
+ }
+ }
+ is QSTileUserAction.LongClick -> {} // no-op
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/qr/domain/model/QRCodeScannerTileModel.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/qr/domain/model/QRCodeScannerTileModel.kt
new file mode 100644
index 000000000000..22c9b66b2806
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/qr/domain/model/QRCodeScannerTileModel.kt
@@ -0,0 +1,25 @@
+/*
+ * 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.tiles.impl.qr.domain.model
+
+import android.content.Intent
+
+/** qr scanner tile model. */
+sealed interface QRCodeScannerTileModel {
+ data class Available(val intent: Intent) : QRCodeScannerTileModel
+ data object TemporarilyUnavailable : QRCodeScannerTileModel
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/qr/ui/QRCodeScannerTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/qr/ui/QRCodeScannerTileMapper.kt
new file mode 100644
index 000000000000..45a77179fb3f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/qr/ui/QRCodeScannerTileMapper.kt
@@ -0,0 +1,59 @@
+/*
+ * 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.tiles.impl.qr.ui
+
+import android.content.res.Resources
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper
+import com.android.systemui.qs.tiles.impl.qr.domain.model.QRCodeScannerTileModel
+import com.android.systemui.qs.tiles.viewmodel.QSTileConfig
+import com.android.systemui.qs.tiles.viewmodel.QSTileState
+import com.android.systemui.res.R
+import javax.inject.Inject
+
+/** Maps [QRCodeScannerTileModel] to [QSTileState]. */
+class QRCodeScannerTileMapper
+@Inject
+constructor(
+ @Main private val resources: Resources,
+ private val theme: Resources.Theme,
+) : QSTileDataToStateMapper<QRCodeScannerTileModel> {
+
+ override fun map(config: QSTileConfig, data: QRCodeScannerTileModel): QSTileState =
+ QSTileState.build(resources, theme, config.uiConfig) {
+ label = resources.getString(R.string.qr_code_scanner_title)
+ contentDescription = label
+ icon = {
+ Icon.Loaded(resources.getDrawable(R.drawable.ic_qr_code_scanner, theme), null)
+ }
+ sideViewIcon = QSTileState.SideViewIcon.Chevron
+ supportedActions = setOf(QSTileState.UserAction.CLICK)
+
+ when (data) {
+ is QRCodeScannerTileModel.Available -> {
+ activationState = QSTileState.ActivationState.INACTIVE
+ secondaryLabel = null
+ }
+ is QRCodeScannerTileModel.TemporarilyUnavailable -> {
+ activationState = QSTileState.ActivationState.UNAVAILABLE
+ secondaryLabel =
+ resources.getString(R.string.qr_code_scanner_updating_secondary_label)
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qrcodescanner/QRCodeScannerControllerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qrcodescanner/QRCodeScannerControllerKosmos.kt
new file mode 100644
index 000000000000..8ad6087cfe82
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qrcodescanner/QRCodeScannerControllerKosmos.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.systemui.qrcodescanner
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.qrcodescanner.controller.QRCodeScannerController
+import com.android.systemui.util.mockito.mock
+
+val Kosmos.qrCodeScannerController by Kosmos.Fixture { mock<QRCodeScannerController>() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/actions/FakeQSTileIntentUserInputHandler.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/actions/FakeQSTileIntentUserInputHandler.kt
index c4bf8ff12817..f50443ec4e86 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/actions/FakeQSTileIntentUserInputHandler.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/actions/FakeQSTileIntentUserInputHandler.kt
@@ -31,7 +31,11 @@ class FakeQSTileIntentUserInputHandler : QSTileIntentUserInputHandler {
private val mutableInputs = mutableListOf<Input>()
- override fun handle(expandable: Expandable?, intent: Intent) {
+ override fun handle(
+ expandable: Expandable?,
+ intent: Intent,
+ handleDismissShadeShowOverLockScreenWhenLocked: Boolean
+ ) {
mutableInputs.add(Input.Intent(expandable, intent))
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserInputHandlerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserInputHandlerKosmos.kt
new file mode 100644
index 000000000000..ccfb6092a2e3
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserInputHandlerKosmos.kt
@@ -0,0 +1,21 @@
+/*
+ * 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.tiles.base.actions
+
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.qsTileIntentUserInputHandler by Kosmos.Fixture { FakeQSTileIntentUserInputHandler() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/analytics/QSTileAnalyticsKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/analytics/QSTileAnalyticsKosmos.kt
new file mode 100644
index 000000000000..146c1ad6ab70
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/analytics/QSTileAnalyticsKosmos.kt
@@ -0,0 +1,22 @@
+/*
+ * 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.tiles.base.analytics
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.util.mockito.mock
+
+val Kosmos.qsTileAnalytics by Kosmos.Fixture { mock<QSTileAnalytics>() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/DisabledByPolicyInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/DisabledByPolicyInteractorKosmos.kt
new file mode 100644
index 000000000000..9ad49f052c9e
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/DisabledByPolicyInteractorKosmos.kt
@@ -0,0 +1,21 @@
+/*
+ * 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.tiles.base.interactor
+
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.fakeDisabledByPolicyInteractor by Kosmos.Fixture { FakeDisabledByPolicyInteractor() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/qr/QRCodeScannerTileKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/qr/QRCodeScannerTileKosmos.kt
new file mode 100644
index 000000000000..dcfcce77942e
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/qr/QRCodeScannerTileKosmos.kt
@@ -0,0 +1,74 @@
+/*
+ * 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.tiles.impl.qr
+
+import android.content.res.mainResources
+import com.android.systemui.classifier.fakeFalsingManager
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.backgroundCoroutineContext
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.qrcodescanner.dagger.QRCodeScannerModule
+import com.android.systemui.qrcodescanner.qrCodeScannerController
+import com.android.systemui.qs.qsEventLogger
+import com.android.systemui.qs.tiles.base.actions.qsTileIntentUserInputHandler
+import com.android.systemui.qs.tiles.base.analytics.qsTileAnalytics
+import com.android.systemui.qs.tiles.base.interactor.fakeDisabledByPolicyInteractor
+import com.android.systemui.qs.tiles.base.viewmodel.QSTileViewModelImpl
+import com.android.systemui.qs.tiles.impl.custom.qsTileLogger
+import com.android.systemui.qs.tiles.impl.qr.domain.interactor.QRCodeScannerTileDataInteractor
+import com.android.systemui.qs.tiles.impl.qr.domain.interactor.QRCodeScannerTileUserActionInteractor
+import com.android.systemui.qs.tiles.impl.qr.ui.QRCodeScannerTileMapper
+import com.android.systemui.user.data.repository.fakeUserRepository
+import com.android.systemui.util.time.systemClock
+
+val Kosmos.qsQRCodeScannerTileConfig by
+ Kosmos.Fixture { QRCodeScannerModule.provideQRCodeScannerTileConfig(qsEventLogger) }
+
+val Kosmos.qrCodeScannerTileDataInteractor by
+ Kosmos.Fixture {
+ QRCodeScannerTileDataInteractor(
+ backgroundCoroutineContext,
+ applicationCoroutineScope,
+ qrCodeScannerController
+ )
+ }
+
+val Kosmos.qrCodeScannerTileUserActionInteractor by
+ Kosmos.Fixture { QRCodeScannerTileUserActionInteractor(qsTileIntentUserInputHandler) }
+
+val Kosmos.qrCodeScannerTileMapper by
+ Kosmos.Fixture { QRCodeScannerTileMapper(mainResources, mainResources.newTheme()) }
+
+val Kosmos.qsQRCodeScannerViewModel by
+ Kosmos.Fixture {
+ QSTileViewModelImpl(
+ qsQRCodeScannerTileConfig,
+ { qrCodeScannerTileUserActionInteractor },
+ { qrCodeScannerTileDataInteractor },
+ { qrCodeScannerTileMapper },
+ fakeDisabledByPolicyInteractor,
+ fakeUserRepository,
+ fakeFalsingManager,
+ qsTileAnalytics,
+ qsTileLogger,
+ systemClock,
+ testDispatcher,
+ testScope.backgroundScope,
+ )
+ }