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

Commit 49f96e27 authored by Coco Duan's avatar Coco Duan Committed by Android (Google) Code Review
Browse files

Merge changes I4bdb5ccb,Ie1b957e0 into main

* changes:
  Add accessibility action to navigate to keyguard from glanceable hub
  Add custom accessbility action to open glanceable hub
parents 023bac15 dee191cd
Loading
Loading
Loading
Loading
+67 −26
Original line number Diff line number Diff line
@@ -32,6 +32,7 @@ import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.focusable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.BoxScope
@@ -45,6 +46,7 @@ import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.GridItemSpan
import androidx.compose.foundation.lazy.grid.LazyGridState
@@ -101,6 +103,9 @@ import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.dimensionResource
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.CustomAccessibilityAction
import androidx.compose.ui.semantics.contentDescription
import androidx.compose.ui.semantics.customActions
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.semantics.testTagsAsResourceId
import androidx.compose.ui.text.style.TextAlign
@@ -119,6 +124,7 @@ import com.android.compose.ui.graphics.painter.rememberDrawablePainter
import com.android.internal.R.dimen.system_app_widget_background_radius
import com.android.systemui.communal.domain.model.CommunalContentModel
import com.android.systemui.communal.shared.model.CommunalContentSize
import com.android.systemui.communal.shared.model.CommunalScenes
import com.android.systemui.communal.ui.compose.Dimensions.CardOutlineWidth
import com.android.systemui.communal.ui.compose.extensions.allowGestures
import com.android.systemui.communal.ui.compose.extensions.detectLongPressGesture
@@ -223,6 +229,7 @@ fun CommunalHub(
                        .motionEventSpy { onMotionEvent(viewModel) }
                },
    ) {
        AccessibilityContainer(viewModel) {
            if (!viewModel.isEditMode && isEmptyState) {
                EmptyStateCta(
                    contentPadding = contentPadding,
@@ -250,6 +257,7 @@ fun CommunalHub(
                    widgetConfigurator = widgetConfigurator,
                )
            }
        }

        // TODO(b/326060686): Remove this once keyguard indication area can persist over hub
        if (viewModel is CommunalViewModel) {
@@ -1028,6 +1036,39 @@ private fun Umo(viewModel: BaseCommunalViewModel, modifier: Modifier = Modifier)
    )
}

/** Container of the glanceable hub grid to enable accessibility actions when focused. */
@Composable
fun AccessibilityContainer(viewModel: BaseCommunalViewModel, content: @Composable () -> Unit) {
    val context = LocalContext.current
    val isFocusable by viewModel.isFocusable.collectAsState(initial = false)
    Box(
        modifier =
            Modifier.fillMaxWidth().wrapContentHeight().thenIf(
                isFocusable && !viewModel.isEditMode
            ) {
                Modifier.focusable(isFocusable).semantics {
                    contentDescription =
                        context.getString(
                            R.string.accessibility_content_description_for_communal_hub
                        )
                    customActions =
                        listOf(
                            CustomAccessibilityAction(
                                context.getString(
                                    R.string.accessibility_action_label_close_communal_hub
                                )
                            ) {
                                viewModel.changeScene(CommunalScenes.Blank)
                                true
                            }
                        )
                }
            }
    ) {
        content()
    }
}

/**
 * Returns the `contentPadding` of the grid. Use the vertical padding to push the grid content area
 * below the toolbar and let the grid take the max size. This ensures the item can be dragged
+154 −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.
 */

@file:OptIn(ExperimentalCoroutinesApi::class)

package com.android.systemui.keyguard.ui.viewmodel

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.keyguard.data.repository.fakeKeyguardRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.StatusBarState
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep
import com.android.systemui.kosmos.testScope
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith

@SmallTest
@RunWith(AndroidJUnit4::class)
class AccessibilityActionsViewModelTest : SysuiTestCase() {

    private val kosmos = testKosmos()
    private val testScope = kosmos.testScope
    private val keyguardRepository = kosmos.fakeKeyguardRepository
    private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository

    private lateinit var underTest: AccessibilityActionsViewModel

    @Before
    fun setUp() {
        underTest = kosmos.accessibilityActionsViewModelKosmos
    }

    @Test
    fun isOnKeyguard_isFalse_whenTransitioningAwayFromLockscreen() =
        testScope.runTest {
            val isOnKeyguard by collectLastValue(underTest.isOnKeyguard)

            // Shade not opened.
            keyguardRepository.setStatusBarState(StatusBarState.KEYGUARD)
            // Transitioning away from lock screen.
            keyguardTransitionRepository.sendTransitionStep(
                TransitionStep(
                    from = KeyguardState.LOCKSCREEN,
                    to = KeyguardState.PRIMARY_BOUNCER,
                    transitionState = TransitionState.STARTED,
                )
            )

            keyguardTransitionRepository.sendTransitionStep(
                from = KeyguardState.LOCKSCREEN,
                to = KeyguardState.PRIMARY_BOUNCER,
                transitionState = TransitionState.RUNNING,
                value = 0.5f,
            )
            assertThat(isOnKeyguard).isEqualTo(false)

            // Transitioned to bouncer.
            keyguardTransitionRepository.sendTransitionStep(
                from = KeyguardState.LOCKSCREEN,
                to = KeyguardState.PRIMARY_BOUNCER,
                transitionState = TransitionState.FINISHED,
                value = 1f,
            )
            assertThat(isOnKeyguard).isEqualTo(false)
        }

    @Test
    fun isOnKeyguard_isFalse_whenTransitioningToLockscreenIsRunning() =
        testScope.runTest {
            val isOnKeyguard by collectLastValue(underTest.isOnKeyguard)

            // Shade is not opened.
            keyguardRepository.setStatusBarState(StatusBarState.KEYGUARD)
            // Starts transitioning to lock screen.
            keyguardTransitionRepository.sendTransitionStep(
                TransitionStep(
                    from = KeyguardState.GLANCEABLE_HUB,
                    to = KeyguardState.LOCKSCREEN,
                    transitionState = TransitionState.STARTED,
                )
            )
            assertThat(isOnKeyguard).isEqualTo(false)

            keyguardTransitionRepository.sendTransitionStep(
                from = KeyguardState.GLANCEABLE_HUB,
                to = KeyguardState.LOCKSCREEN,
                transitionState = TransitionState.RUNNING,
                value = 0.5f,
            )
            assertThat(isOnKeyguard).isEqualTo(false)

            // Transition has finished.
            keyguardTransitionRepository.sendTransitionStep(
                from = KeyguardState.GLANCEABLE_HUB,
                to = KeyguardState.LOCKSCREEN,
                transitionState = TransitionState.FINISHED,
                value = 1f,
            )
            assertThat(isOnKeyguard).isEqualTo(true)
        }

    @Test
    fun isOnKeyguard_isTrue_whenKeyguardStateIsLockscreen_andShadeIsNotOpened() =
        testScope.runTest {
            val isOnKeyguard by collectLastValue(underTest.isOnKeyguard)

            keyguardTransitionRepository.sendTransitionSteps(
                from = KeyguardState.GONE,
                to = KeyguardState.LOCKSCREEN,
                testScope = testScope,
            )
            keyguardRepository.setStatusBarState(StatusBarState.KEYGUARD)

            assertThat(isOnKeyguard).isEqualTo(true)
        }

    @Test
    fun isOnKeyguard_isFalse_whenKeyguardStateIsLockscreen_andShadeOpened() =
        testScope.runTest {
            val isOnKeyguard by collectLastValue(underTest.isOnKeyguard)

            keyguardTransitionRepository.sendTransitionSteps(
                from = KeyguardState.GONE,
                to = KeyguardState.LOCKSCREEN,
                testScope = testScope,
            )
            keyguardRepository.setStatusBarState(StatusBarState.SHADE_LOCKED)

            assertThat(isOnKeyguard).isEqualTo(false)
        }
}
+3 −0
Original line number Diff line number Diff line
@@ -186,6 +186,8 @@
    <item type="id" name="action_remove_menu"/>
    <item type="id" name="action_edit"/>

    <item type="id" name="accessibility_action_open_communal_hub"/>

    <!-- rounded corner view id -->
    <item type="id" name="rounded_corner_top_left"/>
    <item type="id" name="rounded_corner_top_right"/>
@@ -231,6 +233,7 @@
    <item type="id" name="smart_space_barrier_bottom" />
    <item type="id" name="small_clock_guideline_top" />
    <item type="id" name="weather_clock_date_and_icons_barrier_bottom" />
    <item type="id" name="accessibility_actions_view" />

    <!-- Privacy dialog -->
    <item type="id" name="privacy_dialog_close_app_button" />
+8 −1
Original line number Diff line number Diff line
@@ -568,7 +568,7 @@
    <!-- Content description for the split notification shade that also includes QS (not shown on the screen). [CHAR LIMIT=NONE] -->
    <string name="accessibility_desc_qs_notification_shade">Quick settings and Notification shade.</string>
    <!-- Content description for the lock screen (not shown on the screen). [CHAR LIMIT=NONE] -->
    <string name="accessibility_desc_lock_screen">Lock screen.</string>
    <string name="accessibility_desc_lock_screen">Lock screen</string>
    <!-- Content description for the work profile lock screen. This prevents work profile apps from being used, but personal apps can be used as normal (not shown on the screen). [CHAR LIMIT=NONE] -->
    <string name="accessibility_desc_work_lock">Work lock screen</string>
    <!-- Content description for the close button in the zen mode panel introduction message. [CHAR LIMIT=NONE] -->
@@ -1134,6 +1134,9 @@
    <!-- Indication on the keyguard that is shown when the device is dock charging. [CHAR LIMIT=80]-->
    <string name="keyguard_indication_charging_time_dock"><xliff:g id="percentage" example="20%">%2$s</xliff:g> • Charging • Full in <xliff:g id="charging_time_left" example="4 hr, 2 min">%1$s</xliff:g></string>

    <!-- Label for accessibility action that shows widgets on lock screen on click. [CHAR LIMIT=NONE] -->
    <string name="accessibility_action_open_communal_hub">Widgets on lock screen</string>

    <!-- Indicator on keyguard to start the communal tutorial. [CHAR LIMIT=100] -->
    <string name="communal_tutorial_indicator_text">Swipe left to start the communal tutorial</string>

@@ -1171,6 +1174,10 @@
    <string name="work_mode_off_title">Unpause work apps?</string>
    <!-- Title for button to unpause on work profile. [CHAR LIMIT=NONE] -->
    <string name="work_mode_turn_on">Unpause</string>
    <!-- Label for accessibility action that navigates to lock screen. [CHAR LIMIT=NONE] -->
    <string name="accessibility_action_label_close_communal_hub">Close widgets on lock screen</string>
    <!-- Accessibility content description for communal hub. [CHAR LIMIT=NONE] -->
    <string name="accessibility_content_description_for_communal_hub">Widgets on lock screen</string>

    <!-- Related to user switcher --><skip/>

+3 −0
Original line number Diff line number Diff line
@@ -36,6 +36,9 @@ abstract class BaseCommunalViewModel(
) {
    val currentScene: Flow<SceneKey> = communalInteractor.desiredScene

    /** Whether communal hub can be focused to enable accessibility actions. */
    val isFocusable: Flow<Boolean> = communalInteractor.isIdleOnCommunal

    /** Whether widgets are currently being re-ordered. */
    open val reorderingWidgets: StateFlow<Boolean> = MutableStateFlow(false)

Loading