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

Commit a540811d authored by Stefan Andonian's avatar Stefan Andonian Committed by Android (Google) Code Review
Browse files

Merge "Migrate Record Issue Tile to the new QS Tile architecture" into main

parents 4568adf5 f132185a
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