Remove Controller usage from NotificationShelf
Under a unidirectional architecture, Views should not have access to their Controller - AmbientState is View-related logic, so NotificationShelf can access that information directly on mAmbientState instead of going through the controller. - NotificationShelfController needs to push the KeyguardBypassController state into NotificationShelf, in order to reverse the data flow - Rather than NotificationShelf having a concept of "Bypass", change the API to "setCanModifyColorOfNotifications" (as opposed to "setBypassEnabled", for example) Bug: 271161129 Test: atest SystemUITests Change-Id: I1c1615d5c3b28e2237f77b786b4dad062ffbea37
This commit is contained in:
@@ -92,6 +92,9 @@ interface DeviceEntryFaceAuthRepository {
|
||||
/** Current state of whether face authentication is running. */
|
||||
val isAuthRunning: Flow<Boolean>
|
||||
|
||||
/** Whether bypass is currently enabled */
|
||||
val isBypassEnabled: Flow<Boolean>
|
||||
|
||||
/**
|
||||
* Trigger face authentication.
|
||||
*
|
||||
@@ -166,7 +169,7 @@ constructor(
|
||||
override val isAuthenticated: Flow<Boolean>
|
||||
get() = _isAuthenticated
|
||||
|
||||
private val bypassEnabled: Flow<Boolean> =
|
||||
override val isBypassEnabled: Flow<Boolean> =
|
||||
keyguardBypassController?.let {
|
||||
conflatedCallbackFlow {
|
||||
val callback =
|
||||
@@ -222,7 +225,7 @@ constructor(
|
||||
// & detection is supported & biometric unlock is not allowed.
|
||||
listOf(
|
||||
canFaceAuthOrDetectRun(),
|
||||
logAndObserve(bypassEnabled, "bypassEnabled"),
|
||||
logAndObserve(isBypassEnabled, "isBypassEnabled"),
|
||||
logAndObserve(
|
||||
biometricSettingsRepository.isNonStrongBiometricAllowed.isFalse(),
|
||||
"nonStrongBiometricIsNotAllowed"
|
||||
|
||||
@@ -24,6 +24,7 @@ import android.content.res.Resources;
|
||||
import android.graphics.Rect;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.IndentingPrintWriter;
|
||||
import android.util.Log;
|
||||
import android.util.MathUtils;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
@@ -96,6 +97,8 @@ public class NotificationShelf extends ActivatableNotificationView implements St
|
||||
private NotificationShelfController mController;
|
||||
private float mActualWidth = -1;
|
||||
private boolean mSensitiveRevealAnimEndabled;
|
||||
private boolean mShelfRefactorFlagEnabled;
|
||||
private boolean mCanModifyColorOfNotifications;
|
||||
|
||||
public NotificationShelf(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
@@ -425,7 +428,7 @@ public class NotificationShelf extends ActivatableNotificationView implements St
|
||||
transitionAmount = inShelfAmount;
|
||||
}
|
||||
// We don't want to modify the color if the notification is hun'd
|
||||
if (isLastChild && mController.canModifyColorOfNotifications()) {
|
||||
if (isLastChild && canModifyColorOfNotifications()) {
|
||||
if (colorOfViewBeforeLast == NO_COLOR) {
|
||||
colorOfViewBeforeLast = ownColorUntinted;
|
||||
}
|
||||
@@ -490,6 +493,14 @@ public class NotificationShelf extends ActivatableNotificationView implements St
|
||||
}
|
||||
}
|
||||
|
||||
private boolean canModifyColorOfNotifications() {
|
||||
if (mShelfRefactorFlagEnabled) {
|
||||
return mCanModifyColorOfNotifications && mAmbientState.isShadeExpanded();
|
||||
} else {
|
||||
return mController.canModifyColorOfNotifications();
|
||||
}
|
||||
}
|
||||
|
||||
private void updateCornerRoundnessOnScroll(
|
||||
ActivatableNotificationView anv,
|
||||
float viewStart,
|
||||
@@ -959,10 +970,31 @@ public class NotificationShelf extends ActivatableNotificationView implements St
|
||||
return false;
|
||||
}
|
||||
|
||||
private void assertRefactorFlagDisabled() {
|
||||
if (mShelfRefactorFlagEnabled) {
|
||||
throw new IllegalStateException(
|
||||
"Code path not supported when Flags.NOTIFICATION_SHELF_REFACTOR is enabled.");
|
||||
}
|
||||
}
|
||||
|
||||
private boolean checkRefactorFlagEnabled() {
|
||||
if (!mShelfRefactorFlagEnabled) {
|
||||
Log.wtf(TAG,
|
||||
"Code path not supported when Flags.NOTIFICATION_SHELF_REFACTOR is disabled.");
|
||||
}
|
||||
return mShelfRefactorFlagEnabled;
|
||||
}
|
||||
|
||||
public void setController(NotificationShelfController notificationShelfController) {
|
||||
assertRefactorFlagDisabled();
|
||||
mController = notificationShelfController;
|
||||
}
|
||||
|
||||
public void setCanModifyColorOfNotifications(boolean canModifyColorOfNotifications) {
|
||||
if (!checkRefactorFlagEnabled()) return;
|
||||
mCanModifyColorOfNotifications = canModifyColorOfNotifications;
|
||||
}
|
||||
|
||||
public void setIndexOfFirstViewInShelf(ExpandableView firstViewInShelf) {
|
||||
mIndexOfFirstViewInShelf = mHostLayoutController.indexOfChild(firstViewInShelf);
|
||||
}
|
||||
@@ -975,6 +1007,10 @@ public class NotificationShelf extends ActivatableNotificationView implements St
|
||||
mSensitiveRevealAnimEndabled = enabled;
|
||||
}
|
||||
|
||||
public void setRefactorFlagEnabled(boolean enabled) {
|
||||
mShelfRefactorFlagEnabled = enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method resets the OnScroll roundness of a view to 0f
|
||||
* <p>
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* 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.shelf.domain.interactor
|
||||
|
||||
import com.android.systemui.keyguard.data.repository.DeviceEntryFaceAuthRepository
|
||||
import com.android.systemui.keyguard.data.repository.KeyguardRepository
|
||||
import com.android.systemui.statusbar.NotificationShelf
|
||||
import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent
|
||||
import javax.inject.Inject
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
|
||||
/** Interactor for the [NotificationShelf] */
|
||||
@CentralSurfacesComponent.CentralSurfacesScope
|
||||
class NotificationShelfInteractor
|
||||
@Inject
|
||||
constructor(
|
||||
private val keyguardRepository: KeyguardRepository,
|
||||
private val deviceEntryFaceAuthRepository: DeviceEntryFaceAuthRepository,
|
||||
) {
|
||||
/** Is the system in a state where the shelf is just a static display of notification icons? */
|
||||
val isShelfStatic: Flow<Boolean>
|
||||
get() =
|
||||
combine(
|
||||
keyguardRepository.isKeyguardShowing,
|
||||
deviceEntryFaceAuthRepository.isBypassEnabled,
|
||||
) { isKeyguardShowing, isBypassEnabled ->
|
||||
isKeyguardShowing && isBypassEnabled
|
||||
}
|
||||
}
|
||||
@@ -14,14 +14,17 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.systemui.statusbar.notification.shelf.view
|
||||
package com.android.systemui.statusbar.notification.shelf.ui.viewbinder
|
||||
|
||||
import android.view.View
|
||||
import android.view.View.OnAttachStateChangeListener
|
||||
import android.view.accessibility.AccessibilityManager
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.repeatOnLifecycle
|
||||
import com.android.systemui.classifier.FalsingCollector
|
||||
import com.android.systemui.flags.FeatureFlags
|
||||
import com.android.systemui.flags.Flags
|
||||
import com.android.systemui.lifecycle.repeatWhenAttached
|
||||
import com.android.systemui.plugins.FalsingManager
|
||||
import com.android.systemui.statusbar.LegacyNotificationShelfControllerImpl
|
||||
import com.android.systemui.statusbar.NotificationShelf
|
||||
@@ -31,21 +34,16 @@ import com.android.systemui.statusbar.notification.row.ActivatableNotificationVi
|
||||
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.shelf.ui.viewmodel.NotificationShelfViewModel
|
||||
import com.android.systemui.statusbar.notification.stack.AmbientState
|
||||
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
|
||||
import com.android.systemui.statusbar.phone.KeyguardBypassController
|
||||
import com.android.systemui.statusbar.phone.NotificationIconContainer
|
||||
import com.android.systemui.statusbar.phone.NotificationTapHelper
|
||||
import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent.CentralSurfacesScope
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import javax.inject.Inject
|
||||
|
||||
/** Binds a [NotificationShelf] to its backend. */
|
||||
interface NotificationShelfViewBinder {
|
||||
fun bind(shelf: NotificationShelf)
|
||||
}
|
||||
|
||||
/**
|
||||
* Controller class for [NotificationShelf]. This implementation serves as a temporary wrapper
|
||||
* around a [NotificationShelfViewBinder], so that external code can continue to depend on the
|
||||
@@ -57,8 +55,7 @@ class NotificationShelfViewBinderWrapperControllerImpl
|
||||
@Inject
|
||||
constructor(
|
||||
private val shelf: NotificationShelf,
|
||||
private val viewBinder: NotificationShelfViewBinder,
|
||||
private val keyguardBypassController: KeyguardBypassController,
|
||||
private val viewModel: NotificationShelfViewModel,
|
||||
featureFlags: FeatureFlags,
|
||||
private val notifTapHelperFactory: NotificationTapHelper.Factory,
|
||||
private val a11yManager: AccessibilityManager,
|
||||
@@ -67,20 +64,19 @@ constructor(
|
||||
private val statusBarStateController: SysuiStatusBarStateController,
|
||||
) : NotificationShelfController {
|
||||
|
||||
private var ambientState: AmbientState? = null
|
||||
|
||||
override val view: NotificationShelf
|
||||
get() = shelf
|
||||
|
||||
init {
|
||||
shelf.apply {
|
||||
setRefactorFlagEnabled(featureFlags.isEnabled(Flags.NOTIFICATION_SHELF_REFACTOR))
|
||||
useRoundnessSourceTypes(featureFlags.isEnabled(Flags.USE_ROUNDNESS_SOURCETYPES))
|
||||
setSensitiveRevealAnimEndabled(featureFlags.isEnabled(Flags.SENSITIVE_REVEAL_ANIM))
|
||||
}
|
||||
}
|
||||
|
||||
fun init() {
|
||||
viewBinder.bind(shelf)
|
||||
NotificationShelfViewBinder.bind(viewModel, shelf)
|
||||
|
||||
ActivatableNotificationViewController(
|
||||
shelf,
|
||||
@@ -91,7 +87,6 @@ constructor(
|
||||
falsingCollector,
|
||||
)
|
||||
.init()
|
||||
shelf.setController(this)
|
||||
val onAttachStateListener =
|
||||
object : OnAttachStateChangeListener {
|
||||
override fun onViewAttachedToWindow(v: View) {
|
||||
@@ -117,10 +112,7 @@ constructor(
|
||||
override val shelfIcons: NotificationIconContainer
|
||||
get() = shelf.shelfIcons
|
||||
|
||||
override fun canModifyColorOfNotifications(): Boolean {
|
||||
return (ambientState?.isShadeExpanded == true &&
|
||||
!(ambientState?.isOnKeyguard == true && keyguardBypassController.bypassEnabled))
|
||||
}
|
||||
override fun canModifyColorOfNotifications(): Boolean = unsupported
|
||||
|
||||
override fun setOnActivatedListener(listener: ActivatableNotificationView.OnActivatedListener) {
|
||||
shelf.setOnActivatedListener(listener)
|
||||
@@ -128,25 +120,28 @@ constructor(
|
||||
|
||||
override fun bind(
|
||||
ambientState: AmbientState,
|
||||
notificationStackScrollLayoutController: NotificationStackScrollLayoutController
|
||||
notificationStackScrollLayoutController: NotificationStackScrollLayoutController,
|
||||
) {
|
||||
shelf.bind(ambientState, notificationStackScrollLayoutController)
|
||||
this.ambientState = ambientState
|
||||
}
|
||||
|
||||
override fun setOnClickListener(listener: View.OnClickListener) {
|
||||
shelf.setOnClickListener(listener)
|
||||
}
|
||||
|
||||
private val unsupported: Nothing
|
||||
get() = error("Code path not supported when Flags.NOTIFICATION_SHELF_REFACTOR is enabled")
|
||||
}
|
||||
|
||||
@Module(includes = [PrivateShelfViewBinderModule::class]) object NotificationShelfViewBinderModule
|
||||
|
||||
@Module
|
||||
private interface PrivateShelfViewBinderModule {
|
||||
@Binds fun bindImpl(impl: NotificationShelfViewBinderImpl): NotificationShelfViewBinder
|
||||
}
|
||||
|
||||
@CentralSurfacesScope
|
||||
private class NotificationShelfViewBinderImpl @Inject constructor() : NotificationShelfViewBinder {
|
||||
override fun bind(shelf: NotificationShelf) {}
|
||||
/** Binds a [NotificationShelf] to its backend. */
|
||||
object NotificationShelfViewBinder {
|
||||
fun bind(viewModel: NotificationShelfViewModel, shelf: NotificationShelf) {
|
||||
shelf.repeatWhenAttached {
|
||||
repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||
viewModel.canModifyColorOfNotifications
|
||||
.onEach(shelf::setCanModifyColorOfNotifications)
|
||||
.launchIn(this)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* 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.shelf.ui.viewmodel
|
||||
|
||||
import com.android.systemui.statusbar.NotificationShelf
|
||||
import com.android.systemui.statusbar.notification.shelf.domain.interactor.NotificationShelfInteractor
|
||||
import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent.CentralSurfacesScope
|
||||
import javax.inject.Inject
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.map
|
||||
|
||||
/** ViewModel for [NotificationShelf]. */
|
||||
@CentralSurfacesScope
|
||||
class NotificationShelfViewModel
|
||||
@Inject
|
||||
constructor(
|
||||
private val interactor: NotificationShelfInteractor,
|
||||
) {
|
||||
/** Is the shelf allowed to modify the color of notifications in the host layout? */
|
||||
val canModifyColorOfNotifications: Flow<Boolean>
|
||||
get() = interactor.isShelfStatic.map { static -> !static }
|
||||
}
|
||||
@@ -52,8 +52,7 @@ import com.android.systemui.statusbar.OperatorNameViewController;
|
||||
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.shelf.view.NotificationShelfViewBinderModule;
|
||||
import com.android.systemui.statusbar.notification.shelf.view.NotificationShelfViewBinderWrapperControllerImpl;
|
||||
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;
|
||||
import com.android.systemui.statusbar.phone.LetterboxAppearanceCalculator;
|
||||
@@ -87,8 +86,7 @@ import dagger.Module;
|
||||
import dagger.Provides;
|
||||
import dagger.multibindings.IntoSet;
|
||||
|
||||
@Module(subcomponents = StatusBarFragmentComponent.class,
|
||||
includes = { NotificationShelfViewBinderModule.class })
|
||||
@Module(subcomponents = StatusBarFragmentComponent.class)
|
||||
public abstract class StatusBarViewModule {
|
||||
|
||||
public static final String SHADE_HEADER = "large_screen_shade_header";
|
||||
|
||||
@@ -59,11 +59,10 @@ import com.android.systemui.statusbar.phone.FakeKeyguardStateController
|
||||
import com.android.systemui.statusbar.phone.KeyguardBypassController
|
||||
import com.android.systemui.user.data.repository.FakeUserRepository
|
||||
import com.android.systemui.util.mockito.KotlinArgumentCaptor
|
||||
import com.android.systemui.util.mockito.captureMany
|
||||
import com.android.systemui.util.mockito.whenever
|
||||
import com.android.systemui.util.time.SystemClock
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import java.io.PrintWriter
|
||||
import java.io.StringWriter
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.test.StandardTestDispatcher
|
||||
@@ -81,6 +80,7 @@ import org.mockito.ArgumentMatchers.any
|
||||
import org.mockito.ArgumentMatchers.eq
|
||||
import org.mockito.Captor
|
||||
import org.mockito.Mock
|
||||
import org.mockito.Mockito.atLeastOnce
|
||||
import org.mockito.Mockito.clearInvocations
|
||||
import org.mockito.Mockito.isNull
|
||||
import org.mockito.Mockito.mock
|
||||
@@ -88,6 +88,8 @@ import org.mockito.Mockito.never
|
||||
import org.mockito.Mockito.verify
|
||||
import org.mockito.Mockito.verifyNoMoreInteractions
|
||||
import org.mockito.MockitoAnnotations
|
||||
import java.io.PrintWriter
|
||||
import java.io.StringWriter
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
@SmallTest
|
||||
@@ -120,6 +122,7 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() {
|
||||
private lateinit var authStatus: FlowValue<AuthenticationStatus?>
|
||||
private lateinit var detectStatus: FlowValue<DetectionStatus?>
|
||||
private lateinit var authRunning: FlowValue<Boolean?>
|
||||
private lateinit var bypassEnabled: FlowValue<Boolean?>
|
||||
private lateinit var lockedOut: FlowValue<Boolean?>
|
||||
private lateinit var canFaceAuthRun: FlowValue<Boolean?>
|
||||
private lateinit var authenticated: FlowValue<Boolean?>
|
||||
@@ -725,6 +728,23 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun isBypassEnabledReflectsBypassControllerState() =
|
||||
testScope.runTest {
|
||||
initCollectors()
|
||||
runCurrent()
|
||||
val listeners = captureMany {
|
||||
verify(bypassController, atLeastOnce())
|
||||
.registerOnBypassStateChangedListener(capture())
|
||||
}
|
||||
|
||||
listeners.forEach { it.onBypassStateChanged(true) }
|
||||
assertThat(bypassEnabled()).isTrue()
|
||||
|
||||
listeners.forEach { it.onBypassStateChanged(false) }
|
||||
assertThat(bypassEnabled()).isFalse()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun detectDoesNotRunWhenNonStrongBiometricIsAllowed() =
|
||||
testScope.runTest {
|
||||
@@ -844,6 +864,7 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() {
|
||||
lockedOut = collectLastValue(underTest.isLockedOut)
|
||||
canFaceAuthRun = collectLastValue(underTest.canRunFaceAuth)
|
||||
authenticated = collectLastValue(underTest.isAuthenticated)
|
||||
bypassEnabled = collectLastValue(underTest.isBypassEnabled)
|
||||
fakeUserRepository.setSelectedUserInfo(primaryUser)
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* 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.shelf.domain.interactor
|
||||
|
||||
import android.testing.AndroidTestingRunner
|
||||
import androidx.test.filters.SmallTest
|
||||
import com.android.systemui.SysuiTestCase
|
||||
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.notification.shelf.domain.interactor.NotificationShelfInteractor
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
@RunWith(AndroidTestingRunner::class)
|
||||
@SmallTest
|
||||
class NotificationShelfInteractorTest : SysuiTestCase() {
|
||||
|
||||
private val keyguardRepository = FakeKeyguardRepository()
|
||||
private val deviceEntryFaceAuthRepository = FakeDeviceEntryFaceAuthRepository()
|
||||
private val underTest =
|
||||
NotificationShelfInteractor(keyguardRepository, deviceEntryFaceAuthRepository)
|
||||
|
||||
@Test
|
||||
fun shelfIsNotStatic_whenKeyguardNotShowing() = runTest {
|
||||
val shelfStatic by collectLastValue(underTest.isShelfStatic)
|
||||
|
||||
keyguardRepository.setKeyguardShowing(false)
|
||||
|
||||
assertThat(shelfStatic).isFalse()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun shelfIsNotStatic_whenKeyguardShowingAndNotBypass() = runTest {
|
||||
val shelfStatic by collectLastValue(underTest.isShelfStatic)
|
||||
|
||||
keyguardRepository.setKeyguardShowing(true)
|
||||
deviceEntryFaceAuthRepository.isBypassEnabled.value = false
|
||||
|
||||
assertThat(shelfStatic).isFalse()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun shelfIsStatic_whenBypass() = runTest {
|
||||
val shelfStatic by collectLastValue(underTest.isShelfStatic)
|
||||
|
||||
keyguardRepository.setKeyguardShowing(true)
|
||||
deviceEntryFaceAuthRepository.isBypassEnabled.value = true
|
||||
|
||||
assertThat(shelfStatic).isTrue()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* 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.shelf.ui.viewmodel
|
||||
|
||||
import android.testing.AndroidTestingRunner
|
||||
import androidx.test.filters.SmallTest
|
||||
import com.android.systemui.SysuiTestCase
|
||||
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.notification.shelf.domain.interactor.NotificationShelfInteractor
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
@RunWith(AndroidTestingRunner::class)
|
||||
@SmallTest
|
||||
class NotificationShelfViewModelTest : SysuiTestCase() {
|
||||
|
||||
private val keyguardRepository = FakeKeyguardRepository()
|
||||
private val deviceEntryFaceAuthRepository = FakeDeviceEntryFaceAuthRepository()
|
||||
private val interactor =
|
||||
NotificationShelfInteractor(keyguardRepository, deviceEntryFaceAuthRepository)
|
||||
private val underTest = NotificationShelfViewModel(interactor)
|
||||
|
||||
@Test
|
||||
fun canModifyColorOfNotifications_whenKeyguardNotShowing() = runTest {
|
||||
val canModifyNotifColor by collectLastValue(underTest.canModifyColorOfNotifications)
|
||||
|
||||
keyguardRepository.setKeyguardShowing(false)
|
||||
|
||||
assertThat(canModifyNotifColor).isTrue()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun canModifyColorOfNotifications_whenKeyguardShowingAndNotBypass() = runTest {
|
||||
val canModifyNotifColor by collectLastValue(underTest.canModifyColorOfNotifications)
|
||||
|
||||
keyguardRepository.setKeyguardShowing(true)
|
||||
deviceEntryFaceAuthRepository.isBypassEnabled.value = false
|
||||
|
||||
assertThat(canModifyNotifColor).isTrue()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun cannotModifyColorOfNotifications_whenBypass() = runTest {
|
||||
val canModifyNotifColor by collectLastValue(underTest.canModifyColorOfNotifications)
|
||||
|
||||
keyguardRepository.setKeyguardShowing(true)
|
||||
deviceEntryFaceAuthRepository.isBypassEnabled.value = true
|
||||
|
||||
assertThat(canModifyNotifColor).isFalse()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* 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.keyguard.data.repository
|
||||
|
||||
import com.android.keyguard.FaceAuthUiEvent
|
||||
import com.android.systemui.keyguard.shared.model.AuthenticationStatus
|
||||
import com.android.systemui.keyguard.shared.model.DetectionStatus
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.filterNotNull
|
||||
import kotlinx.coroutines.flow.map
|
||||
|
||||
class FakeDeviceEntryFaceAuthRepository : DeviceEntryFaceAuthRepository {
|
||||
|
||||
override val isAuthenticated = MutableStateFlow(false)
|
||||
override val canRunFaceAuth = MutableStateFlow(false)
|
||||
private val _authenticationStatus = MutableStateFlow<AuthenticationStatus?>(null)
|
||||
override val authenticationStatus: Flow<AuthenticationStatus> =
|
||||
_authenticationStatus.filterNotNull()
|
||||
fun setAuthenticationStatus(status: AuthenticationStatus) {
|
||||
_authenticationStatus.value = status
|
||||
}
|
||||
private val _detectionStatus = MutableStateFlow<DetectionStatus?>(null)
|
||||
override val detectionStatus: Flow<DetectionStatus>
|
||||
get() = _detectionStatus.filterNotNull()
|
||||
fun setDetectionStatus(status: DetectionStatus) {
|
||||
_detectionStatus.value = status
|
||||
}
|
||||
override val isLockedOut = MutableStateFlow(false)
|
||||
private val _runningAuthRequest = MutableStateFlow<Pair<FaceAuthUiEvent, Boolean>?>(null)
|
||||
val runningAuthRequest: StateFlow<Pair<FaceAuthUiEvent, Boolean>?> =
|
||||
_runningAuthRequest.asStateFlow()
|
||||
override val isAuthRunning = _runningAuthRequest.map { it != null }
|
||||
override val isBypassEnabled = MutableStateFlow(false)
|
||||
|
||||
override suspend fun authenticate(uiEvent: FaceAuthUiEvent, fallbackToDetection: Boolean) {
|
||||
_runningAuthRequest.value = uiEvent to fallbackToDetection
|
||||
}
|
||||
|
||||
override fun cancel() {
|
||||
_runningAuthRequest.value = null
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user