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

Commit 6f79e20d authored by helen cheuk's avatar helen cheuk
Browse files

[Action Corner] Get action from settings

Get action from settings and convert it to action type for each action
corner. Read the action type in ActionCornerInteractor when action
corner is active.

Bug: 397182595
Flag: com.android.systemui.shared.cursor_hot_corner
Test: ActionCornerSettingRepositoryTest
Test: ActionCornerInteractorTest

Change-Id: I1e94e9a1d7b4865bacd08b30b890d8b6ae65fc04
parent 214beace
Loading
Loading
Loading
Loading
+119 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2025 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.actioncorner.data.repository

import android.provider.Settings.Secure.ACTION_CORNER_ACTION_HOME
import android.provider.Settings.Secure.ACTION_CORNER_ACTION_NOTIFICATIONS
import android.provider.Settings.Secure.ACTION_CORNER_ACTION_OVERVIEW
import android.provider.Settings.Secure.ACTION_CORNER_ACTION_QUICK_SETTINGS
import android.provider.Settings.Secure.ACTION_CORNER_BOTTOM_LEFT_ACTION
import android.provider.Settings.Secure.ACTION_CORNER_BOTTOM_RIGHT_ACTION
import android.provider.Settings.Secure.ACTION_CORNER_TOP_LEFT_ACTION
import android.provider.Settings.Secure.ACTION_CORNER_TOP_RIGHT_ACTION
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.actioncorner.data.model.ActionType.HOME
import com.android.systemui.actioncorner.data.model.ActionType.NONE
import com.android.systemui.actioncorner.data.model.ActionType.NOTIFICATIONS
import com.android.systemui.actioncorner.data.model.ActionType.OVERVIEW
import com.android.systemui.actioncorner.data.model.ActionType.QUICK_SETTINGS
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
import com.android.systemui.kosmos.collectLastValue
import com.android.systemui.kosmos.runTest
import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.kosmos.testScope
import com.android.systemui.kosmos.useUnconfinedTestDispatcher
import com.android.systemui.testKosmos
import com.android.systemui.util.settings.data.repository.userAwareSecureSettingsRepository
import com.google.common.truth.Truth.assertThat
import kotlin.test.Test
import org.junit.Rule
import org.junit.runner.RunWith
import org.mockito.junit.MockitoJUnit
import org.mockito.junit.MockitoRule

@SmallTest
@RunWith(AndroidJUnit4::class)
@kotlinx.coroutines.ExperimentalCoroutinesApi
class ActionCornerSettingRepositoryTest : SysuiTestCase() {
    @get:Rule val mockitoRule: MockitoRule = MockitoJUnit.rule()
    private val kosmos = testKosmos().useUnconfinedTestDispatcher()
    private val settingsRepository = kosmos.userAwareSecureSettingsRepository
    private val Kosmos.underTest by Fixture {
        ActionCornerSettingRepository(settingsRepository, testScope.backgroundScope, testDispatcher)
    }

    @Test
    fun allCornersDefaultToNoneAction() =
        kosmos.runTest {
            val cornerActions =
                listOf(
                    underTest.topLeftCornerAction,
                    underTest.topRightCornerAction,
                    underTest.bottomLeftCornerAction,
                    underTest.bottomRightCornerAction,
                )

            cornerActions.forEach {
                val model by collectLastValue(it)
                assertThat(model).isEqualTo(NONE)
            }
        }

    @Test
    fun testNotificationsActionOnTopLeftCorner() =
        kosmos.runTest {
            settingsRepository.setInt(
                ACTION_CORNER_TOP_LEFT_ACTION,
                ACTION_CORNER_ACTION_NOTIFICATIONS,
            )
            val model by collectLastValue(underTest.topLeftCornerAction)
            assertThat(model).isEqualTo(NOTIFICATIONS)
        }

    @Test
    fun testQuickSettingsActionOnTopRightCorner() =
        kosmos.runTest {
            settingsRepository.setInt(
                ACTION_CORNER_TOP_RIGHT_ACTION,
                ACTION_CORNER_ACTION_QUICK_SETTINGS,
            )
            val model by collectLastValue(underTest.topRightCornerAction)
            assertThat(model).isEqualTo(QUICK_SETTINGS)
        }

    @Test
    fun testOverviewActionOnBottomLeftCorner() =
        kosmos.runTest {
            settingsRepository.setInt(
                ACTION_CORNER_BOTTOM_LEFT_ACTION,
                ACTION_CORNER_ACTION_OVERVIEW,
            )
            val model by collectLastValue(underTest.bottomLeftCornerAction)
            assertThat(model).isEqualTo(OVERVIEW)
        }

    @Test
    fun testHomeActionOnBottomRightCorner() =
        kosmos.runTest {
            settingsRepository.setInt(ACTION_CORNER_BOTTOM_RIGHT_ACTION, ACTION_CORNER_ACTION_HOME)
            val model by collectLastValue(underTest.bottomRightCornerAction)
            assertThat(model).isEqualTo(HOME)
        }
}
+45 −6
Original line number Diff line number Diff line
@@ -16,6 +16,14 @@

package com.android.systemui.actioncorner.domain.interactor

import android.provider.Settings.Secure.ACTION_CORNER_ACTION_HOME
import android.provider.Settings.Secure.ACTION_CORNER_ACTION_NOTIFICATIONS
import android.provider.Settings.Secure.ACTION_CORNER_ACTION_OVERVIEW
import android.provider.Settings.Secure.ACTION_CORNER_ACTION_QUICK_SETTINGS
import android.provider.Settings.Secure.ACTION_CORNER_BOTTOM_LEFT_ACTION
import android.provider.Settings.Secure.ACTION_CORNER_BOTTOM_RIGHT_ACTION
import android.provider.Settings.Secure.ACTION_CORNER_TOP_LEFT_ACTION
import android.provider.Settings.Secure.ACTION_CORNER_TOP_RIGHT_ACTION
import android.view.Display.DEFAULT_DISPLAY
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
@@ -25,11 +33,13 @@ import com.android.systemui.actioncorner.data.model.ActionCornerRegion
import com.android.systemui.actioncorner.data.model.ActionCornerRegion.BOTTOM_LEFT
import com.android.systemui.actioncorner.data.model.ActionCornerRegion.BOTTOM_RIGHT
import com.android.systemui.actioncorner.data.model.ActionCornerState.ActiveActionCorner
import com.android.systemui.actioncorner.data.repository.ActionCornerSettingRepository
import com.android.systemui.actioncorner.data.repository.FakeActionCornerRepository
import com.android.systemui.flags.EnableSceneContainer
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
import com.android.systemui.kosmos.runTest
import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.kosmos.testScope
import com.android.systemui.kosmos.useUnconfinedTestDispatcher
import com.android.systemui.lifecycle.activateIn
@@ -42,6 +52,7 @@ import com.android.systemui.shade.shadeTestUtil
import com.android.systemui.shared.system.actioncorner.ActionCornerConstants.HOME
import com.android.systemui.shared.system.actioncorner.ActionCornerConstants.OVERVIEW
import com.android.systemui.testKosmos
import com.android.systemui.util.settings.data.repository.userAwareSecureSettingsRepository
import com.google.common.truth.Truth.assertThat
import kotlin.test.Test
import org.junit.Before
@@ -55,6 +66,12 @@ import org.mockito.kotlin.verify
class ActionCornerInteractorTest : SysuiTestCase() {
    private val kosmos = testKosmos().useUnconfinedTestDispatcher()
    private val Kosmos.actionCornerRepository by Fixture { FakeActionCornerRepository() }

    private val settingsRepository = kosmos.userAwareSecureSettingsRepository
    private val Kosmos.actionCornerSettingRepository by Fixture {
        ActionCornerSettingRepository(settingsRepository, testScope.backgroundScope, testDispatcher)
    }

    private val Kosmos.launcherProxyService by Fixture { mock<LauncherProxyService>() }
    private val Kosmos.underTest by Fixture {
        ActionCornerInteractor(
@@ -63,6 +80,7 @@ class ActionCornerInteractorTest : SysuiTestCase() {
            launcherProxyService,
            shadeModeInteractor,
            shadeInteractor,
            actionCornerSettingRepository,
        )
    }

@@ -73,22 +91,31 @@ class ActionCornerInteractorTest : SysuiTestCase() {
    }

    @Test
    fun bottomLeftCornerActivated_notifyLauncherOfOverviewAction() =
    fun bottomLeftCornerActivated_overviewActionConfigured_notifyLauncherOfOverviewAction() =
        kosmos.runTest {
            settingsRepository.setInt(
                ACTION_CORNER_BOTTOM_LEFT_ACTION,
                ACTION_CORNER_ACTION_OVERVIEW,
            )
            actionCornerRepository.addState(ActiveActionCorner(BOTTOM_LEFT, DEFAULT_DISPLAY))
            verify(launcherProxyService).onActionCornerActivated(OVERVIEW, DEFAULT_DISPLAY)
        }

    @Test
    fun bottomRightCornerActivated_notifyLauncherOfHomeAction() =
    fun bottomRightCornerActivated_homeActionConfigured_notifyLauncherOfHomeAction() =
        kosmos.runTest {
            settingsRepository.setInt(ACTION_CORNER_BOTTOM_RIGHT_ACTION, ACTION_CORNER_ACTION_HOME)
            actionCornerRepository.addState(ActiveActionCorner(BOTTOM_RIGHT, DEFAULT_DISPLAY))
            verify(launcherProxyService).onActionCornerActivated(HOME, DEFAULT_DISPLAY)
        }

    @Test
    fun shadeCollapsed_topLeftCornerActivated_expandNotificationShade() =
    fun shadeCollapsed_topLeftCornerActivated_notificationsActionConfigured_expandNotificationShade() =
        kosmos.runTest {
            settingsRepository.setInt(
                ACTION_CORNER_TOP_LEFT_ACTION,
                ACTION_CORNER_ACTION_NOTIFICATIONS,
            )
            shadeTestUtil.setShadeExpansion(0f)

            actionCornerRepository.addState(
@@ -100,8 +127,12 @@ class ActionCornerInteractorTest : SysuiTestCase() {
        }

    @Test
    fun shadeExpanded_topLeftCornerActivated_collapseNotificationShade() =
    fun shadeExpanded_topLeftCornerActivated_notificationsActionConfigured_collapseNotificationShade() =
        kosmos.runTest {
            settingsRepository.setInt(
                ACTION_CORNER_TOP_LEFT_ACTION,
                ACTION_CORNER_ACTION_NOTIFICATIONS,
            )
            shadeTestUtil.setShadeExpansion(1f)

            actionCornerRepository.addState(
@@ -113,8 +144,12 @@ class ActionCornerInteractorTest : SysuiTestCase() {
        }

    @Test
    fun qsCollapsed_topRightCornerActivated_expandQsPanel() =
    fun qsCollapsed_topRightCornerActivated_qsActionConfigured_expandQsPanel() =
        kosmos.runTest {
            settingsRepository.setInt(
                ACTION_CORNER_TOP_RIGHT_ACTION,
                ACTION_CORNER_ACTION_QUICK_SETTINGS,
            )
            shadeTestUtil.setQsExpansion(0f)

            actionCornerRepository.addState(
@@ -126,8 +161,12 @@ class ActionCornerInteractorTest : SysuiTestCase() {
        }

    @Test
    fun qsExpanded_topRightCornerActivated_collapseQsPanel() =
    fun qsExpanded_topRightCornerActivated_qsActionConfigured_collapseQsPanel() =
        kosmos.runTest {
            settingsRepository.setInt(
                ACTION_CORNER_TOP_RIGHT_ACTION,
                ACTION_CORNER_ACTION_QUICK_SETTINGS,
            )
            shadeTestUtil.setQsExpansion(1f)

            actionCornerRepository.addState(
+2 −1
Original line number Diff line number Diff line
@@ -26,13 +26,14 @@ import com.android.systemui.cursorposition.data.repository.MultiDisplayCursorPos
import com.android.systemui.cursorposition.data.repository.SingleDisplayCursorPositionRepository
import com.android.systemui.cursorposition.data.repository.SingleDisplayCursorPositionRepositoryFactory
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.settings.UserSettingsRepositoryModule
import dagger.Binds
import dagger.Module
import dagger.Provides
import dagger.multibindings.ClassKey
import dagger.multibindings.IntoMap

@Module
@Module(includes = [UserSettingsRepositoryModule::class])
abstract class ActionCornerModule {
    @Binds
    @IntoMap
+12 −0
Original line number Diff line number Diff line
@@ -44,3 +44,15 @@ enum class ActionCornerRegion {
    BOTTOM_LEFT,
    BOTTOM_RIGHT,
}

/**
 * Indicates the action type configured for the action corner. For [NONE], it means there is no
 * action configured for that corner.
 */
enum class ActionType {
    NONE,
    HOME,
    OVERVIEW,
    NOTIFICATIONS,
    QUICK_SETTINGS,
}
+85 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2025 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.actioncorner.data.repository

import android.provider.Settings.Secure.ACTION_CORNER_ACTION_HOME
import android.provider.Settings.Secure.ACTION_CORNER_ACTION_NOTIFICATIONS
import android.provider.Settings.Secure.ACTION_CORNER_ACTION_OVERVIEW
import android.provider.Settings.Secure.ACTION_CORNER_ACTION_QUICK_SETTINGS
import android.provider.Settings.Secure.ACTION_CORNER_BOTTOM_LEFT_ACTION
import android.provider.Settings.Secure.ACTION_CORNER_BOTTOM_RIGHT_ACTION
import android.provider.Settings.Secure.ACTION_CORNER_TOP_LEFT_ACTION
import android.provider.Settings.Secure.ACTION_CORNER_TOP_RIGHT_ACTION
import android.provider.Settings.Secure.ActionCornerActionType
import com.android.systemui.actioncorner.data.model.ActionType
import com.android.systemui.actioncorner.data.model.ActionType.HOME
import com.android.systemui.actioncorner.data.model.ActionType.NONE
import com.android.systemui.actioncorner.data.model.ActionType.NOTIFICATIONS
import com.android.systemui.actioncorner.data.model.ActionType.OVERVIEW
import com.android.systemui.actioncorner.data.model.ActionType.QUICK_SETTINGS
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.util.settings.repository.UserAwareSecureSettingsRepository
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn

/**
 * Repository for action configured for each action corner.Reads corresponding settings from
 * [UserAwareSecureSettingsRepository] and convert it to action type for each corner.
 */
class ActionCornerSettingRepository
@Inject
constructor(
    private val settingsRepository: UserAwareSecureSettingsRepository,
    @Background private val backgroundScope: CoroutineScope,
    @Background private val backgroundDispatcher: CoroutineDispatcher,
) {
    val topLeftCornerAction: StateFlow<ActionType> =
        getCornerActionFlow(ACTION_CORNER_TOP_LEFT_ACTION)

    val topRightCornerAction: StateFlow<ActionType> =
        getCornerActionFlow(ACTION_CORNER_TOP_RIGHT_ACTION)

    val bottomLeftCornerAction: StateFlow<ActionType> =
        getCornerActionFlow(ACTION_CORNER_BOTTOM_LEFT_ACTION)

    val bottomRightCornerAction: StateFlow<ActionType> =
        getCornerActionFlow(ACTION_CORNER_BOTTOM_RIGHT_ACTION)

    private fun getCornerActionFlow(settingName: String): StateFlow<ActionType> {
        return settingsRepository
            .intSetting(name = settingName)
            .map(::actionMapper)
            .flowOn(backgroundDispatcher)
            // Start it eagerly to avoid latency on reading settings when user hits the corner
            .stateIn(backgroundScope, started = SharingStarted.Eagerly, initialValue = NONE)
    }

    private fun actionMapper(@ActionCornerActionType action: Int): ActionType =
        when (action) {
            ACTION_CORNER_ACTION_HOME -> HOME
            ACTION_CORNER_ACTION_OVERVIEW -> OVERVIEW
            ACTION_CORNER_ACTION_NOTIFICATIONS -> NOTIFICATIONS
            ACTION_CORNER_ACTION_QUICK_SETTINGS -> QUICK_SETTINGS
            else -> NONE
        }
}
Loading