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

Commit f132185a authored by Stefan Andonian's avatar Stefan Andonian
Browse files

Migrate Record Issue Tile to the new QS Tile architecture

Bug: 326408170
Flag: com.android.systemui.record_issue_qs_tile
Test: Tested this manually.
Change-Id: I4329db811184f902eca1117a2a65af1a3429765b
parent 2b683b78
Loading
Loading
Loading
Loading
+78 −0
Original line number Diff line number Diff line
/*
 * 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.irecording

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.kosmos.Kosmos
import com.android.systemui.kosmos.testCase
import com.android.systemui.kosmos.testScope
import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger
import com.android.systemui.recordissue.IssueRecordingState
import com.android.systemui.settings.fakeUserFileManager
import com.android.systemui.settings.userTracker
import com.google.common.truth.Truth
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith

@SmallTest
@RunWith(AndroidJUnit4::class)
class IssueRecordingDataInteractorTest : SysuiTestCase() {

    private val kosmos = Kosmos().also { it.testCase = this }
    private val userTracker = kosmos.userTracker
    private val userFileManager = kosmos.fakeUserFileManager
    private val testUser = UserHandle.of(1)

    lateinit var state: IssueRecordingState
    private lateinit var underTest: IssueRecordingDataInteractor

    @Before
    fun setup() {
        state = IssueRecordingState(userTracker, userFileManager)
        underTest = IssueRecordingDataInteractor(state, kosmos.testScope.testScheduler)
    }

    @OptIn(ExperimentalCoroutinesApi::class)
    @Test
    fun emitsEvent_whenIsRecordingStatusChanges_correctly() {
        kosmos.testScope.runTest {
            val data by
                collectLastValue(
                    underTest.tileData(testUser, flowOf(DataUpdateTrigger.InitialRequest))
                )
            runCurrent()
            Truth.assertThat(data?.isRecording).isFalse()

            state.isRecording = true
            runCurrent()
            Truth.assertThat(data?.isRecording).isTrue()

            state.isRecording = false
            runCurrent()
            Truth.assertThat(data?.isRecording).isFalse()
        }
    }
}
+64 −0
Original line number Diff line number Diff line
/*
 * 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.irecording

import android.content.res.mainResources
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.testCase
import com.android.systemui.qs.pipeline.shared.TileSpec
import com.android.systemui.qs.qsEventLogger
import com.android.systemui.qs.tiles.viewmodel.QSTileConfig
import com.android.systemui.qs.tiles.viewmodel.QSTileState
import com.android.systemui.qs.tiles.viewmodel.QSTileUIConfig
import com.android.systemui.recordissue.RecordIssueModule
import com.android.systemui.res.R
import com.google.common.truth.Truth
import org.junit.Test
import org.junit.runner.RunWith

@SmallTest
@RunWith(AndroidJUnit4::class)
class IssueRecordingMapperTest : SysuiTestCase() {
    private val kosmos = Kosmos().also { it.testCase = this }
    private val uiConfig =
        QSTileUIConfig.Resource(R.drawable.qs_record_issue_icon_off, R.string.qs_record_issue_label)
    private val config =
        QSTileConfig(
            TileSpec.create(RecordIssueModule.TILE_SPEC),
            uiConfig,
            kosmos.qsEventLogger.getNewInstanceId()
        )
    private val resources = kosmos.mainResources
    private val theme = resources.newTheme()

    @Test
    fun whenData_isRecording_useCorrectResources() {
        val underTest = IssueRecordingMapper(resources, theme)
        val tileState = underTest.map(config, IssueRecordingModel(true))
        Truth.assertThat(tileState.activationState).isEqualTo(QSTileState.ActivationState.ACTIVE)
    }

    @Test
    fun whenData_isNotRecording_useCorrectResources() {
        val underTest = IssueRecordingMapper(resources, theme)
        val tileState = underTest.map(config, IssueRecordingModel(false))
        Truth.assertThat(tileState.activationState).isEqualTo(QSTileState.ActivationState.INACTIVE)
    }
}
+116 −0
Original line number Diff line number Diff line
/*
 * 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.irecording

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.animation.dialogTransitionAnimator
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.testCase
import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.kosmos.testScope
import com.android.systemui.plugins.activityStarter
import com.android.systemui.plugins.statusbar.statusBarStateController
import com.android.systemui.qs.pipeline.domain.interactor.panelInteractor
import com.android.systemui.qs.tiles.base.interactor.QSTileInput
import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction
import com.android.systemui.recordissue.RecordIssueDialogDelegate
import com.android.systemui.settings.UserContextProvider
import com.android.systemui.settings.userTracker
import com.android.systemui.statusbar.phone.KeyguardDismissUtil
import com.android.systemui.statusbar.policy.keyguardStateController
import com.google.common.truth.Truth
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mockito.mock

@SmallTest
@RunWith(AndroidJUnit4::class)
class IssueRecordingUserActionInteractorTest : SysuiTestCase() {

    val user = UserHandle(1)
    val kosmos = Kosmos().also { it.testCase = this }

    private lateinit var userContextProvider: UserContextProvider
    private lateinit var underTest: IssueRecordingUserActionInteractor

    private var hasCreatedDialogDelegate: Boolean = false

    @Before
    fun setup() {
        hasCreatedDialogDelegate = false
        with(kosmos) {
            val factory =
                object : RecordIssueDialogDelegate.Factory {
                    override fun create(onStarted: Runnable): RecordIssueDialogDelegate {
                        hasCreatedDialogDelegate = true

                        // Inside some tests in presubmit, createDialog throws an error because
                        // the test thread's looper hasn't been prepared, and Dialog.class
                        // internally is creating a new handler. For testing, we only care that the
                        // dialog is created, so using a mock is acceptable here.
                        return mock(RecordIssueDialogDelegate::class.java)
                    }
                }

            userContextProvider = userTracker
            underTest =
                IssueRecordingUserActionInteractor(
                    testDispatcher,
                    KeyguardDismissUtil(
                        keyguardStateController,
                        statusBarStateController,
                        activityStarter
                    ),
                    keyguardStateController,
                    dialogTransitionAnimator,
                    panelInteractor,
                    userTracker,
                    factory
                )
        }
    }

    @Test
    fun handleInput_showsPromptToStartRecording_whenNotRecordingAlready() {
        kosmos.testScope.runTest {
            underTest.handleInput(
                QSTileInput(user, QSTileUserAction.Click(null), IssueRecordingModel(false))
            )
            Truth.assertThat(hasCreatedDialogDelegate).isTrue()
        }
    }

    @Test
    fun handleInput_attemptsToStopRecording_whenRecording() {
        kosmos.testScope.runTest {
            val input = QSTileInput(user, QSTileUserAction.Click(null), IssueRecordingModel(true))
            try {
                underTest.handleInput(input)
            } catch (e: NullPointerException) {
                // As of 06/07/2024, PendingIntent.startService is not easily mockable and throws
                // an NPE inside IActivityManager. Catching that here and ignore it, then verify
                // mock interactions were done correctly
            }
            Truth.assertThat(hasCreatedDialogDelegate).isFalse()
        }
    }
}
+1 −4
Original line number Diff line number Diff line
@@ -46,6 +46,7 @@ import com.android.systemui.qs.tileimpl.QSTileImpl
import com.android.systemui.recordissue.IssueRecordingService
import com.android.systemui.recordissue.IssueRecordingState
import com.android.systemui.recordissue.RecordIssueDialogDelegate
import com.android.systemui.recordissue.RecordIssueModule.Companion.TILE_SPEC
import com.android.systemui.recordissue.TraceurMessageSender
import com.android.systemui.res.R
import com.android.systemui.screenrecord.RecordingService
@@ -197,8 +198,4 @@ constructor(
            expandedAccessibilityClassName = Switch::class.java.name
        }
    }

    companion object {
        const val TILE_SPEC = "record_issue"
    }
}
+57 −0
Original line number Diff line number Diff line
/*
 * 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.irecording

import android.os.UserHandle
import com.android.systemui.Flags
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger
import com.android.systemui.qs.tiles.base.interactor.QSTileDataInteractor
import com.android.systemui.recordissue.IssueRecordingState
import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
import javax.inject.Inject
import kotlin.coroutines.CoroutineContext
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.onStart

class IssueRecordingDataInteractor
@Inject
constructor(
    private val state: IssueRecordingState,
    @Background private val bgCoroutineContext: CoroutineContext,
) : QSTileDataInteractor<IssueRecordingModel> {

    override fun tileData(
        user: UserHandle,
        triggers: Flow<DataUpdateTrigger>
    ): Flow<IssueRecordingModel> =
        conflatedCallbackFlow {
                val listener = Runnable { trySend(IssueRecordingModel(state.isRecording)) }
                state.addListener(listener)
                awaitClose { state.removeListener(listener) }
            }
            .onStart { emit(IssueRecordingModel(state.isRecording)) }
            .distinctUntilChanged()
            .flowOn(bgCoroutineContext)

    override fun availability(user: UserHandle): Flow<Boolean> =
        flowOf(android.os.Build.IS_DEBUGGABLE && Flags.recordIssueQsTile())
}
Loading