Add ActivatableNotificationViewBinder hierarchy
Bug: 271161129
Test: atest SystemUITests
Change-Id: Ia1a06a400ce5f978f6147b74b9a9afb7c787efdd
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/AccessibilityRepository.kt b/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/AccessibilityRepository.kt
new file mode 100644
index 0000000..ae9f57f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/AccessibilityRepository.kt
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2023 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.accessibility.data.repository
+
+import android.view.accessibility.AccessibilityManager
+import android.view.accessibility.AccessibilityManager.TouchExplorationStateChangeListener
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import dagger.Module
+import dagger.Provides
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.distinctUntilChanged
+
+/** Exposes accessibility-related state. */
+interface AccessibilityRepository {
+ /** @see [AccessibilityManager.isTouchExplorationEnabled] */
+ val isTouchExplorationEnabled: Flow<Boolean>
+
+ companion object {
+ operator fun invoke(a11yManager: AccessibilityManager): AccessibilityRepository =
+ AccessibilityRepositoryImpl(a11yManager)
+ }
+}
+
+private class AccessibilityRepositoryImpl(
+ manager: AccessibilityManager,
+) : AccessibilityRepository {
+ override val isTouchExplorationEnabled: Flow<Boolean> =
+ conflatedCallbackFlow {
+ val listener = TouchExplorationStateChangeListener(::trySend)
+ manager.addTouchExplorationStateChangeListener(listener)
+ trySend(manager.isTouchExplorationEnabled)
+ awaitClose { manager.removeTouchExplorationStateChangeListener(listener) }
+ }
+ .distinctUntilChanged()
+}
+
+@Module
+object AccessibilityRepositoryModule {
+ @Provides fun provideRepo(manager: AccessibilityManager) = AccessibilityRepository(manager)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/domain/interactor/AccessibilityInteractor.kt b/packages/SystemUI/src/com/android/systemui/accessibility/domain/interactor/AccessibilityInteractor.kt
new file mode 100644
index 0000000..968ce0d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/domain/interactor/AccessibilityInteractor.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2023 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.accessibility.domain.interactor
+
+import com.android.systemui.accessibility.data.repository.AccessibilityRepository
+import com.android.systemui.dagger.SysUISingleton
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+
+@SysUISingleton
+class AccessibilityInteractor
+@Inject
+constructor(
+ private val a11yRepo: AccessibilityRepository,
+) {
+ /** @see [android.view.accessibility.AccessibilityManager.isTouchExplorationEnabled] */
+ val isTouchExplorationEnabled: Flow<Boolean>
+ get() = a11yRepo.isTouchExplorationEnabled
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index 7945470..75fcbd0 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -29,6 +29,7 @@
import com.android.systemui.BootCompleteCache;
import com.android.systemui.BootCompleteCacheImpl;
import com.android.systemui.accessibility.AccessibilityModule;
+import com.android.systemui.accessibility.data.repository.AccessibilityRepositoryModule;
import com.android.systemui.appops.dagger.AppOpsModule;
import com.android.systemui.assist.AssistModule;
import com.android.systemui.biometrics.AlternateUdfpsTouchProvider;
@@ -140,6 +141,7 @@
*/
@Module(includes = {
AccessibilityModule.class,
+ AccessibilityRepositoryModule.class,
AppOpsModule.class,
AssistModule.class,
BiometricsModule.class,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
index aa3ecb6..766ad88 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
@@ -196,7 +196,7 @@
public void onTap() {}
/** Sets the last action up time this view was touched. */
- void setLastActionUpTime(long eventTime) {
+ public void setLastActionUpTime(long eventTime) {
mLastActionUpTime = eventTime;
}
@@ -705,7 +705,7 @@
return mRefocusOnDismiss || isAccessibilityFocused();
}
- void setTouchHandler(Gefingerpoken touchHandler) {
+ public void setTouchHandler(Gefingerpoken touchHandler) {
mTouchHandler = touchHandler;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewbinder/ActivatableNotificationViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewbinder/ActivatableNotificationViewBinder.kt
new file mode 100644
index 0000000..54af107
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewbinder/ActivatableNotificationViewBinder.kt
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2023 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.statusbar.notification.row.ui.viewbinder
+
+import android.view.MotionEvent
+import android.view.View
+import android.view.View.OnTouchListener
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.repeatOnLifecycle
+import com.android.systemui.Gefingerpoken
+import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.statusbar.notification.row.ActivatableNotificationView
+import com.android.systemui.statusbar.notification.row.ui.viewmodel.ActivatableNotificationViewModel
+import kotlinx.coroutines.awaitCancellation
+import kotlinx.coroutines.launch
+
+/** Binds an [ActivatableNotificationView] to its [view model][ActivatableNotificationViewModel]. */
+object ActivatableNotificationViewBinder {
+
+ fun bind(
+ viewModel: ActivatableNotificationViewModel,
+ view: ActivatableNotificationView,
+ falsingManager: FalsingManager,
+ ) {
+ ExpandableOutlineViewBinder.bind(viewModel, view)
+ val touchHandler = TouchHandler(view, falsingManager)
+ view.repeatWhenAttached {
+ repeatOnLifecycle(Lifecycle.State.STARTED) {
+ launch {
+ viewModel.isTouchable.collect { isTouchable ->
+ touchHandler.isTouchEnabled = isTouchable
+ }
+ }
+ view.registerListenersWhileAttached(touchHandler)
+ }
+ }
+ }
+
+ private suspend fun ActivatableNotificationView.registerListenersWhileAttached(
+ touchHandler: TouchHandler,
+ ): Unit =
+ try {
+ setOnTouchListener(touchHandler)
+ setTouchHandler(touchHandler)
+ awaitCancellation()
+ } finally {
+ setTouchHandler(null)
+ setOnTouchListener(null)
+ }
+}
+
+private class TouchHandler(
+ private val view: ActivatableNotificationView,
+ private val falsingManager: FalsingManager,
+) : Gefingerpoken, OnTouchListener {
+
+ var isTouchEnabled = false
+
+ override fun onTouch(v: View, ev: MotionEvent): Boolean {
+ val result = false
+ if (ev.action == MotionEvent.ACTION_UP) {
+ view.setLastActionUpTime(ev.eventTime)
+ }
+ // With a11y, just do nothing.
+ if (!isTouchEnabled) {
+ return false
+ }
+ if (ev.action == MotionEvent.ACTION_UP) {
+ // If this is a false tap, capture the even so it doesn't result in a click.
+ val falseTap: Boolean = falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)
+ if (!falseTap && v is ActivatableNotificationView) {
+ v.onTap()
+ }
+ return falseTap
+ }
+ return result
+ }
+
+ override fun onInterceptTouchEvent(ev: MotionEvent): Boolean = false
+
+ /** Use [onTouch] instead. */
+ override fun onTouchEvent(ev: MotionEvent): Boolean = false
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewbinder/ExpandableOutlineViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewbinder/ExpandableOutlineViewBinder.kt
new file mode 100644
index 0000000..745ce77
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewbinder/ExpandableOutlineViewBinder.kt
@@ -0,0 +1,13 @@
+package com.android.systemui.statusbar.notification.row.ui.viewbinder
+
+import com.android.systemui.statusbar.notification.row.ExpandableOutlineView
+import com.android.systemui.statusbar.notification.row.ui.viewmodel.ExpandableOutlineViewModel as ViewModel
+
+object ExpandableOutlineViewBinder {
+ fun bind(
+ viewModel: ViewModel,
+ view: ExpandableOutlineView,
+ ) {
+ ExpandableViewBinder.bind(viewModel, view)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewbinder/ExpandableViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewbinder/ExpandableViewBinder.kt
new file mode 100644
index 0000000..49cfb576
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewbinder/ExpandableViewBinder.kt
@@ -0,0 +1,8 @@
+package com.android.systemui.statusbar.notification.row.ui.viewbinder
+
+import com.android.systemui.statusbar.notification.row.ExpandableView
+import com.android.systemui.statusbar.notification.row.ui.viewmodel.ExpandableViewModel as ViewModel
+
+object ExpandableViewBinder {
+ fun bind(viewModel: ViewModel, view: ExpandableView) {}
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/ActivatableNotificationViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/ActivatableNotificationViewModel.kt
new file mode 100644
index 0000000..f46d424
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/ActivatableNotificationViewModel.kt
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2023 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.statusbar.notification.row.ui.viewmodel
+
+import com.android.systemui.accessibility.domain.interactor.AccessibilityInteractor
+import dagger.Module
+import dagger.Provides
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.map
+
+/** ViewModel for [com.android.systemui.statusbar.notification.row.ActivatableNotificationView]. */
+interface ActivatableNotificationViewModel : ExpandableOutlineViewModel {
+ /** Does the view react to touches? */
+ val isTouchable: Flow<Boolean>
+
+ companion object {
+ operator fun invoke(
+ a11yInteractor: AccessibilityInteractor,
+ ): ActivatableNotificationViewModel = ActivatableNotificationViewModelImpl(a11yInteractor)
+ }
+}
+
+private class ActivatableNotificationViewModelImpl(
+ a11yInteractor: AccessibilityInteractor,
+) : ActivatableNotificationViewModel {
+ override val isTouchable: Flow<Boolean> =
+ // If a11y touch exploration is enabled, then the activatable view should ignore touches
+ a11yInteractor.isTouchExplorationEnabled.map { !it }
+}
+
+@Module
+object ActivatableNotificationViewModelModule {
+ @Provides
+ fun provideViewModel(interactor: AccessibilityInteractor) =
+ ActivatableNotificationViewModel(interactor)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/ExpandableOutlineViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/ExpandableOutlineViewModel.kt
new file mode 100644
index 0000000..5904c77
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/ExpandableOutlineViewModel.kt
@@ -0,0 +1,4 @@
+package com.android.systemui.statusbar.notification.row.ui.viewmodel
+
+/** ViewModel for [com.android.systemui.statusbar.notification.row.ExpandableOutlineView]. */
+interface ExpandableOutlineViewModel : ExpandableViewModel
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/ExpandableViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/ExpandableViewModel.kt
new file mode 100644
index 0000000..5efaf04
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/ExpandableViewModel.kt
@@ -0,0 +1,4 @@
+package com.android.systemui.statusbar.notification.row.ui.viewmodel
+
+/** ViewModel for [com.android.systemui.statusbar.notification.row.ExpandableView]. */
+interface ExpandableViewModel
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewbinder/NotificationShelfViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewbinder/NotificationShelfViewBinder.kt
index 9638753e..c823189 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewbinder/NotificationShelfViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewbinder/NotificationShelfViewBinder.kt
@@ -17,7 +17,6 @@
package com.android.systemui.statusbar.notification.shelf.ui.viewbinder
import android.view.View
-import android.view.accessibility.AccessibilityManager
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
import com.android.systemui.flags.FeatureFlags
@@ -27,9 +26,7 @@
import com.android.systemui.statusbar.LegacyNotificationShelfControllerImpl
import com.android.systemui.statusbar.NotificationShelf
import com.android.systemui.statusbar.NotificationShelfController
-import com.android.systemui.statusbar.notification.row.ActivatableNotificationViewController
-import com.android.systemui.statusbar.notification.row.ExpandableOutlineViewController
-import com.android.systemui.statusbar.notification.row.ExpandableViewController
+import com.android.systemui.statusbar.notification.row.ui.viewbinder.ActivatableNotificationViewBinder
import com.android.systemui.statusbar.notification.shelf.ui.viewmodel.NotificationShelfViewModel
import com.android.systemui.statusbar.notification.stack.AmbientState
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
@@ -56,7 +53,6 @@
private val shelf: NotificationShelf,
private val viewModel: NotificationShelfViewModel,
featureFlags: FeatureFlags,
- private val a11yManager: AccessibilityManager,
private val falsingManager: FalsingManager,
hostControllerLazy: Lazy<NotificationStackScrollLayoutController>,
private val notificationIconAreaController: NotificationIconAreaController,
@@ -76,15 +72,7 @@
}
fun init() {
- NotificationShelfViewBinder.bind(viewModel, shelf)
-
- ActivatableNotificationViewController(
- shelf,
- ExpandableOutlineViewController(shelf, ExpandableViewController(shelf)),
- a11yManager,
- falsingManager,
- )
- .init()
+ NotificationShelfViewBinder.bind(viewModel, shelf, falsingManager)
hostController.setShelf(shelf)
hostController.setOnNotificationRemovedListener { child, _ ->
view.requestRoundnessResetFor(child)
@@ -113,7 +101,12 @@
/** Binds a [NotificationShelf] to its backend. */
object NotificationShelfViewBinder {
- fun bind(viewModel: NotificationShelfViewModel, shelf: NotificationShelf) {
+ fun bind(
+ viewModel: NotificationShelfViewModel,
+ shelf: NotificationShelf,
+ falsingManager: FalsingManager,
+ ) {
+ ActivatableNotificationViewBinder.bind(viewModel, shelf, falsingManager)
shelf.repeatWhenAttached {
repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.canModifyColorOfNotifications
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModel.kt
index 0b41b63..fb19443 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModel.kt
@@ -17,6 +17,7 @@
package com.android.systemui.statusbar.notification.shelf.ui.viewmodel
import com.android.systemui.statusbar.NotificationShelf
+import com.android.systemui.statusbar.notification.row.ui.viewmodel.ActivatableNotificationViewModel
import com.android.systemui.statusbar.notification.shelf.domain.interactor.NotificationShelfInteractor
import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent.CentralSurfacesScope
import javax.inject.Inject
@@ -29,7 +30,8 @@
@Inject
constructor(
private val interactor: NotificationShelfInteractor,
-) {
+ activatableViewModel: ActivatableNotificationViewModel,
+) : ActivatableNotificationViewModel by activatableViewModel {
/** Is the shelf allowed to be clickable when it has content? */
val isClickable: Flow<Boolean>
get() = interactor.isShowingOnKeyguard
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java
index 5d4adda..b96001f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java
@@ -21,9 +21,7 @@
import android.os.Handler;
import android.view.LayoutInflater;
import android.view.ViewStub;
-
import androidx.constraintlayout.motion.widget.MotionLayout;
-
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.keyguard.LockIconView;
import com.android.systemui.R;
@@ -52,6 +50,7 @@
import com.android.systemui.statusbar.core.StatusBarInitializer.OnStatusBarViewInitializedListener;
import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler;
import com.android.systemui.statusbar.notification.row.dagger.NotificationShelfComponent;
+import com.android.systemui.statusbar.notification.row.ui.viewmodel.ActivatableNotificationViewModelModule;
import com.android.systemui.statusbar.notification.shelf.ui.viewbinder.NotificationShelfViewBinderWrapperControllerImpl;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
import com.android.systemui.statusbar.phone.KeyguardBottomAreaView;
@@ -75,18 +74,16 @@
import com.android.systemui.tuner.TunerService;
import com.android.systemui.util.CarrierConfigTracker;
import com.android.systemui.util.settings.SecureSettings;
-
-import java.util.concurrent.Executor;
-
-import javax.inject.Named;
-import javax.inject.Provider;
-
import dagger.Binds;
import dagger.Module;
import dagger.Provides;
import dagger.multibindings.IntoSet;
+import java.util.concurrent.Executor;
+import javax.inject.Named;
+import javax.inject.Provider;
-@Module(subcomponents = StatusBarFragmentComponent.class)
+@Module(subcomponents = StatusBarFragmentComponent.class,
+ includes = { ActivatableNotificationViewModelModule.class })
public abstract class StatusBarViewModule {
public static final String SHADE_HEADER = "large_screen_shade_header";
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/data/repository/AccessibilityRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/accessibility/data/repository/AccessibilityRepositoryTest.kt
new file mode 100644
index 0000000..aff52f5
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/data/repository/AccessibilityRepositoryTest.kt
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2023 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.accessibility.data.repository
+
+import android.testing.AndroidTestingRunner
+import android.view.accessibility.AccessibilityManager
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.mockito.withArgCaptor
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.junit.MockitoJUnit
+import org.mockito.junit.MockitoRule
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class AccessibilityRepositoryTest : SysuiTestCase() {
+
+ @Rule @JvmField val mockitoRule: MockitoRule = MockitoJUnit.rule()
+
+ // mocks
+ @Mock private lateinit var a11yManager: AccessibilityManager
+
+ // real impls
+ private val underTest by lazy { AccessibilityRepository(a11yManager) }
+
+ @Test
+ fun isTouchExplorationEnabled_reflectsA11yManager_initFalse() = runTest {
+ whenever(a11yManager.isTouchExplorationEnabled).thenReturn(false)
+ val isTouchExplorationEnabled by collectLastValue(underTest.isTouchExplorationEnabled)
+ assertThat(isTouchExplorationEnabled).isFalse()
+ }
+
+ @Test
+ fun isTouchExplorationEnabled_reflectsA11yManager_initTrue() = runTest {
+ whenever(a11yManager.isTouchExplorationEnabled).thenReturn(true)
+ val isTouchExplorationEnabled by collectLastValue(underTest.isTouchExplorationEnabled)
+ assertThat(isTouchExplorationEnabled).isTrue()
+ }
+
+ @Test
+ fun isTouchExplorationEnabled_reflectsA11yManager_changeTrue() = runTest {
+ whenever(a11yManager.isTouchExplorationEnabled).thenReturn(false)
+ val isTouchExplorationEnabled by collectLastValue(underTest.isTouchExplorationEnabled)
+ runCurrent()
+ withArgCaptor { verify(a11yManager).addTouchExplorationStateChangeListener(capture()) }
+ .onTouchExplorationStateChanged(/* enabled = */ true)
+ assertThat(isTouchExplorationEnabled).isTrue()
+ }
+
+ @Test
+ fun isTouchExplorationEnabled_reflectsA11yManager_changeFalse() = runTest {
+ whenever(a11yManager.isTouchExplorationEnabled).thenReturn(true)
+ val isTouchExplorationEnabled by collectLastValue(underTest.isTouchExplorationEnabled)
+ runCurrent()
+ withArgCaptor { verify(a11yManager).addTouchExplorationStateChangeListener(capture()) }
+ .onTouchExplorationStateChanged(/* enabled = */ false)
+ assertThat(isTouchExplorationEnabled).isFalse()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/ActivatableNotificationViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/ActivatableNotificationViewModelTest.kt
new file mode 100644
index 0000000..c960230
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/ActivatableNotificationViewModelTest.kt
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2023 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.statusbar.notification.row.ui.viewmodel
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.accessibility.data.repository.FakeAccessibilityRepository
+import com.android.systemui.accessibility.domain.interactor.AccessibilityInteractor
+import com.android.systemui.coroutines.collectLastValue
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class ActivatableNotificationViewModelTest : SysuiTestCase() {
+
+ // fakes
+ private val a11yRepo = FakeAccessibilityRepository()
+
+ // real impls
+ private val a11yInteractor = AccessibilityInteractor(a11yRepo)
+ private val underTest = ActivatableNotificationViewModel(a11yInteractor)
+
+ @Test
+ fun isTouchable_whenA11yTouchExplorationDisabled() = runTest {
+ a11yRepo.isTouchExplorationEnabled.value = false
+ val isTouchable: Boolean? by collectLastValue(underTest.isTouchable)
+ assertThat(isTouchable).isTrue()
+ }
+
+ @Test
+ fun isNotTouchable_whenA11yTouchExplorationEnabled() = runTest {
+ a11yRepo.isTouchExplorationEnabled.value = true
+ val isTouchable: Boolean? by collectLastValue(underTest.isTouchable)
+ assertThat(isTouchable).isFalse()
+ }
+
+ @Test
+ fun isTouchable_whenA11yTouchExplorationChangesToDisabled() = runTest {
+ a11yRepo.isTouchExplorationEnabled.value = true
+ val isTouchable: Boolean? by collectLastValue(underTest.isTouchable)
+ runCurrent()
+ a11yRepo.isTouchExplorationEnabled.value = false
+ assertThat(isTouchable).isTrue()
+ }
+
+ @Test
+ fun isNotTouchable_whenA11yTouchExplorationChangesToEnabled() = runTest {
+ a11yRepo.isTouchExplorationEnabled.value = false
+ val isTouchable: Boolean? by collectLastValue(underTest.isTouchable)
+ runCurrent()
+ a11yRepo.isTouchExplorationEnabled.value = true
+ assertThat(isTouchable).isFalse()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModelTest.kt
index c36925a..e9a8f3f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModelTest.kt
@@ -22,10 +22,13 @@
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.accessibility.data.repository.FakeAccessibilityRepository
+import com.android.systemui.accessibility.domain.interactor.AccessibilityInteractor
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFaceAuthRepository
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
import com.android.systemui.statusbar.LockscreenShadeTransitionController
+import com.android.systemui.statusbar.notification.row.ui.viewmodel.ActivatableNotificationViewModel
import com.android.systemui.statusbar.notification.shelf.domain.interactor.NotificationShelfInteractor
import com.android.systemui.statusbar.phone.CentralSurfaces
import com.android.systemui.util.mockito.any
@@ -58,8 +61,11 @@
private val keyguardRepository = FakeKeyguardRepository()
private val deviceEntryFaceAuthRepository = FakeDeviceEntryFaceAuthRepository()
private val systemClock = FakeSystemClock()
+ private val a11yRepo = FakeAccessibilityRepository()
// real impls
+ private val a11yInteractor = AccessibilityInteractor(a11yRepo)
+ private val activatableViewModel = ActivatableNotificationViewModel(a11yInteractor)
private val interactor by lazy {
NotificationShelfInteractor(
keyguardRepository,
@@ -69,7 +75,7 @@
keyguardTransitionController,
)
}
- private val underTest by lazy { NotificationShelfViewModel(interactor) }
+ private val underTest by lazy { NotificationShelfViewModel(interactor, activatableViewModel) }
@Test
fun canModifyColorOfNotifications_whenKeyguardNotShowing() = runTest {
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/accessibility/data/repository/FakeAccessibilityRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/accessibility/data/repository/FakeAccessibilityRepository.kt
new file mode 100644
index 0000000..8444c7b
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/accessibility/data/repository/FakeAccessibilityRepository.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2023 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.accessibility.data.repository
+
+import kotlinx.coroutines.flow.MutableStateFlow
+
+class FakeAccessibilityRepository(
+ override val isTouchExplorationEnabled: MutableStateFlow<Boolean> = MutableStateFlow(false)
+) : AccessibilityRepository