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

Commit 80edeb36 authored by Ioana Alexandru's avatar Ioana Alexandru Committed by Android (Google) Code Review
Browse files

Merge "Revert^2 "Open modes settings on tile long press"" into main

parents 31f579a1 2183d918
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -258,6 +258,7 @@ filegroup {
        "tests/src/**/systemui/statusbar/policy/LocationControllerImplTest.java",
        "tests/src/**/systemui/statusbar/policy/RemoteInputViewTest.java",
        "tests/src/**/systemui/statusbar/policy/SmartReplyViewTest.java",
        "tests/src/**/systemui/statusbar/policy/ui/dialog/ModesDialogDelegateTest.kt",
        "tests/src/**/systemui/statusbar/StatusBarStateControllerImplTest.kt",
        "tests/src/**/systemui/theme/ThemeOverlayApplierTest.java",
        "tests/src/**/systemui/touch/TouchInsetManagerTest.java",
+35 −31
Original line number Diff line number Diff line
@@ -21,64 +21,68 @@ import android.provider.Settings
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.qs.tiles.base.actions.FakeQSTileIntentUserInputHandler
import com.android.systemui.animation.Expandable
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.modes.domain.model.ModesTileModel
import com.android.systemui.statusbar.phone.SystemUIDialog
import com.android.systemui.statusbar.policy.ui.dialog.ModesDialogDelegate
import com.google.common.truth.Truth
import kotlin.coroutines.EmptyCoroutineContext
import com.android.systemui.statusbar.policy.ui.dialog.mockModesDialogDelegate
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.MockitoAnnotations
import org.mockito.kotlin.eq
import org.mockito.kotlin.mock
import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever

@SmallTest
@RunWith(AndroidJUnit4::class)
@EnableFlags(android.app.Flags.FLAG_MODES_UI)
class ModesTileUserActionInteractorTest : SysuiTestCase() {
    private val inputHandler = FakeQSTileIntentUserInputHandler()
    private val kosmos = testKosmos()
    private val inputHandler = kosmos.qsTileIntentUserInputHandler
    private val mockDialogDelegate = kosmos.mockModesDialogDelegate

    @Mock private lateinit var dialogTransitionAnimator: DialogTransitionAnimator
    @Mock private lateinit var dialogDelegate: ModesDialogDelegate
    @Mock private lateinit var mockDialog: SystemUIDialog
    private val underTest =
        ModesTileUserActionInteractor(
            inputHandler,
            mockDialogDelegate,
        )

    private lateinit var underTest: ModesTileUserActionInteractor
    @Test
    fun handleClick_active() = runTest {
        val expandable = mock<Expandable>()
        underTest.handleInput(
            QSTileInputTestKtx.click(data = ModesTileModel(true), expandable = expandable))

    @Before
    fun setup() {
        MockitoAnnotations.initMocks(this)
        verify(mockDialogDelegate).showDialog(eq(expandable))
    }

        whenever(dialogDelegate.createDialog()).thenReturn(mockDialog)
    @Test
    fun handleClick_inactive() = runTest {
        val expandable = mock<Expandable>()
        underTest.handleInput(
            QSTileInputTestKtx.click(data = ModesTileModel(false), expandable = expandable))

        underTest =
            ModesTileUserActionInteractor(
                EmptyCoroutineContext,
                inputHandler,
                dialogTransitionAnimator,
                dialogDelegate,
            )
        verify(mockDialogDelegate).showDialog(eq(expandable))
    }

    @Test
    fun handleClick() = runTest {
        underTest.handleInput(QSTileInputTestKtx.click(ModesTileModel(false)))
    fun handleLongClick_active() = runTest {
        underTest.handleInput(QSTileInputTestKtx.longClick(ModesTileModel(true)))

        verify(mockDialog).show()
        QSTileIntentUserInputHandlerSubject.assertThat(inputHandler).handledOneIntentInput {
            assertThat(it.intent.action).isEqualTo(Settings.ACTION_ZEN_MODE_SETTINGS)
        }
    }

    @Test
    fun handleLongClick() = runTest {
    fun handleLongClick_inactive() = runTest {
        underTest.handleInput(QSTileInputTestKtx.longClick(ModesTileModel(false)))

        QSTileIntentUserInputHandlerSubject.assertThat(inputHandler).handledOneIntentInput {
            Truth.assertThat(it.intent.action).isEqualTo(Settings.ACTION_ZEN_MODE_SETTINGS)
            assertThat(it.intent.action).isEqualTo(Settings.ACTION_ZEN_MODE_SETTINGS)
        }
    }
}
+67 −5
Original line number Diff line number Diff line
@@ -18,6 +18,8 @@

package com.android.systemui.statusbar.policy.ui.dialog.viewmodel

import android.content.Intent
import android.provider.Settings
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.settingslib.notification.modes.TestModeBuilder
@@ -27,6 +29,7 @@ import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.kosmos.testScope
import com.android.systemui.statusbar.policy.data.repository.fakeZenModeRepository
import com.android.systemui.statusbar.policy.domain.interactor.zenModeInteractor
import com.android.systemui.statusbar.policy.ui.dialog.mockModesDialogDelegate
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -34,16 +37,21 @@ import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mockito.clearInvocations
import org.mockito.kotlin.argumentCaptor
import org.mockito.kotlin.verify

@SmallTest
@RunWith(AndroidJUnit4::class)
class ModesDialogViewModelTest : SysuiTestCase() {
    private val kosmos = testKosmos()
    private val testScope = kosmos.testScope
    val repository = kosmos.fakeZenModeRepository
    val interactor = kosmos.zenModeInteractor
    private val repository = kosmos.fakeZenModeRepository
    private val interactor = kosmos.zenModeInteractor
    private val mockDialogDelegate = kosmos.mockModesDialogDelegate

    val underTest = ModesDialogViewModel(context, interactor, kosmos.testDispatcher)
    private val underTest =
        ModesDialogViewModel(context, interactor, kosmos.testDispatcher, mockDialogDelegate)

    @Test
    fun tiles_filtersOutDisabledModes() =
@@ -64,7 +72,8 @@ class ModesDialogViewModelTest : SysuiTestCase() {
                        .setEnabled(false)
                        .setManualInvocationAllowed(true)
                        .build(),
                ))
                )
            )
            runCurrent()

            assertThat(tiles?.size).isEqualTo(2)
@@ -108,7 +117,8 @@ class ModesDialogViewModelTest : SysuiTestCase() {
                        .setActive(false)
                        .setManualInvocationAllowed(false)
                        .build(),
                ))
                )
            )
            runCurrent()

            assertThat(tiles?.size).isEqualTo(3)
@@ -161,4 +171,56 @@ class ModesDialogViewModelTest : SysuiTestCase() {

            assertThat(tiles?.first()?.enabled).isFalse()
        }

    @Test
    fun onLongClick_launchesIntent() =
        testScope.runTest {
            val tiles by collectLastValue(underTest.tiles)
            val intentCaptor = argumentCaptor<Intent>()

            val modeId = "id"
            repository.addModes(
                listOf(
                    TestModeBuilder()
                        .setId(modeId)
                        .setId("A")
                        .setActive(true)
                        .setManualInvocationAllowed(true)
                        .build(),
                    TestModeBuilder()
                        .setId(modeId)
                        .setId("B")
                        .setActive(false)
                        .setManualInvocationAllowed(true)
                        .build(),
                )
            )
            runCurrent()

            assertThat(tiles?.size).isEqualTo(2)

            // Trigger onLongClick for A
            tiles?.first()?.onLongClick?.let { it() }
            runCurrent()

            // Check that it launched the correct intent
            verify(mockDialogDelegate).launchFromDialog(intentCaptor.capture())
            var intent = intentCaptor.lastValue
            assertThat(intent.action).isEqualTo(Settings.ACTION_AUTOMATIC_ZEN_RULE_SETTINGS)
            assertThat(intent.extras?.getString(Settings.EXTRA_AUTOMATIC_ZEN_RULE_ID))
                .isEqualTo("A")

            clearInvocations(mockDialogDelegate)

            // Trigger onLongClick for B
            tiles?.last()?.onLongClick?.let { it() }
            runCurrent()

            // Check that it launched the correct intent
            verify(mockDialogDelegate).launchFromDialog(intentCaptor.capture())
            intent = intentCaptor.lastValue
            assertThat(intent.action).isEqualTo(Settings.ACTION_AUTOMATIC_ZEN_RULE_SETTINGS)
            assertThat(intent.extras?.getString(Settings.EXTRA_AUTOMATIC_ZEN_RULE_ID))
                .isEqualTo("B")
        }
}
+7 −28
Original line number Diff line number Diff line
@@ -16,14 +16,10 @@

package com.android.systemui.qs.tiles.impl.modes.domain.interactor

//noinspection CleanArchitectureDependencyViolation: dialog needs to be opened on click
import android.content.Intent
import android.provider.Settings
import com.android.internal.jank.InteractionJankMonitor
import com.android.systemui.animation.DialogCuj
import com.android.systemui.animation.DialogTransitionAnimator
import com.android.systemui.animation.Expandable
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.dagger.SysUISingleton
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
@@ -31,15 +27,13 @@ import com.android.systemui.qs.tiles.impl.modes.domain.model.ModesTileModel
import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction
import com.android.systemui.statusbar.policy.ui.dialog.ModesDialogDelegate
import javax.inject.Inject
import kotlin.coroutines.CoroutineContext
import kotlinx.coroutines.withContext

@SysUISingleton
class ModesTileUserActionInteractor
@Inject
constructor(
    @Main private val coroutineContext: CoroutineContext,
    private val qsTileIntentUserActionHandler: QSTileIntentUserInputHandler,
    private val dialogTransitionAnimator: DialogTransitionAnimator,
    private val qsTileIntentUserInputHandler: QSTileIntentUserInputHandler,
    // TODO(b/353896370): The domain layer should not have to depend on the UI layer.
    private val dialogDelegate: ModesDialogDelegate,
) : QSTileUserActionInteractor<ModesTileModel> {
    val longClickIntent = Intent(Settings.ACTION_ZEN_MODE_SETTINGS)
@@ -51,29 +45,14 @@ constructor(
                    handleClick(action.expandable)
                }
                is QSTileUserAction.LongClick -> {
                    qsTileIntentUserActionHandler.handle(action.expandable, longClickIntent)
                    qsTileIntentUserInputHandler.handle(action.expandable, longClickIntent)
                }
            }
        }
    }

    suspend fun handleClick(expandable: Expandable?) {
        // Show a dialog with the list of modes to configure. Dialogs shown by the
        // DialogTransitionAnimator must be created and shown on the main thread, so we post it to
        // the UI handler.
        withContext(coroutineContext) {
            val dialog = dialogDelegate.createDialog()

            expandable
                ?.dialogTransitionController(
                    DialogCuj(InteractionJankMonitor.CUJ_SHADE_DIALOG_OPEN, INTERACTION_JANK_TAG)
                )
                ?.let { controller -> dialogTransitionAnimator.show(dialog, controller) }
                ?: dialog.show()
        }
    }

    companion object {
        private const val INTERACTION_JANK_TAG = "configure_priority_modes"
        // Show a dialog with the list of modes to configure.
        dialogDelegate.showDialog(expandable)
    }
}
+120 −32
Original line number Diff line number Diff line
@@ -18,45 +18,97 @@ package com.android.systemui.statusbar.policy.ui.dialog

import android.content.Intent
import android.provider.Settings
import android.util.Log
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.res.stringResource
import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.LifecycleOwner
import com.android.compose.PlatformButton
import com.android.compose.PlatformOutlinedButton
import com.android.internal.annotations.VisibleForTesting
import com.android.internal.jank.InteractionJankMonitor
import com.android.systemui.animation.DialogCuj
import com.android.systemui.animation.DialogTransitionAnimator
import com.android.systemui.animation.Expandable
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.dialog.ui.composable.AlertDialogContent
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.res.R
import com.android.systemui.statusbar.phone.ComponentSystemUIDialog
import com.android.systemui.statusbar.phone.SystemUIDialog
import com.android.systemui.statusbar.phone.SystemUIDialogFactory
import com.android.systemui.statusbar.phone.create
import com.android.systemui.statusbar.policy.ui.dialog.composable.ModeTileGrid
import com.android.systemui.statusbar.policy.ui.dialog.viewmodel.ModesDialogViewModel
import com.android.systemui.util.Assert
import javax.inject.Inject
import javax.inject.Provider
import kotlin.coroutines.CoroutineContext
import kotlinx.coroutines.withContext

@SysUISingleton
class ModesDialogDelegate
@Inject
constructor(
    private val sysuiDialogFactory: SystemUIDialogFactory,
    private val dialogTransitionAnimator: DialogTransitionAnimator,
    private val activityStarter: ActivityStarter,
    private val viewModel: ModesDialogViewModel,
    // Using a provider to avoid a circular dependency.
    private val viewModel: Provider<ModesDialogViewModel>,
    @Main private val mainCoroutineContext: CoroutineContext,
) : SystemUIDialog.Delegate {
    // NOTE: This should only be accessed/written from the main thread.
    @VisibleForTesting var currentDialog: ComponentSystemUIDialog? = null

    override fun createDialog(): SystemUIDialog {
        return sysuiDialogFactory.create { dialog ->
        Assert.isMainThread()
        if (currentDialog != null) {
            Log.w(TAG, "Dialog is already open, dismissing it and creating a new one.")
            currentDialog?.dismiss()
        }

        currentDialog = sysuiDialogFactory.create() { ModesDialogContent(it) }
        currentDialog
            ?.lifecycle
            ?.addObserver(
                object : DefaultLifecycleObserver {
                    override fun onStop(owner: LifecycleOwner) {
                        Assert.isMainThread()
                        currentDialog = null
                    }
                }
            )

        return currentDialog!!
    }

    @Composable
    private fun ModesDialogContent(dialog: SystemUIDialog) {
        AlertDialogContent(
            title = { Text(stringResource(R.string.zen_modes_dialog_title)) },
                content = { ModeTileGrid(viewModel) },
            content = { ModeTileGrid(viewModel.get()) },
            neutralButton = {
                    PlatformOutlinedButton(
                        onClick = {
                            val animationController =
                                dialogTransitionAnimator.createActivityTransitionController(
                                    dialog.getButton(SystemUIDialog.BUTTON_NEUTRAL)
                PlatformOutlinedButton(onClick = { openSettings(dialog) }) {
                    Text(stringResource(R.string.zen_modes_dialog_settings))
                }
            },
            positiveButton = {
                PlatformButton(onClick = { dialog.dismiss() }) {
                    Text(stringResource(R.string.zen_modes_dialog_done))
                }
            },
        )
    }

    private fun openSettings(dialog: SystemUIDialog) {
        val animationController =
            dialogTransitionAnimator.createActivityTransitionController(dialog)
        if (animationController == null) {
                                // The controller will take care of dismissing for us after the
                                // animation, but let's make sure we dismiss the dialog if we don't
                                // animate it.
            // The controller will take care of dismissing for us after
            // the animation, but let's make sure we dismiss the dialog
            // if we don't animate it.
            dialog.dismiss()
        }
        activityStarter.startActivity(
@@ -65,20 +117,56 @@ constructor(
            animationController
        )
    }
                    ) {
                        Text(stringResource(R.string.zen_modes_dialog_settings))

    suspend fun showDialog(expandable: Expandable? = null): SystemUIDialog {
        // Dialogs shown by the DialogTransitionAnimator must be created and shown on the main
        // thread, so we post it to the UI handler.
        withContext(mainCoroutineContext) {
            // Create the dialog if necessary
            if (currentDialog == null) {
                createDialog()
            }
                },
                positiveButton = {
                    PlatformButton(onClick = { dialog.dismiss() }) {
                        Text(stringResource(R.string.zen_modes_dialog_done))

            expandable
                ?.dialogTransitionController(
                    DialogCuj(InteractionJankMonitor.CUJ_SHADE_DIALOG_OPEN, INTERACTION_JANK_TAG)
                )
                ?.let { controller -> dialogTransitionAnimator.show(currentDialog!!, controller) }
                ?: currentDialog!!.show()
        }
                },

        return currentDialog!!
    }

    /**
     * Launches the [intent] by animating from the dialog. If the dialog is not showing, just
     * launches it normally without animating.
     */
    fun launchFromDialog(intent: Intent) {
        Assert.isMainThread()
        if (currentDialog == null) {
            Log.w(
                TAG,
                "Cannot launch from dialog, the dialog is not present. " +
                    "Will launch activity without animating."
            )
        }

        val animationController =
            currentDialog?.let { dialogTransitionAnimator.createActivityTransitionController(it) }
        if (animationController == null) {
            currentDialog?.dismiss()
        }
        activityStarter.startActivity(
            intent,
            true, /* dismissShade */
            animationController,
        )
    }

    companion object {
        private const val TAG = "ModesDialogDelegate"
        private val ZEN_MODE_SETTINGS_INTENT = Intent(Settings.ACTION_ZEN_MODE_SETTINGS)
        private const val INTERACTION_JANK_TAG = "configure_priority_modes"
    }
}
Loading