Loading packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt +16 −4 Original line number Diff line number Diff line Loading @@ -20,6 +20,8 @@ import android.appwidget.AppWidgetHostView import android.graphics.drawable.Icon import android.os.Bundle import android.util.SizeF import android.view.View.IMPORTANT_FOR_ACCESSIBILITY_AUTO import android.view.View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS import android.widget.FrameLayout import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.core.animateFloatAsState Loading Loading @@ -115,8 +117,6 @@ import androidx.compose.ui.viewinterop.AndroidView import androidx.compose.ui.window.Popup import androidx.core.view.setPadding import androidx.window.layout.WindowMetricsCalculator import com.android.compose.modifiers.height import com.android.compose.modifiers.padding import com.android.compose.modifiers.thenIf import com.android.compose.theme.LocalAndroidColorScheme import com.android.compose.ui.graphics.painter.rememberDrawablePainter Loading Loading @@ -374,7 +374,7 @@ private fun ScrollOnUpdatedLiveContentEffect( liveContentKeys.indexOfFirst { !prevLiveContentKeys.contains(it) } // Scroll if current position is behind the first updated content if (indexOfFirstUpdatedContent in 0..<gridState.firstVisibleItemIndex) { if (indexOfFirstUpdatedContent in 0 until gridState.firstVisibleItemIndex) { // Launching with a scope to prevent the job from being canceled in the case of a // recomposition during scrolling coroutineScope.launch { gridState.animateScrollToItem(indexOfFirstUpdatedContent) } Loading Loading @@ -841,6 +841,8 @@ private fun WidgetContent( widgetConfigurator: WidgetConfigurator?, modifier: Modifier = Modifier, ) { val isFocusable by viewModel.isFocusable.collectAsState(initial = false) Box( modifier = modifier.thenIf(!viewModel.isEditMode && model.inQuietMode) { Loading @@ -865,6 +867,16 @@ private fun WidgetContent( setPadding(0) } }, update = { it.apply { importantForAccessibility = if (isFocusable) { IMPORTANT_FOR_ACCESSIBILITY_AUTO } else { IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS } } }, // For reusing composition in lazy lists. onReset = {}, ) Loading packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt +115 −0 Original line number Diff line number Diff line Loading @@ -24,17 +24,21 @@ import android.platform.test.flag.junit.FlagsParameterization import android.provider.Settings import android.widget.RemoteViews import androidx.test.filters.SmallTest import com.android.compose.animation.scene.ObservableTransitionState import com.android.systemui.Flags.FLAG_COMMUNAL_HUB import com.android.systemui.SysuiTestCase import com.android.systemui.communal.data.repository.FakeCommunalMediaRepository import com.android.systemui.communal.data.repository.FakeCommunalRepository import com.android.systemui.communal.data.repository.FakeCommunalTutorialRepository import com.android.systemui.communal.data.repository.FakeCommunalWidgetRepository import com.android.systemui.communal.data.repository.fakeCommunalMediaRepository import com.android.systemui.communal.data.repository.fakeCommunalRepository import com.android.systemui.communal.data.repository.fakeCommunalTutorialRepository import com.android.systemui.communal.data.repository.fakeCommunalWidgetRepository import com.android.systemui.communal.domain.interactor.communalInteractor import com.android.systemui.communal.domain.interactor.communalTutorialInteractor import com.android.systemui.communal.domain.model.CommunalContentModel import com.android.systemui.communal.shared.model.CommunalScenes import com.android.systemui.communal.shared.model.CommunalWidgetContentModel import com.android.systemui.communal.ui.viewmodel.CommunalViewModel import com.android.systemui.communal.ui.viewmodel.CommunalViewModel.Companion.POPUP_AUTO_HIDE_TIMEOUT_MS Loading @@ -45,8 +49,14 @@ import com.android.systemui.flags.Flags.COMMUNAL_SERVICE_ENABLED import com.android.systemui.flags.andSceneContainer import com.android.systemui.flags.fakeFeatureFlagsClassic import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor 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.log.logcatLogBuffer import com.android.systemui.media.controls.ui.controller.MediaHierarchyManager Loading @@ -63,6 +73,7 @@ import com.android.systemui.user.data.repository.fakeUserRepository import com.android.systemui.util.mockito.whenever import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.test.advanceTimeBy import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest Loading Loading @@ -94,6 +105,8 @@ class CommunalViewModelTest(flags: FlagsParameterization?) : SysuiTestCase() { private lateinit var mediaRepository: FakeCommunalMediaRepository private lateinit var userRepository: FakeUserRepository private lateinit var shadeTestUtil: ShadeTestUtil private lateinit var keyguardTransitionRepository: FakeKeyguardTransitionRepository private lateinit var communalRepository: FakeCommunalRepository private lateinit var underTest: CommunalViewModel Loading @@ -106,12 +119,14 @@ class CommunalViewModelTest(flags: FlagsParameterization?) : SysuiTestCase() { MockitoAnnotations.initMocks(this) keyguardRepository = kosmos.fakeKeyguardRepository keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository tutorialRepository = kosmos.fakeCommunalTutorialRepository widgetRepository = kosmos.fakeCommunalWidgetRepository smartspaceRepository = kosmos.fakeSmartspaceRepository mediaRepository = kosmos.fakeCommunalMediaRepository userRepository = kosmos.fakeUserRepository shadeTestUtil = kosmos.shadeTestUtil communalRepository = kosmos.fakeCommunalRepository kosmos.fakeFeatureFlagsClassic.set(COMMUNAL_SERVICE_ENABLED, true) mSetFlagsRule.enableFlags(FLAG_COMMUNAL_HUB) Loading @@ -125,6 +140,7 @@ class CommunalViewModelTest(flags: FlagsParameterization?) : SysuiTestCase() { underTest = CommunalViewModel( testScope, kosmos.keyguardTransitionInteractor, kosmos.communalInteractor, kosmos.communalTutorialInteractor, kosmos.shadeInteractor, Loading Loading @@ -326,6 +342,105 @@ class CommunalViewModelTest(flags: FlagsParameterization?) : SysuiTestCase() { assertThat(underTest.canChangeScene()).isFalse() } @Test fun isFocusable_isFalse_whenTransitioningAwayFromGlanceableHub() = testScope.runTest { val isFocusable by collectLastValue(underTest.isFocusable) // Shade not expanded. shadeTestUtil.setLockscreenShadeExpansion(0f) // On communal scene. communalRepository.setTransitionState( flowOf(ObservableTransitionState.Idle(CommunalScenes.Communal)) ) // Open bouncer. keyguardTransitionRepository.sendTransitionStep( TransitionStep( from = KeyguardState.GLANCEABLE_HUB, to = KeyguardState.PRIMARY_BOUNCER, transitionState = TransitionState.STARTED, ) ) keyguardTransitionRepository.sendTransitionStep( from = KeyguardState.GLANCEABLE_HUB, to = KeyguardState.PRIMARY_BOUNCER, transitionState = TransitionState.RUNNING, value = 0.5f, ) assertThat(isFocusable).isEqualTo(false) // Transitioned to bouncer. keyguardTransitionRepository.sendTransitionStep( from = KeyguardState.GLANCEABLE_HUB, to = KeyguardState.PRIMARY_BOUNCER, transitionState = TransitionState.FINISHED, value = 1f, ) assertThat(isFocusable).isEqualTo(false) } @Test fun isFocusable_isFalse_whenNotOnCommunalScene() = testScope.runTest { val isFocusable by collectLastValue(underTest.isFocusable) keyguardTransitionRepository.sendTransitionSteps( from = KeyguardState.LOCKSCREEN, to = KeyguardState.GLANCEABLE_HUB, testScope = testScope, ) shadeTestUtil.setLockscreenShadeExpansion(0f) // Transitioned away from communal scene. communalRepository.setTransitionState( flowOf(ObservableTransitionState.Idle(CommunalScenes.Blank)) ) assertThat(isFocusable).isEqualTo(false) } @Test fun isFocusable_isTrue_whenIdleOnCommunal_andShadeNotExpanded() = testScope.runTest { val isFocusable by collectLastValue(underTest.isFocusable) // On communal scene. communalRepository.setTransitionState( flowOf(ObservableTransitionState.Idle(CommunalScenes.Communal)) ) // Transitioned to Glanceable hub. keyguardTransitionRepository.sendTransitionSteps( from = KeyguardState.LOCKSCREEN, to = KeyguardState.GLANCEABLE_HUB, testScope = testScope, ) // Shade not expanded. shadeTestUtil.setLockscreenShadeExpansion(0f) assertThat(isFocusable).isEqualTo(true) } @Test fun isFocusable_isFalse_whenQsIsExpanded() = testScope.runTest { val isFocusable by collectLastValue(underTest.isFocusable) // On communal scene. communalRepository.setTransitionState( flowOf(ObservableTransitionState.Idle(CommunalScenes.Communal)) ) // Transitioned to Glanceable hub. keyguardTransitionRepository.sendTransitionSteps( from = KeyguardState.LOCKSCREEN, to = KeyguardState.GLANCEABLE_HUB, testScope = testScope, ) // Qs is expanded. shadeTestUtil.setQsExpansion(1f) assertThat(isFocusable).isEqualTo(false) } private suspend fun setIsMainUser(isMainUser: Boolean) { whenever(user.isMain).thenReturn(isMainUser) userRepository.setUserInfos(listOf(user)) Loading packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt +2 −2 Original line number Diff line number Diff line Loading @@ -37,8 +37,8 @@ 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 communal hub can be focused by accessibility tools. */ open val isFocusable: Flow<Boolean> = MutableStateFlow(false) /** Whether widgets are currently being re-ordered. */ open val reorderingWidgets: StateFlow<Boolean> = MutableStateFlow(false) Loading packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt +15 −0 Original line number Diff line number Diff line Loading @@ -22,6 +22,8 @@ import com.android.systemui.communal.domain.model.CommunalContentModel import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.log.LogBuffer import com.android.systemui.log.core.Logger import com.android.systemui.log.dagger.CommunalLog Loading Loading @@ -54,6 +56,7 @@ class CommunalViewModel @Inject constructor( @Application private val scope: CoroutineScope, keyguardTransitionInteractor: KeyguardTransitionInteractor, private val communalInteractor: CommunalInteractor, tutorialInteractor: CommunalTutorialInteractor, private val shadeInteractor: ShadeInteractor, Loading Loading @@ -93,6 +96,18 @@ constructor( private val _currentPopup: MutableStateFlow<PopupType?> = MutableStateFlow(null) override val currentPopup: Flow<PopupType?> = _currentPopup.asStateFlow() // The widget is focusable for accessibility when the hub is fully visible and shade is not // opened. override val isFocusable: Flow<Boolean> = combine( keyguardTransitionInteractor.isFinishedInState(KeyguardState.GLANCEABLE_HUB), communalInteractor.isIdleOnCommunal, shadeInteractor.isAnyFullyExpanded, ) { transitionedToGlanceableHub, isIdleOnCommunal, isAnyFullyExpanded -> transitionedToGlanceableHub && isIdleOnCommunal && !isAnyFullyExpanded } .distinctUntilChanged() private val _isEnableWidgetDialogShowing: MutableStateFlow<Boolean> = MutableStateFlow(false) val isEnableWidgetDialogShowing: Flow<Boolean> = _isEnableWidgetDialogShowing.asStateFlow() Loading packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostView.kt +21 −0 Original line number Diff line number Diff line Loading @@ -22,8 +22,10 @@ import android.graphics.Outline import android.graphics.Rect import android.view.View import android.view.ViewOutlineProvider import android.view.accessibility.AccessibilityNodeInfo import com.android.systemui.animation.LaunchableView import com.android.systemui.animation.LaunchableViewDelegate import com.android.systemui.res.R /** AppWidgetHostView that displays in communal hub with support for rounded corners. */ class CommunalAppWidgetHostView(context: Context) : AppWidgetHostView(context), LaunchableView { Loading @@ -42,6 +44,25 @@ class CommunalAppWidgetHostView(context: Context) : AppWidgetHostView(context), init { enforcedCornerRadius = RoundedCornerEnforcement.computeEnforcedRadius(context) enforcedRectangle = Rect() accessibilityDelegate = object : AccessibilityDelegate() { override fun onInitializeAccessibilityNodeInfo( host: View, info: AccessibilityNodeInfo ) { super.onInitializeAccessibilityNodeInfo(host, info) // Hint user to long press in order to enter edit mode info.addAction( AccessibilityNodeInfo.AccessibilityAction( AccessibilityNodeInfo.AccessibilityAction.ACTION_LONG_CLICK.id, context.getString( R.string.accessibility_action_label_edit_widgets ).lowercase() ) ) } } } override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) { Loading Loading
packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt +16 −4 Original line number Diff line number Diff line Loading @@ -20,6 +20,8 @@ import android.appwidget.AppWidgetHostView import android.graphics.drawable.Icon import android.os.Bundle import android.util.SizeF import android.view.View.IMPORTANT_FOR_ACCESSIBILITY_AUTO import android.view.View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS import android.widget.FrameLayout import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.core.animateFloatAsState Loading Loading @@ -115,8 +117,6 @@ import androidx.compose.ui.viewinterop.AndroidView import androidx.compose.ui.window.Popup import androidx.core.view.setPadding import androidx.window.layout.WindowMetricsCalculator import com.android.compose.modifiers.height import com.android.compose.modifiers.padding import com.android.compose.modifiers.thenIf import com.android.compose.theme.LocalAndroidColorScheme import com.android.compose.ui.graphics.painter.rememberDrawablePainter Loading Loading @@ -374,7 +374,7 @@ private fun ScrollOnUpdatedLiveContentEffect( liveContentKeys.indexOfFirst { !prevLiveContentKeys.contains(it) } // Scroll if current position is behind the first updated content if (indexOfFirstUpdatedContent in 0..<gridState.firstVisibleItemIndex) { if (indexOfFirstUpdatedContent in 0 until gridState.firstVisibleItemIndex) { // Launching with a scope to prevent the job from being canceled in the case of a // recomposition during scrolling coroutineScope.launch { gridState.animateScrollToItem(indexOfFirstUpdatedContent) } Loading Loading @@ -841,6 +841,8 @@ private fun WidgetContent( widgetConfigurator: WidgetConfigurator?, modifier: Modifier = Modifier, ) { val isFocusable by viewModel.isFocusable.collectAsState(initial = false) Box( modifier = modifier.thenIf(!viewModel.isEditMode && model.inQuietMode) { Loading @@ -865,6 +867,16 @@ private fun WidgetContent( setPadding(0) } }, update = { it.apply { importantForAccessibility = if (isFocusable) { IMPORTANT_FOR_ACCESSIBILITY_AUTO } else { IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS } } }, // For reusing composition in lazy lists. onReset = {}, ) Loading
packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt +115 −0 Original line number Diff line number Diff line Loading @@ -24,17 +24,21 @@ import android.platform.test.flag.junit.FlagsParameterization import android.provider.Settings import android.widget.RemoteViews import androidx.test.filters.SmallTest import com.android.compose.animation.scene.ObservableTransitionState import com.android.systemui.Flags.FLAG_COMMUNAL_HUB import com.android.systemui.SysuiTestCase import com.android.systemui.communal.data.repository.FakeCommunalMediaRepository import com.android.systemui.communal.data.repository.FakeCommunalRepository import com.android.systemui.communal.data.repository.FakeCommunalTutorialRepository import com.android.systemui.communal.data.repository.FakeCommunalWidgetRepository import com.android.systemui.communal.data.repository.fakeCommunalMediaRepository import com.android.systemui.communal.data.repository.fakeCommunalRepository import com.android.systemui.communal.data.repository.fakeCommunalTutorialRepository import com.android.systemui.communal.data.repository.fakeCommunalWidgetRepository import com.android.systemui.communal.domain.interactor.communalInteractor import com.android.systemui.communal.domain.interactor.communalTutorialInteractor import com.android.systemui.communal.domain.model.CommunalContentModel import com.android.systemui.communal.shared.model.CommunalScenes import com.android.systemui.communal.shared.model.CommunalWidgetContentModel import com.android.systemui.communal.ui.viewmodel.CommunalViewModel import com.android.systemui.communal.ui.viewmodel.CommunalViewModel.Companion.POPUP_AUTO_HIDE_TIMEOUT_MS Loading @@ -45,8 +49,14 @@ import com.android.systemui.flags.Flags.COMMUNAL_SERVICE_ENABLED import com.android.systemui.flags.andSceneContainer import com.android.systemui.flags.fakeFeatureFlagsClassic import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor 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.log.logcatLogBuffer import com.android.systemui.media.controls.ui.controller.MediaHierarchyManager Loading @@ -63,6 +73,7 @@ import com.android.systemui.user.data.repository.fakeUserRepository import com.android.systemui.util.mockito.whenever import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.test.advanceTimeBy import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest Loading Loading @@ -94,6 +105,8 @@ class CommunalViewModelTest(flags: FlagsParameterization?) : SysuiTestCase() { private lateinit var mediaRepository: FakeCommunalMediaRepository private lateinit var userRepository: FakeUserRepository private lateinit var shadeTestUtil: ShadeTestUtil private lateinit var keyguardTransitionRepository: FakeKeyguardTransitionRepository private lateinit var communalRepository: FakeCommunalRepository private lateinit var underTest: CommunalViewModel Loading @@ -106,12 +119,14 @@ class CommunalViewModelTest(flags: FlagsParameterization?) : SysuiTestCase() { MockitoAnnotations.initMocks(this) keyguardRepository = kosmos.fakeKeyguardRepository keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository tutorialRepository = kosmos.fakeCommunalTutorialRepository widgetRepository = kosmos.fakeCommunalWidgetRepository smartspaceRepository = kosmos.fakeSmartspaceRepository mediaRepository = kosmos.fakeCommunalMediaRepository userRepository = kosmos.fakeUserRepository shadeTestUtil = kosmos.shadeTestUtil communalRepository = kosmos.fakeCommunalRepository kosmos.fakeFeatureFlagsClassic.set(COMMUNAL_SERVICE_ENABLED, true) mSetFlagsRule.enableFlags(FLAG_COMMUNAL_HUB) Loading @@ -125,6 +140,7 @@ class CommunalViewModelTest(flags: FlagsParameterization?) : SysuiTestCase() { underTest = CommunalViewModel( testScope, kosmos.keyguardTransitionInteractor, kosmos.communalInteractor, kosmos.communalTutorialInteractor, kosmos.shadeInteractor, Loading Loading @@ -326,6 +342,105 @@ class CommunalViewModelTest(flags: FlagsParameterization?) : SysuiTestCase() { assertThat(underTest.canChangeScene()).isFalse() } @Test fun isFocusable_isFalse_whenTransitioningAwayFromGlanceableHub() = testScope.runTest { val isFocusable by collectLastValue(underTest.isFocusable) // Shade not expanded. shadeTestUtil.setLockscreenShadeExpansion(0f) // On communal scene. communalRepository.setTransitionState( flowOf(ObservableTransitionState.Idle(CommunalScenes.Communal)) ) // Open bouncer. keyguardTransitionRepository.sendTransitionStep( TransitionStep( from = KeyguardState.GLANCEABLE_HUB, to = KeyguardState.PRIMARY_BOUNCER, transitionState = TransitionState.STARTED, ) ) keyguardTransitionRepository.sendTransitionStep( from = KeyguardState.GLANCEABLE_HUB, to = KeyguardState.PRIMARY_BOUNCER, transitionState = TransitionState.RUNNING, value = 0.5f, ) assertThat(isFocusable).isEqualTo(false) // Transitioned to bouncer. keyguardTransitionRepository.sendTransitionStep( from = KeyguardState.GLANCEABLE_HUB, to = KeyguardState.PRIMARY_BOUNCER, transitionState = TransitionState.FINISHED, value = 1f, ) assertThat(isFocusable).isEqualTo(false) } @Test fun isFocusable_isFalse_whenNotOnCommunalScene() = testScope.runTest { val isFocusable by collectLastValue(underTest.isFocusable) keyguardTransitionRepository.sendTransitionSteps( from = KeyguardState.LOCKSCREEN, to = KeyguardState.GLANCEABLE_HUB, testScope = testScope, ) shadeTestUtil.setLockscreenShadeExpansion(0f) // Transitioned away from communal scene. communalRepository.setTransitionState( flowOf(ObservableTransitionState.Idle(CommunalScenes.Blank)) ) assertThat(isFocusable).isEqualTo(false) } @Test fun isFocusable_isTrue_whenIdleOnCommunal_andShadeNotExpanded() = testScope.runTest { val isFocusable by collectLastValue(underTest.isFocusable) // On communal scene. communalRepository.setTransitionState( flowOf(ObservableTransitionState.Idle(CommunalScenes.Communal)) ) // Transitioned to Glanceable hub. keyguardTransitionRepository.sendTransitionSteps( from = KeyguardState.LOCKSCREEN, to = KeyguardState.GLANCEABLE_HUB, testScope = testScope, ) // Shade not expanded. shadeTestUtil.setLockscreenShadeExpansion(0f) assertThat(isFocusable).isEqualTo(true) } @Test fun isFocusable_isFalse_whenQsIsExpanded() = testScope.runTest { val isFocusable by collectLastValue(underTest.isFocusable) // On communal scene. communalRepository.setTransitionState( flowOf(ObservableTransitionState.Idle(CommunalScenes.Communal)) ) // Transitioned to Glanceable hub. keyguardTransitionRepository.sendTransitionSteps( from = KeyguardState.LOCKSCREEN, to = KeyguardState.GLANCEABLE_HUB, testScope = testScope, ) // Qs is expanded. shadeTestUtil.setQsExpansion(1f) assertThat(isFocusable).isEqualTo(false) } private suspend fun setIsMainUser(isMainUser: Boolean) { whenever(user.isMain).thenReturn(isMainUser) userRepository.setUserInfos(listOf(user)) Loading
packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt +2 −2 Original line number Diff line number Diff line Loading @@ -37,8 +37,8 @@ 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 communal hub can be focused by accessibility tools. */ open val isFocusable: Flow<Boolean> = MutableStateFlow(false) /** Whether widgets are currently being re-ordered. */ open val reorderingWidgets: StateFlow<Boolean> = MutableStateFlow(false) Loading
packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt +15 −0 Original line number Diff line number Diff line Loading @@ -22,6 +22,8 @@ import com.android.systemui.communal.domain.model.CommunalContentModel import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.log.LogBuffer import com.android.systemui.log.core.Logger import com.android.systemui.log.dagger.CommunalLog Loading Loading @@ -54,6 +56,7 @@ class CommunalViewModel @Inject constructor( @Application private val scope: CoroutineScope, keyguardTransitionInteractor: KeyguardTransitionInteractor, private val communalInteractor: CommunalInteractor, tutorialInteractor: CommunalTutorialInteractor, private val shadeInteractor: ShadeInteractor, Loading Loading @@ -93,6 +96,18 @@ constructor( private val _currentPopup: MutableStateFlow<PopupType?> = MutableStateFlow(null) override val currentPopup: Flow<PopupType?> = _currentPopup.asStateFlow() // The widget is focusable for accessibility when the hub is fully visible and shade is not // opened. override val isFocusable: Flow<Boolean> = combine( keyguardTransitionInteractor.isFinishedInState(KeyguardState.GLANCEABLE_HUB), communalInteractor.isIdleOnCommunal, shadeInteractor.isAnyFullyExpanded, ) { transitionedToGlanceableHub, isIdleOnCommunal, isAnyFullyExpanded -> transitionedToGlanceableHub && isIdleOnCommunal && !isAnyFullyExpanded } .distinctUntilChanged() private val _isEnableWidgetDialogShowing: MutableStateFlow<Boolean> = MutableStateFlow(false) val isEnableWidgetDialogShowing: Flow<Boolean> = _isEnableWidgetDialogShowing.asStateFlow() Loading
packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostView.kt +21 −0 Original line number Diff line number Diff line Loading @@ -22,8 +22,10 @@ import android.graphics.Outline import android.graphics.Rect import android.view.View import android.view.ViewOutlineProvider import android.view.accessibility.AccessibilityNodeInfo import com.android.systemui.animation.LaunchableView import com.android.systemui.animation.LaunchableViewDelegate import com.android.systemui.res.R /** AppWidgetHostView that displays in communal hub with support for rounded corners. */ class CommunalAppWidgetHostView(context: Context) : AppWidgetHostView(context), LaunchableView { Loading @@ -42,6 +44,25 @@ class CommunalAppWidgetHostView(context: Context) : AppWidgetHostView(context), init { enforcedCornerRadius = RoundedCornerEnforcement.computeEnforcedRadius(context) enforcedRectangle = Rect() accessibilityDelegate = object : AccessibilityDelegate() { override fun onInitializeAccessibilityNodeInfo( host: View, info: AccessibilityNodeInfo ) { super.onInitializeAccessibilityNodeInfo(host, info) // Hint user to long press in order to enter edit mode info.addAction( AccessibilityNodeInfo.AccessibilityAction( AccessibilityNodeInfo.AccessibilityAction.ACTION_LONG_CLICK.id, context.getString( R.string.accessibility_action_label_edit_widgets ).lowercase() ) ) } } } override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) { Loading