summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt28
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt48
-rw-r--r--packages/SystemUI/res/values/strings.xml3
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt28
5 files changed, 101 insertions, 10 deletions
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
index e29e0fdc145d..b759cf7d82c5 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
@@ -221,7 +221,7 @@ fun CommunalHub(
val layoutDirection = LocalLayoutDirection.current
if (viewModel.isEditMode) {
- ScrollOnNewWidgetAddedEffect(communalContent, gridState)
+ ObserveNewWidgetAddedEffect(communalContent, gridState, viewModel)
} else {
ScrollOnUpdatedLiveContentEffect(communalContent, gridState)
}
@@ -553,19 +553,37 @@ private fun ScrollOnUpdatedLiveContentEffect(
}
}
-/** Observes communal content and scrolls to a newly added widget if any. */
+/**
+ * Observes communal content and determines whether a new widget has been added, upon which case:
+ * - Announce for accessibility
+ * - Scroll if the new widget is not visible
+ */
@Composable
-private fun ScrollOnNewWidgetAddedEffect(
+private fun ObserveNewWidgetAddedEffect(
communalContent: List<CommunalContentModel>,
gridState: LazyGridState,
+ viewModel: BaseCommunalViewModel,
) {
val coroutineScope = rememberCoroutineScope()
val widgetKeys = remember { mutableListOf<String>() }
+ var communalContentPending by remember { mutableStateOf(true) }
LaunchedEffect(communalContent) {
+ // Do nothing until any communal content comes in
+ if (communalContentPending && communalContent.isEmpty()) {
+ return@LaunchedEffect
+ }
+
val oldWidgetKeys = widgetKeys.toList()
+ val widgets = communalContent.filterIsInstance<CommunalContentModel.WidgetContent.Widget>()
widgetKeys.clear()
- widgetKeys.addAll(communalContent.filter { it.isWidgetContent() }.map { it.key })
+ widgetKeys.addAll(widgets.map { it.key })
+
+ // Do nothing on first communal content since we don't have a delta
+ if (communalContentPending) {
+ communalContentPending = false
+ return@LaunchedEffect
+ }
// Do nothing if there is no new widget
val indexOfFirstNewWidget = widgetKeys.indexOfFirst { !oldWidgetKeys.contains(it) }
@@ -573,6 +591,8 @@ private fun ScrollOnNewWidgetAddedEffect(
return@LaunchedEffect
}
+ viewModel.onNewWidgetAdded(widgets[indexOfFirstNewWidget].providerInfo)
+
// Scroll if the new widget is not visible
val lastVisibleItemIndex = gridState.layoutInfo.visibleItemsInfo.lastOrNull()?.index
if (lastVisibleItemIndex != null && indexOfFirstNewWidget > lastVisibleItemIndex) {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt
index 61487b0a8c73..57ce9de2d057 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt
@@ -16,6 +16,7 @@
package com.android.systemui.communal.view.viewmodel
+import android.appwidget.AppWidgetProviderInfo
import android.content.ActivityNotFoundException
import android.content.ComponentName
import android.content.Intent
@@ -24,6 +25,9 @@ import android.content.pm.PackageManager
import android.content.pm.ResolveInfo
import android.content.pm.UserInfo
import android.provider.Settings
+import android.view.accessibility.AccessibilityEvent
+import android.view.accessibility.AccessibilityManager
+import android.view.accessibility.accessibilityManager
import android.widget.RemoteViews
import androidx.activity.result.ActivityResultLauncher
import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -42,7 +46,6 @@ import com.android.systemui.communal.data.repository.fakeCommunalWidgetRepositor
import com.android.systemui.communal.domain.interactor.CommunalInteractor
import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor
import com.android.systemui.communal.domain.interactor.communalInteractor
-import com.android.systemui.communal.domain.interactor.communalPrefsInteractor
import com.android.systemui.communal.domain.interactor.communalSceneInteractor
import com.android.systemui.communal.domain.interactor.communalSettingsInteractor
import com.android.systemui.communal.domain.model.CommunalContentModel
@@ -61,8 +64,6 @@ import com.android.systemui.media.controls.ui.view.MediaHost
import com.android.systemui.settings.fakeUserTracker
import com.android.systemui.testKosmos
import com.android.systemui.user.data.repository.fakeUserRepository
-import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.test.advanceTimeBy
import kotlinx.coroutines.test.runTest
@@ -77,8 +78,12 @@ import org.mockito.Mockito
import org.mockito.Mockito.never
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.any
+import org.mockito.kotlin.argumentCaptor
import org.mockito.kotlin.eq
+import org.mockito.kotlin.mock
import org.mockito.kotlin.spy
+import org.mockito.kotlin.whenever
@SmallTest
@RunWith(AndroidJUnit4::class)
@@ -98,6 +103,7 @@ class CommunalEditModeViewModelTest : SysuiTestCase() {
private lateinit var mediaRepository: FakeCommunalMediaRepository
private lateinit var communalSceneInteractor: CommunalSceneInteractor
private lateinit var communalInteractor: CommunalInteractor
+ private lateinit var accessibilityManager: AccessibilityManager
private val testableResources = context.orCreateTestableResources
@@ -119,6 +125,7 @@ class CommunalEditModeViewModelTest : SysuiTestCase() {
selectedUserIndex = 0,
)
kosmos.fakeFeatureFlagsClassic.set(Flags.COMMUNAL_SERVICE_ENABLED, true)
+ accessibilityManager = kosmos.accessibilityManager
underTest =
CommunalEditModeViewModel(
@@ -130,8 +137,10 @@ class CommunalEditModeViewModelTest : SysuiTestCase() {
uiEventLogger,
logcatLogBuffer("CommunalEditModeViewModelTest"),
kosmos.testDispatcher,
- kosmos.communalPrefsInteractor,
metricsLogger,
+ context,
+ accessibilityManager,
+ packageManager,
)
}
@@ -356,6 +365,37 @@ class CommunalEditModeViewModelTest : SysuiTestCase() {
verify(communalInteractor).setScrollPosition(eq(index), eq(offset))
}
+ @Test
+ fun onNewWidgetAdded_accessibilityDisabled_doNothing() {
+ whenever(accessibilityManager.isEnabled).thenReturn(false)
+
+ val provider =
+ mock<AppWidgetProviderInfo> {
+ on { loadLabel(packageManager) }.thenReturn("Test Clock")
+ }
+ underTest.onNewWidgetAdded(provider)
+
+ verify(accessibilityManager, never()).sendAccessibilityEvent(any())
+ }
+
+ @Test
+ fun onNewWidgetAdded_accessibilityEnabled_sendAccessibilityAnnouncement() {
+ whenever(accessibilityManager.isEnabled).thenReturn(true)
+
+ val provider =
+ mock<AppWidgetProviderInfo> {
+ on { loadLabel(packageManager) }.thenReturn("Test Clock")
+ }
+ underTest.onNewWidgetAdded(provider)
+
+ val captor = argumentCaptor<AccessibilityEvent>()
+ verify(accessibilityManager).sendAccessibilityEvent(captor.capture())
+
+ val event = captor.firstValue
+ assertThat(event.eventType).isEqualTo(AccessibilityEvent.TYPE_ANNOUNCEMENT)
+ assertThat(event.contentDescription).isEqualTo("Test Clock widget added to lock screen")
+ }
+
private companion object {
val MAIN_USER_INFO = UserInfo(0, "primary", UserInfo.FLAG_MAIN)
const val WIDGET_PICKER_PACKAGE_NAME = "widget_picker_package_name"
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 089db2df9fbd..d7c3527bf9a2 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1225,6 +1225,9 @@
<!-- 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>
+ <!-- Label for an accessibility announcement when a widget has been added to the lock screen. [CHAR LIMIT=NONE] -->
+ <string name="accessibility_announcement_communal_widget_added"><xliff:g id="widget_name" example="Calendar month view">%1$s</xliff:g> widget added to 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>
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
index 4be93ccde1d5..d1a5a4b8641f 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
@@ -16,6 +16,7 @@
package com.android.systemui.communal.ui.viewmodel
+import android.appwidget.AppWidgetProviderInfo
import android.content.ComponentName
import android.os.UserHandle
import android.view.View
@@ -197,6 +198,9 @@ abstract class BaseCommunalViewModel(
/** Called as the user request to show the customize widget button. */
open fun onLongClick() {}
+ /** Called as the UI determines that a new widget has been added to the grid. */
+ open fun onNewWidgetAdded(provider: AppWidgetProviderInfo) {}
+
/** Called when the grid scroll position has been updated. */
open fun onScrollPositionUpdated(firstVisibleItemIndex: Int, firstVisibleItemScroll: Int) {
currentScrollIndex = firstVisibleItemIndex
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
index 5b825d80d9b1..1a86c717b962 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
@@ -19,16 +19,18 @@ package com.android.systemui.communal.ui.viewmodel
import android.appwidget.AppWidgetManager
import android.appwidget.AppWidgetProviderInfo
import android.content.ComponentName
+import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.content.res.Resources
import android.os.UserHandle
import android.util.Log
+import android.view.accessibility.AccessibilityEvent
+import android.view.accessibility.AccessibilityManager
import androidx.activity.result.ActivityResultLauncher
import com.android.internal.logging.UiEventLogger
import com.android.systemui.communal.data.model.CommunalWidgetCategories
import com.android.systemui.communal.domain.interactor.CommunalInteractor
-import com.android.systemui.communal.domain.interactor.CommunalPrefsInteractor
import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor
import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor
import com.android.systemui.communal.domain.model.CommunalContentModel
@@ -37,6 +39,7 @@ import com.android.systemui.communal.shared.log.CommunalUiEvent
import com.android.systemui.communal.shared.model.EditModeState
import com.android.systemui.communal.widgets.WidgetConfigurator
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.KeyguardState
@@ -74,8 +77,10 @@ constructor(
private val uiEventLogger: UiEventLogger,
@CommunalLog logBuffer: LogBuffer,
@Background private val backgroundDispatcher: CoroutineDispatcher,
- private val communalPrefsInteractor: CommunalPrefsInteractor,
private val metricsLogger: CommunalMetricsLogger,
+ @Application private val context: Context,
+ private val accessibilityManager: AccessibilityManager,
+ private val packageManager: PackageManager,
) : BaseCommunalViewModel(communalSceneInteractor, communalInteractor, mediaHost) {
private val logger = Logger(logBuffer, "CommunalEditModeViewModel")
@@ -156,6 +161,25 @@ constructor(
uiEventLogger.log(CommunalUiEvent.COMMUNAL_HUB_REORDER_WIDGET_CANCEL)
}
+ override fun onNewWidgetAdded(provider: AppWidgetProviderInfo) {
+ if (!accessibilityManager.isEnabled) {
+ return
+ }
+
+ // Send an accessibility announcement for the newly added widget
+ val widgetLabel = provider.loadLabel(packageManager)
+ val announcementText =
+ context.getString(
+ R.string.accessibility_announcement_communal_widget_added,
+ widgetLabel
+ )
+ accessibilityManager.sendAccessibilityEvent(
+ AccessibilityEvent(AccessibilityEvent.TYPE_ANNOUNCEMENT).apply {
+ contentDescription = announcementText
+ }
+ )
+ }
+
val isIdleOnCommunal: StateFlow<Boolean> = communalInteractor.isIdleOnCommunal
/** Launch the widget picker activity using the given {@link ActivityResultLauncher}. */