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

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

Merge changes Ic40fd27c,I7658ddd5 into main

* changes:
  Show modes that need setup in dialog
  Visual stability for modes dialog
parents 80edeb36 ad971777
Loading
Loading
Loading
Loading
+15 −7
Original line number Diff line number Diff line
@@ -73,15 +73,22 @@ class FakeZenModeRepository : ZenModeRepository {
    }

    fun activateMode(id: String) {
        val oldMode = mutableModesFlow.value.find { it.id == id } ?: return
        removeMode(id)
        mutableModesFlow.value += TestModeBuilder(oldMode).setActive(true).build()
        updateModeActiveState(id = id, isActive = true)
    }

    fun deactivateMode(id: String) {
        val oldMode = mutableModesFlow.value.find { it.id == id } ?: return
        removeMode(id)
        mutableModesFlow.value += TestModeBuilder(oldMode).setActive(false).build()
        updateModeActiveState(id = id, isActive = false)
    }

    // Update the active state while maintaining the mode's position in the list
    private fun updateModeActiveState(id: String, isActive: Boolean) {
        val modes = mutableModesFlow.value.toMutableList()
        val index = modes.indexOfFirst { it.id == id }
        if (index < 0) {
            throw IllegalArgumentException("mode $id not found")
        }
        modes[index] = TestModeBuilder(modes[index]).setActive(isActive).build()
        mutableModesFlow.value = modes
    }
}

@@ -101,7 +108,8 @@ fun FakeZenModeRepository.updateNotificationPolicy(
            suppressedVisualEffects,
            state,
            priorityConversationSenders,
        ))
        )
    )

private fun newMode(id: String, active: Boolean = false): ZenMode {
    return TestModeBuilder().setId(id).setName("Mode $id").setActive(active).build()
+10 −0
Original line number Diff line number Diff line
@@ -16,6 +16,9 @@

package com.android.settingslib.notification.modes;

import static android.service.notification.ZenModeConfig.UPDATE_ORIGIN_UNKNOWN;
import static android.service.notification.ZenModeConfig.UPDATE_ORIGIN_USER;

import android.app.AutomaticZenRule;
import android.app.NotificationManager;
import android.content.ComponentName;
@@ -144,8 +147,15 @@ public class TestModeBuilder {
    }

    public TestModeBuilder setEnabled(boolean enabled) {
        return setEnabled(enabled, /* byUser= */ false);
    }

    public TestModeBuilder setEnabled(boolean enabled, boolean byUser) {
        mRule.setEnabled(enabled);
        mConfigZenRule.enabled = enabled;
        if (!enabled) {
            mConfigZenRule.disabledOrigin = byUser ? UPDATE_ORIGIN_USER : UPDATE_ORIGIN_UNKNOWN;
        }
        return this;
    }

+214 −5
Original line number Diff line number Diff line
@@ -33,6 +33,7 @@ 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
import kotlinx.coroutines.Job
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Test
@@ -54,13 +55,20 @@ class ModesDialogViewModelTest : SysuiTestCase() {
        ModesDialogViewModel(context, interactor, kosmos.testDispatcher, mockDialogDelegate)

    @Test
    fun tiles_filtersOutDisabledModes() =
    fun tiles_filtersOutUserDisabledModes() =
        testScope.runTest {
            val tiles by collectLastValue(underTest.tiles)

            repository.addModes(
                listOf(
                    TestModeBuilder().setName("Disabled").setEnabled(false).build(),
                    TestModeBuilder()
                        .setName("Disabled by user")
                        .setEnabled(false, /* byUser= */ true)
                        .build(),
                    TestModeBuilder()
                        .setName("Disabled by other")
                        .setEnabled(false, /* byUser= */ false)
                        .build(),
                    TestModeBuilder.MANUAL_DND,
                    TestModeBuilder()
                        .setName("Enabled")
@@ -69,20 +77,25 @@ class ModesDialogViewModelTest : SysuiTestCase() {
                        .build(),
                    TestModeBuilder()
                        .setName("Disabled with manual")
                        .setEnabled(false)
                        .setEnabled(false, /* byUser= */ true)
                        .setManualInvocationAllowed(true)
                        .build(),
                )
            )
            runCurrent()

            assertThat(tiles?.size).isEqualTo(2)
            assertThat(tiles?.size).isEqualTo(3)
            with(tiles?.elementAt(0)!!) {
                assertThat(this.text).isEqualTo("Disabled by other")
                assertThat(this.subtext).isEqualTo("Set up")
                assertThat(this.enabled).isEqualTo(false)
            }
            with(tiles?.elementAt(1)!!) {
                assertThat(this.text).isEqualTo("Manual DND")
                assertThat(this.subtext).isEqualTo("On")
                assertThat(this.enabled).isEqualTo(true)
            }
            with(tiles?.elementAt(1)!!) {
            with(tiles?.elementAt(2)!!) {
                assertThat(this.text).isEqualTo("Enabled")
                assertThat(this.subtext).isEqualTo("Off")
                assertThat(this.enabled).isEqualTo(false)
@@ -139,6 +152,117 @@ class ModesDialogViewModelTest : SysuiTestCase() {
            }
        }

    @Test
    fun tiles_stableWhileCollecting() =
        testScope.runTest {
            val job = Job()
            val tiles by collectLastValue(underTest.tiles, context = job)

            repository.addModes(
                listOf(
                    TestModeBuilder()
                        .setName("Active without manual")
                        .setActive(true)
                        .setManualInvocationAllowed(false)
                        .build(),
                    TestModeBuilder()
                        .setName("Active with manual")
                        .setActive(true)
                        .setManualInvocationAllowed(true)
                        .build(),
                    TestModeBuilder()
                        .setName("Inactive with manual")
                        .setActive(false)
                        .setManualInvocationAllowed(true)
                        .build(),
                    TestModeBuilder()
                        .setName("Inactive without manual")
                        .setActive(false)
                        .setManualInvocationAllowed(false)
                        .build(),
                )
            )
            runCurrent()

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

            // Check that tile is initially present
            with(tiles?.elementAt(0)!!) {
                assertThat(this.text).isEqualTo("Active without manual")
                assertThat(this.subtext).isEqualTo("On")
                assertThat(this.enabled).isEqualTo(true)

                // Click tile to toggle it
                this.onClick()
                runCurrent()
            }
            // Check that tile is still present at the same location, but turned off
            assertThat(tiles?.size).isEqualTo(3)
            with(tiles?.elementAt(0)!!) {
                assertThat(this.text).isEqualTo("Active without manual")
                assertThat(this.subtext).isEqualTo("Manage in settings")
                assertThat(this.enabled).isEqualTo(false)
            }

            // Stop collecting, then start again
            job.cancel()
            val tiles2 by collectLastValue(underTest.tiles)
            runCurrent()

            // Check that tile is now gone
            assertThat(tiles2?.size).isEqualTo(2)
            assertThat(tiles2?.elementAt(0)!!.text).isEqualTo("Active with manual")
            assertThat(tiles2?.elementAt(1)!!.text).isEqualTo("Inactive with manual")
        }

    @Test
    fun tiles_filtersOutRemovedModes() =
        testScope.runTest {
            val job = Job()
            val tiles by collectLastValue(underTest.tiles, context = job)

            repository.addModes(
                listOf(
                    TestModeBuilder()
                        .setId("A")
                        .setName("Active without manual")
                        .setActive(true)
                        .setManualInvocationAllowed(false)
                        .build(),
                    TestModeBuilder()
                        .setId("B")
                        .setName("Active with manual")
                        .setActive(true)
                        .setManualInvocationAllowed(true)
                        .build(),
                    TestModeBuilder()
                        .setId("C")
                        .setName("Inactive with manual")
                        .setActive(false)
                        .setManualInvocationAllowed(true)
                        .build(),
                )
            )
            runCurrent()

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

            repository.removeMode("A")
            runCurrent()

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

            repository.removeMode("B")
            runCurrent()

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

            repository.removeMode("C")
            runCurrent()

            assertThat(tiles?.size).isEqualTo(0)
        }

    @Test
    fun onClick_togglesTileState() =
        testScope.runTest {
@@ -172,6 +296,91 @@ class ModesDialogViewModelTest : SysuiTestCase() {
            assertThat(tiles?.first()?.enabled).isFalse()
        }

    @Test
    fun onClick_noManualActivation() =
        testScope.runTest {
            val job = Job()
            val tiles by collectLastValue(underTest.tiles, context = job)

            repository.addModes(
                listOf(
                    TestModeBuilder()
                        .setName("Active without manual")
                        .setActive(true)
                        .setManualInvocationAllowed(false)
                        .build(),
                )
            )
            runCurrent()

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

            // Click tile to toggle it off
            tiles?.elementAt(0)!!.onClick()
            runCurrent()

            assertThat(tiles?.size).isEqualTo(1)
            with(tiles?.elementAt(0)!!) {
                assertThat(this.text).isEqualTo("Active without manual")
                assertThat(this.subtext).isEqualTo("Manage in settings")
                assertThat(this.enabled).isEqualTo(false)

                // Press the tile again
                this.onClick()
                runCurrent()
            }

            // Check that nothing happened
            with(tiles?.elementAt(0)!!) {
                assertThat(this.text).isEqualTo("Active without manual")
                assertThat(this.subtext).isEqualTo("Manage in settings")
                assertThat(this.enabled).isEqualTo(false)
            }
        }

    @Test
    fun onClick_setUp() =
        testScope.runTest {
            val tiles by collectLastValue(underTest.tiles)

            repository.addModes(
                listOf(
                    TestModeBuilder()
                        .setId("ID")
                        .setName("Disabled by other")
                        .setEnabled(false, /* byUser= */ false)
                        .build(),
                )
            )
            runCurrent()

            assertThat(tiles?.size).isEqualTo(1)
            with(tiles?.elementAt(0)!!) {
                assertThat(this.text).isEqualTo("Disabled by other")
                assertThat(this.subtext).isEqualTo("Set up")
                assertThat(this.enabled).isEqualTo(false)

                // Click the tile
                this.onClick()
                runCurrent()
            }

            // Check that it launched the correct intent
            val intentCaptor = argumentCaptor<Intent>()
            verify(mockDialogDelegate).launchFromDialog(intentCaptor.capture())
            val intent = intentCaptor.lastValue
            assertThat(intent.action).isEqualTo(Settings.ACTION_AUTOMATIC_ZEN_RULE_SETTINGS)
            assertThat(intent.extras?.getString(Settings.EXTRA_AUTOMATIC_ZEN_RULE_ID))
                .isEqualTo("ID")

            // Check that nothing happened to the tile
            with(tiles?.elementAt(0)!!) {
                assertThat(this.text).isEqualTo("Disabled by other")
                assertThat(this.subtext).isEqualTo("Set up")
                assertThat(this.enabled).isEqualTo(false)
            }
        }

    @Test
    fun onLongClick_launchesIntent() =
        testScope.runTest {
+6 −0
Original line number Diff line number Diff line
@@ -1103,6 +1103,12 @@
    <!-- Priority modes: label for an inactive mode [CHAR LIMIT=35] -->
    <string name="zen_mode_off">Off</string>

    <!-- Priority modes: label for a mode that needs to be set up [CHAR LIMIT=35] -->
    <string name="zen_mode_set_up">Set up</string>

    <!-- Priority modes: label for a mode that cannot be manually turned on [CHAR LIMIT=35] -->
    <string name="zen_mode_no_manual_invocation">Manage in settings</string>

    <!-- Zen mode: Priority only introduction message on first use -->
    <string name="zen_priority_introduction">You won\'t be disturbed by sounds and vibrations, except from alarms, reminders, events, and callers you specify. You\'ll still hear anything you choose to play including music, videos, and games.</string>

+48 −15
Original line number Diff line number Diff line
@@ -31,6 +31,7 @@ import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.scan

/**
 * Viewmodel for the priority ("zen") modes dialog that can be opened from quick settings. It allows
@@ -46,11 +47,30 @@ constructor(
    private val dialogDelegate: ModesDialogDelegate,
) {
    // Modes that should be displayed in the dialog
    // TODO(b/346519570): Include modes that have not been set up yet.
    private val visibleModes: Flow<List<ZenMode>> =
        zenModeInteractor.modes.map {
            it.filter { mode ->
                mode.rule.isEnabled && (mode.isActive || mode.rule.isManualInvocationAllowed)
        zenModeInteractor.modes
            // While this is being collected (or in other words, while the dialog is open), we don't
            // want a mode to disappear from the list if, for instance, the user deactivates it,
            // since that can be confusing (similar to how we have visual stability for
            // notifications while the shade is open).
            // This ensures new modes are added to the list, and updates to modes already in the
            // list are registered correctly.
            .scan(listOf()) { prev, modes ->
                val prevIds = prev.map { it.id }.toSet()

                modes.filter { mode ->
                    when {
                        // Mode appeared previously -> keep it even if otherwise we may have
                        // filtered it
                        mode.id in prevIds -> true
                        // Mode is enabled -> show if active (so user can toggle off), or if it
                        // can be manually toggled on
                        mode.rule.isEnabled -> mode.isActive || mode.rule.isManualInvocationAllowed
                        // Mode was created as disabled, or disabled by the app that owns it ->
                        // will be shown with a "Set up" text
                        !mode.rule.isEnabled -> mode.status == ZenMode.Status.DISABLED_BY_OTHER
                        else -> false
                    }
                }
            }

@@ -68,26 +88,39 @@ constructor(
                        //  "ON: Do Not Disturb, Until Mon 08:09"; see DndTile.
                        contentDescription = "",
                        onClick = {
                            if (mode.isActive) {
                            if (!mode.rule.isEnabled) {
                                openSettings(mode)
                            } else if (mode.isActive) {
                                zenModeInteractor.deactivateMode(mode)
                            } else {
                                if (mode.rule.isManualInvocationAllowed) {
                                    // TODO(b/346519570): Handle duration for DND mode.
                                    zenModeInteractor.activateMode(mode)
                                }
                            }
                        },
                        onLongClick = {
                        onLongClick = { openSettings(mode) }
                    )
                }
            }
            .flowOn(bgDispatcher)

    private fun openSettings(mode: ZenMode) {
        val intent: Intent =
            Intent(ACTION_AUTOMATIC_ZEN_RULE_SETTINGS)
                .putExtra(EXTRA_AUTOMATIC_ZEN_RULE_ID, mode.id)

        dialogDelegate.launchFromDialog(intent)
    }
                    )

    private fun getTileSubtext(mode: ZenMode): String {
        if (!mode.rule.isEnabled) {
            return context.resources.getString(R.string.zen_mode_set_up)
        }
        if (!mode.rule.isManualInvocationAllowed && !mode.isActive) {
            return context.resources.getString(R.string.zen_mode_no_manual_invocation)
        }
            .flowOn(bgDispatcher)

    private fun getTileSubtext(mode: ZenMode): String {
        // TODO(b/346519570): Use ZenModeConfig.getDescription for manual DND
        val on = context.resources.getString(R.string.zen_mode_on)
        val off = context.resources.getString(R.string.zen_mode_off)
        return mode.rule.triggerDescription ?: if (mode.isActive) on else off