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

Commit da44fb35 authored by Ioana Alexandru's avatar Ioana Alexandru
Browse files

Launch modes settings from empty shade view

When modes are blocking notifications in the shade, launch the
corresponding modes setting when pressing on the text. If only one mode
is blocking notifications, launch that specific mode's settings page. If
multiple modes are blocking notifications, launch the general Modes
settings page.

In order to do this nicely, I am replacing
NotificationActivityStarter#startHistoryIntent with a more generic
NotificationActivityStarter#startSettingsIntent. Also using the same
approach for the FooterView for consistency, as this would allow us to
also launch other things from there in the future. Another benefit is
also that it allows us to test this logic (since it's in the viewModel
instead of the binder now).

Bug: 366003631
Test: manually tested that the intents are launched correctly (and the
back stack is correct) + viewmodel unit tests
Flag: android.app.modes_ui_empty_shade

Change-Id: I3c0f9e9109c6a007749e12be95f5f858388d04a5
parent 0187fa31
Loading
Loading
Loading
Loading
+82 −0
Original line number Diff line number Diff line
@@ -30,6 +30,7 @@ import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.flags.andSceneContainer
import com.android.systemui.kosmos.testScope
import com.android.systemui.shared.settings.data.repository.fakeSecureSettingsRepository
import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository
import com.android.systemui.statusbar.notification.emptyshade.shared.ModesEmptyShadeFix
import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor
@@ -53,6 +54,7 @@ class EmptyShadeViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
    private val testScope = kosmos.testScope
    private val zenModeRepository = kosmos.zenModeRepository
    private val activeNotificationListRepository = kosmos.activeNotificationListRepository
    private val fakeSecureSettingsRepository = kosmos.fakeSecureSettingsRepository

    private val underTest = kosmos.emptyShadeViewModel

@@ -205,4 +207,84 @@ class EmptyShadeViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {

            assertThat(footerVisible).isTrue()
        }

    @EnableFlags(ModesEmptyShadeFix.FLAG_NAME)
    @Test
    fun onClick_whenHistoryDisabled_leadsToSettingsPage() =
        testScope.runTest {
            val onClick by collectLastValue(underTest.onClick)
            runCurrent()

            fakeSecureSettingsRepository.setInt(Settings.Secure.NOTIFICATION_HISTORY_ENABLED, 0)

            assertThat(onClick?.targetIntent?.action)
                .isEqualTo(Settings.ACTION_NOTIFICATION_SETTINGS)
            assertThat(onClick?.backStack).isEmpty()
        }

    @EnableFlags(ModesEmptyShadeFix.FLAG_NAME)
    @Test
    fun onClick_whenHistoryEnabled_leadsToHistoryPage() =
        testScope.runTest {
            val onClick by collectLastValue(underTest.onClick)
            runCurrent()

            fakeSecureSettingsRepository.setInt(Settings.Secure.NOTIFICATION_HISTORY_ENABLED, 1)

            assertThat(onClick?.targetIntent?.action)
                .isEqualTo(Settings.ACTION_NOTIFICATION_HISTORY)
            assertThat(onClick?.backStack?.map { it.action })
                .containsExactly(Settings.ACTION_NOTIFICATION_SETTINGS)
        }

    @EnableFlags(ModesEmptyShadeFix.FLAG_NAME)
    @Test
    fun onClick_whenOneModeHidingNotifications_leadsToModeSettings() =
        testScope.runTest {
            val onClick by collectLastValue(underTest.onClick)
            runCurrent()

            zenModeRepository.addMode(
                TestModeBuilder()
                    .setId("ID")
                    .setActive(true)
                    .setVisualEffect(VISUAL_EFFECT_NOTIFICATION_LIST, /* allowed= */ false)
                    .build()
            )
            runCurrent()

            assertThat(onClick?.targetIntent?.action)
                .isEqualTo(Settings.ACTION_AUTOMATIC_ZEN_RULE_SETTINGS)
            assertThat(
                    onClick?.targetIntent?.extras?.getString(Settings.EXTRA_AUTOMATIC_ZEN_RULE_ID)
                )
                .isEqualTo("ID")
            assertThat(onClick?.backStack?.map { it.action })
                .containsExactly(Settings.ACTION_ZEN_MODE_SETTINGS)
        }

    @EnableFlags(ModesEmptyShadeFix.FLAG_NAME)
    @Test
    fun onClick_whenMultipleModesHidingNotifications_leadsToGeneralModesSettings() =
        testScope.runTest {
            val onClick by collectLastValue(underTest.onClick)
            runCurrent()

            zenModeRepository.addMode(
                TestModeBuilder()
                    .setActive(true)
                    .setVisualEffect(VISUAL_EFFECT_NOTIFICATION_LIST, /* allowed= */ false)
                    .build()
            )
            zenModeRepository.addMode(
                TestModeBuilder()
                    .setActive(true)
                    .setVisualEffect(VISUAL_EFFECT_NOTIFICATION_LIST, /* allowed= */ false)
                    .build()
            )
            runCurrent()

            assertThat(onClick?.targetIntent?.action).isEqualTo(Settings.ACTION_ZEN_MODE_SETTINGS)
            assertThat(onClick?.backStack).isEmpty()
        }
}
+57 −2
Original line number Diff line number Diff line
@@ -16,6 +16,11 @@
package com.android.systemui.statusbar.notification

import android.content.Intent
import android.provider.Settings.ACTION_AUTOMATIC_ZEN_RULE_SETTINGS
import android.provider.Settings.ACTION_NOTIFICATION_HISTORY
import android.provider.Settings.ACTION_NOTIFICATION_SETTINGS
import android.provider.Settings.ACTION_ZEN_MODE_SETTINGS
import android.provider.Settings.EXTRA_AUTOMATIC_ZEN_RULE_ID
import android.view.View
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
@@ -25,6 +30,7 @@ import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
 * (e.g. clicking on a notification, tapping on the settings icon in the notification guts)
 */
interface NotificationActivityStarter {

    /** Called when the user clicks on the notification bubble icon. */
    fun onNotificationBubbleIconClicked(entry: NotificationEntry?)

@@ -35,14 +41,63 @@ interface NotificationActivityStarter {
    fun startNotificationGutsIntent(intent: Intent?, appUid: Int, row: ExpandableNotificationRow?)

    /**
     * Called when the user clicks "Manage" or "History" in the Shade, or the "No notifications"
     * text.
     * Called when the user clicks "Manage" or "History" in the Shade. Prefer using
     * [startSettingsIntent] instead.
     */
    fun startHistoryIntent(view: View?, showHistory: Boolean)

    /**
     * Called to open a settings intent from a launchable view (such as the "Manage" or "History"
     * button in the shade, or the "No notifications" text).
     *
     * @param view the view to perform the launch animation from (must extend [LaunchableView])
     * @param intentInfo information about the (settings) intent to be launched
     */
    fun startSettingsIntent(view: View, intentInfo: SettingsIntent)

    /** Called when the user succeed to drop notification to proper target view. */
    fun onDragSuccess(entry: NotificationEntry?)

    val isCollapsingToShowActivityOverLockscreen: Boolean
        get() = false

    /**
     * Information about a settings intent to be launched.
     *
     * If the [targetIntent] is T and [backStack] is [A, B, C], the stack will look like
     * [A, B, C, T].
     */
    data class SettingsIntent(
        var targetIntent: Intent,
        var backStack: List<Intent> = emptyList(),
        var cujType: Int? = null,
    ) {
        // Utility factory methods for known intents
        companion object {
            fun forNotificationSettings(cujType: Int? = null) =
                SettingsIntent(
                    targetIntent = Intent(ACTION_NOTIFICATION_SETTINGS),
                    cujType = cujType,
                )

            fun forNotificationHistory(cujType: Int? = null) =
                SettingsIntent(
                    targetIntent = Intent(ACTION_NOTIFICATION_HISTORY),
                    backStack = listOf(Intent(ACTION_NOTIFICATION_SETTINGS)),
                    cujType = cujType,
                )

            fun forModesSettings(cujType: Int? = null) =
                SettingsIntent(targetIntent = Intent(ACTION_ZEN_MODE_SETTINGS), cujType = cujType)

            fun forModeSettings(modeId: String, cujType: Int? = null) =
                SettingsIntent(
                    targetIntent =
                        Intent(ACTION_AUTOMATIC_ZEN_RULE_SETTINGS)
                            .putExtra(EXTRA_AUTOMATIC_ZEN_RULE_ID, modeId),
                    backStack = listOf(Intent(ACTION_ZEN_MODE_SETTINGS)),
                    cujType = cujType,
                )
        }
    }
}
+6 −7
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@
package com.android.systemui.statusbar.notification.emptyshade.ui.viewbinder

import android.view.View
import com.android.systemui.statusbar.notification.NotificationActivityStarter
import com.android.systemui.statusbar.notification.emptyshade.ui.view.EmptyShadeView
import com.android.systemui.statusbar.notification.emptyshade.ui.viewmodel.EmptyShadeViewModel
import kotlinx.coroutines.coroutineScope
@@ -26,18 +27,16 @@ object EmptyShadeViewBinder {
    suspend fun bind(
        view: EmptyShadeView,
        viewModel: EmptyShadeViewModel,
        launchNotificationSettings: View.OnClickListener,
        launchNotificationHistory: View.OnClickListener,
        notificationActivityStarter: NotificationActivityStarter,
    ) = coroutineScope {
        launch { viewModel.text.collect { view.setText(it) } }

        launch {
            viewModel.tappingShouldLaunchHistory.collect { shouldLaunchHistory ->
                if (shouldLaunchHistory) {
                    view.setOnClickListener(launchNotificationHistory)
                } else {
                    view.setOnClickListener(launchNotificationSettings)
            viewModel.onClick.collect { settingsIntent ->
                val onClickListener = { view: View ->
                    notificationActivityStarter.startSettingsIntent(view, settingsIntent)
                }
                view.setOnClickListener(onClickListener)
            }
        }

+22 −4
Original line number Diff line number Diff line
@@ -22,6 +22,7 @@ import com.android.systemui.dump.DumpManager
import com.android.systemui.modes.shared.ModesUi
import com.android.systemui.res.R
import com.android.systemui.shared.notifications.domain.interactor.NotificationSettingsInteractor
import com.android.systemui.statusbar.notification.NotificationActivityStarter.SettingsIntent
import com.android.systemui.statusbar.notification.domain.interactor.SeenNotificationsInteractor
import com.android.systemui.statusbar.notification.emptyshade.shared.ModesEmptyShadeFix
import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor
@@ -34,6 +35,7 @@ import java.util.Locale
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map

@@ -80,8 +82,7 @@ constructor(
            if (ModesUi.isEnabled) {
                zenModeInteractor.modesHidingNotifications.map { modes ->
                    // Create a string that is either "No notifications" if no modes are filtering
                    // them
                    // out, or something like "Notifications paused by SomeMode" otherwise.
                    // them out, or something like "Notifications paused by SomeMode" otherwise.
                    val msgFormat =
                        MessageFormat(
                            context.getString(R.string.modes_suppressing_shade_text),
@@ -116,9 +117,26 @@ constructor(
        )
    }

    val tappingShouldLaunchHistory by lazy {
    val onClick: Flow<SettingsIntent> by lazy {
        ModesEmptyShadeFix.assertInNewMode()
        notificationSettingsInteractor.isNotificationHistoryEnabled
        combine(
            zenModeInteractor.modesHidingNotifications,
            notificationSettingsInteractor.isNotificationHistoryEnabled,
        ) { modes, isNotificationHistoryEnabled ->
            if (modes.isNotEmpty()) {
                if (modes.size == 1) {
                    SettingsIntent.forModeSettings(modes[0].id)
                } else {
                    SettingsIntent.forModesSettings()
                }
            } else {
                if (isNotificationHistoryEnabled) {
                    SettingsIntent.forNotificationHistory()
                } else {
                    SettingsIntent.forNotificationSettings()
                }
            }
        }
    }

    @AssistedFactory
+21 −5
Original line number Diff line number Diff line
@@ -19,6 +19,8 @@ package com.android.systemui.statusbar.notification.footer.ui.viewbinder
import android.view.View
import androidx.lifecycle.lifecycleScope
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.statusbar.notification.NotificationActivityStarter
import com.android.systemui.statusbar.notification.emptyshade.shared.ModesEmptyShadeFix
import com.android.systemui.statusbar.notification.footer.ui.view.FooterView
import com.android.systemui.statusbar.notification.footer.ui.viewmodel.FooterViewModel
import com.android.systemui.util.ui.isAnimating
@@ -36,6 +38,7 @@ object FooterViewBinder {
        clearAllNotifications: View.OnClickListener,
        launchNotificationSettings: View.OnClickListener,
        launchNotificationHistory: View.OnClickListener,
        notificationActivityStarter: NotificationActivityStarter,
    ): DisposableHandle {
        return footer.repeatWhenAttached {
            lifecycleScope.launch {
@@ -45,6 +48,7 @@ object FooterViewBinder {
                    clearAllNotifications,
                    launchNotificationSettings,
                    launchNotificationHistory,
                    notificationActivityStarter,
                )
            }
        }
@@ -56,6 +60,7 @@ object FooterViewBinder {
        clearAllNotifications: View.OnClickListener,
        launchNotificationSettings: View.OnClickListener,
        launchNotificationHistory: View.OnClickListener,
        notificationActivityStarter: NotificationActivityStarter,
    ) = coroutineScope {
        launch { bindClearAllButton(footer, viewModel, clearAllNotifications) }
        launch {
@@ -64,6 +69,7 @@ object FooterViewBinder {
                viewModel,
                launchNotificationSettings,
                launchNotificationHistory,
                notificationActivityStarter,
            )
        }
        launch { bindMessage(footer, viewModel) }
@@ -113,8 +119,17 @@ object FooterViewBinder {
        viewModel: FooterViewModel,
        launchNotificationSettings: View.OnClickListener,
        launchNotificationHistory: View.OnClickListener,
        notificationActivityStarter: NotificationActivityStarter,
    ) = coroutineScope {
        launch {
            if (ModesEmptyShadeFix.isEnabled) {
                viewModel.manageOrHistoryButtonClick.collect { settingsIntent ->
                    val onClickListener = { view: View ->
                        notificationActivityStarter.startSettingsIntent(view, settingsIntent)
                    }
                    footer.setManageButtonClickListener(onClickListener)
                }
            } else {
                viewModel.manageButtonShouldLaunchHistory.collect { shouldLaunchHistory ->
                    if (shouldLaunchHistory) {
                        footer.setManageButtonClickListener(launchNotificationHistory)
@@ -123,6 +138,7 @@ object FooterViewBinder {
                    }
                }
            }
        }

        launch {
            viewModel.manageOrHistoryButton.labelId.collect { textId ->
Loading