1 /* 2 * Copyright (C) 2023 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.systemui.keyguard.ui.viewmodel 18 19 import androidx.test.filters.SmallTest 20 import com.android.systemui.SysuiTestCase 21 import com.android.systemui.coroutines.collectLastValue 22 import com.android.systemui.doze.util.BurnInHelperWrapper 23 import com.android.systemui.flags.FeatureFlags 24 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository 25 import com.android.systemui.keyguard.domain.interactor.KeyguardBottomAreaInteractor 26 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory 27 import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancePosition 28 import com.android.systemui.util.mockito.any 29 import com.android.systemui.util.mockito.mock 30 import com.android.systemui.util.mockito.whenever 31 import com.google.common.truth.Truth.assertThat 32 import kotlinx.coroutines.ExperimentalCoroutinesApi 33 import kotlinx.coroutines.flow.MutableStateFlow 34 import kotlinx.coroutines.test.runTest 35 import org.junit.Before 36 import org.junit.Test 37 import org.junit.runner.RunWith 38 import org.junit.runners.JUnit4 39 import org.mockito.ArgumentMatchers.anyInt 40 import org.mockito.Mock 41 import org.mockito.MockitoAnnotations 42 43 @OptIn(ExperimentalCoroutinesApi::class) 44 @SmallTest 45 @RunWith(JUnit4::class) 46 class KeyguardIndicationAreaViewModelTest : SysuiTestCase() { 47 48 @Mock private lateinit var burnInHelperWrapper: BurnInHelperWrapper 49 @Mock private lateinit var shortcutsCombinedViewModel: KeyguardQuickAffordancesCombinedViewModel 50 @Mock private lateinit var featureFlags: FeatureFlags 51 52 private lateinit var underTest: KeyguardIndicationAreaViewModel 53 private lateinit var repository: FakeKeyguardRepository 54 55 private val startButtonFlow = 56 MutableStateFlow<KeyguardQuickAffordanceViewModel>( 57 KeyguardQuickAffordanceViewModel( 58 slotId = KeyguardQuickAffordancePosition.BOTTOM_START.toSlotId() 59 ) 60 ) 61 private val endButtonFlow = 62 MutableStateFlow<KeyguardQuickAffordanceViewModel>( 63 KeyguardQuickAffordanceViewModel( 64 slotId = KeyguardQuickAffordancePosition.BOTTOM_END.toSlotId() 65 ) 66 ) 67 private val alphaFlow = MutableStateFlow<Float>(1f) 68 69 @Before 70 fun setUp() { 71 MockitoAnnotations.initMocks(this) 72 whenever(burnInHelperWrapper.burnInOffset(anyInt(), any())) 73 .thenReturn(RETURNED_BURN_IN_OFFSET) 74 75 val withDeps = KeyguardInteractorFactory.create() 76 val keyguardInteractor = withDeps.keyguardInteractor 77 repository = withDeps.repository 78 79 val bottomAreaViewModel: KeyguardBottomAreaViewModel = mock() 80 whenever(bottomAreaViewModel.startButton).thenReturn(startButtonFlow) 81 whenever(bottomAreaViewModel.endButton).thenReturn(endButtonFlow) 82 whenever(bottomAreaViewModel.alpha).thenReturn(alphaFlow) 83 underTest = 84 KeyguardIndicationAreaViewModel( 85 keyguardInteractor = keyguardInteractor, 86 bottomAreaInteractor = KeyguardBottomAreaInteractor(repository = repository), 87 keyguardBottomAreaViewModel = bottomAreaViewModel, 88 burnInHelperWrapper = burnInHelperWrapper, 89 shortcutsCombinedViewModel = shortcutsCombinedViewModel, 90 featureFlags = featureFlags, 91 ) 92 } 93 94 @Test 95 fun alpha() = runTest { 96 val value = collectLastValue(underTest.alpha) 97 98 assertThat(value()).isEqualTo(1f) 99 alphaFlow.value = 0.1f 100 assertThat(value()).isEqualTo(0.1f) 101 alphaFlow.value = 0.5f 102 assertThat(value()).isEqualTo(0.5f) 103 alphaFlow.value = 0.2f 104 assertThat(value()).isEqualTo(0.2f) 105 alphaFlow.value = 0f 106 assertThat(value()).isEqualTo(0f) 107 } 108 109 @Test 110 fun isIndicationAreaPadded() = runTest { 111 repository.setKeyguardShowing(true) 112 val value = collectLastValue(underTest.isIndicationAreaPadded) 113 114 assertThat(value()).isFalse() 115 startButtonFlow.value = startButtonFlow.value.copy(isVisible = true) 116 assertThat(value()).isTrue() 117 endButtonFlow.value = endButtonFlow.value.copy(isVisible = true) 118 assertThat(value()).isTrue() 119 startButtonFlow.value = startButtonFlow.value.copy(isVisible = false) 120 assertThat(value()).isTrue() 121 endButtonFlow.value = endButtonFlow.value.copy(isVisible = false) 122 assertThat(value()).isFalse() 123 } 124 125 @Test 126 fun indicationAreaTranslationX() = runTest { 127 val value = collectLastValue(underTest.indicationAreaTranslationX) 128 129 assertThat(value()).isEqualTo(0f) 130 repository.setClockPosition(100, 100) 131 assertThat(value()).isEqualTo(100f) 132 repository.setClockPosition(200, 100) 133 assertThat(value()).isEqualTo(200f) 134 repository.setClockPosition(200, 200) 135 assertThat(value()).isEqualTo(200f) 136 repository.setClockPosition(300, 100) 137 assertThat(value()).isEqualTo(300f) 138 } 139 140 @Test 141 fun indicationAreaTranslationY() = runTest { 142 val value = collectLastValue(underTest.indicationAreaTranslationY(DEFAULT_BURN_IN_OFFSET)) 143 144 // Negative 0 - apparently there's a difference in floating point arithmetic - FML 145 assertThat(value()).isEqualTo(-0f) 146 val expected1 = setDozeAmountAndCalculateExpectedTranslationY(0.1f) 147 assertThat(value()).isEqualTo(expected1) 148 val expected2 = setDozeAmountAndCalculateExpectedTranslationY(0.2f) 149 assertThat(value()).isEqualTo(expected2) 150 val expected3 = setDozeAmountAndCalculateExpectedTranslationY(0.5f) 151 assertThat(value()).isEqualTo(expected3) 152 val expected4 = setDozeAmountAndCalculateExpectedTranslationY(1f) 153 assertThat(value()).isEqualTo(expected4) 154 } 155 156 private fun setDozeAmountAndCalculateExpectedTranslationY(dozeAmount: Float): Float { 157 repository.setDozeAmount(dozeAmount) 158 return dozeAmount * (RETURNED_BURN_IN_OFFSET - DEFAULT_BURN_IN_OFFSET) 159 } 160 161 companion object { 162 private const val DEFAULT_BURN_IN_OFFSET = 5 163 private const val RETURNED_BURN_IN_OFFSET = 3 164 } 165 } 166