1 /* 2 * Copyright (C) 2021 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.statusbar.phone 18 19 import android.view.LayoutInflater 20 import android.view.MotionEvent 21 import android.view.ViewTreeObserver 22 import android.view.ViewTreeObserver.OnPreDrawListener 23 import android.widget.FrameLayout 24 import androidx.test.filters.SmallTest 25 import androidx.test.platform.app.InstrumentationRegistry 26 import com.android.systemui.R 27 import com.android.systemui.SysuiTestCase 28 import com.android.systemui.flags.FeatureFlags 29 import com.android.systemui.flags.Flags 30 import com.android.systemui.scene.ui.view.WindowRootView 31 import com.android.systemui.shade.ShadeControllerImpl 32 import com.android.systemui.shade.ShadeLogger 33 import com.android.systemui.shade.ShadeViewController 34 import com.android.systemui.statusbar.policy.ConfigurationController 35 import com.android.systemui.unfold.SysUIUnfoldComponent 36 import com.android.systemui.unfold.config.UnfoldTransitionConfig 37 import com.android.systemui.unfold.util.ScopedUnfoldTransitionProgressProvider 38 import com.android.systemui.user.ui.viewmodel.StatusBarUserChipViewModel 39 import com.android.systemui.util.mockito.any 40 import com.android.systemui.util.mockito.whenever 41 import com.android.systemui.util.view.ViewUtil 42 import com.google.common.truth.Truth.assertThat 43 import org.junit.Before 44 import org.junit.Test 45 import org.mockito.ArgumentCaptor 46 import org.mockito.Mock 47 import org.mockito.Mockito.mock 48 import org.mockito.Mockito.never 49 import org.mockito.Mockito.spy 50 import org.mockito.Mockito.verify 51 import org.mockito.Mockito.`when` 52 import org.mockito.MockitoAnnotations 53 import java.util.Optional 54 import javax.inject.Provider 55 56 @SmallTest 57 class PhoneStatusBarViewControllerTest : SysuiTestCase() { 58 59 @Mock 60 private lateinit var shadeViewController: ShadeViewController 61 @Mock 62 private lateinit var featureFlags: FeatureFlags 63 @Mock 64 private lateinit var moveFromCenterAnimation: StatusBarMoveFromCenterAnimationController 65 @Mock 66 private lateinit var sysuiUnfoldComponent: SysUIUnfoldComponent 67 @Mock 68 private lateinit var progressProvider: ScopedUnfoldTransitionProgressProvider 69 @Mock 70 private lateinit var configurationController: ConfigurationController 71 @Mock 72 private lateinit var mStatusOverlayHoverListenerFactory: StatusOverlayHoverListenerFactory 73 @Mock 74 private lateinit var userChipViewModel: StatusBarUserChipViewModel 75 @Mock 76 private lateinit var centralSurfacesImpl: CentralSurfacesImpl 77 @Mock 78 private lateinit var shadeControllerImpl: ShadeControllerImpl 79 @Mock 80 private lateinit var windowRootView: Provider<WindowRootView> 81 @Mock 82 private lateinit var shadeLogger: ShadeLogger 83 @Mock 84 private lateinit var viewUtil: ViewUtil 85 86 private lateinit var view: PhoneStatusBarView 87 private lateinit var controller: PhoneStatusBarViewController 88 89 private val unfoldConfig = UnfoldConfig() 90 91 @Before 92 fun setUp() { 93 MockitoAnnotations.initMocks(this) 94 `when`(sysuiUnfoldComponent.getStatusBarMoveFromCenterAnimationController()) 95 .thenReturn(moveFromCenterAnimation) 96 // create the view and controller on main thread as it requires main looper 97 InstrumentationRegistry.getInstrumentation().runOnMainSync { 98 val parent = FrameLayout(mContext) // add parent to keep layout params 99 view = LayoutInflater.from(mContext) 100 .inflate(R.layout.status_bar, parent, false) as PhoneStatusBarView 101 controller = createAndInitController(view) 102 } 103 } 104 105 @Test 106 fun onViewAttachedAndDrawn_startListeningConfigurationControllerCallback() { 107 val view = createViewMock() 108 val argumentCaptor = ArgumentCaptor.forClass( 109 ConfigurationController.ConfigurationListener::class.java) 110 InstrumentationRegistry.getInstrumentation().runOnMainSync { 111 controller = createAndInitController(view) 112 } 113 114 verify(configurationController).addCallback(argumentCaptor.capture()) 115 argumentCaptor.value.onDensityOrFontScaleChanged() 116 117 verify(view).onDensityOrFontScaleChanged() 118 } 119 120 @Test 121 fun onViewAttachedAndDrawn_moveFromCenterAnimationEnabled_moveFromCenterAnimationInitialized() { 122 whenever(featureFlags.isEnabled(Flags.ENABLE_UNFOLD_STATUS_BAR_ANIMATIONS)) 123 .thenReturn(true) 124 val view = createViewMock() 125 val argumentCaptor = ArgumentCaptor.forClass(OnPreDrawListener::class.java) 126 unfoldConfig.isEnabled = true 127 // create the controller on main thread as it requires main looper 128 InstrumentationRegistry.getInstrumentation().runOnMainSync { 129 controller = createAndInitController(view) 130 } 131 132 verify(view.viewTreeObserver).addOnPreDrawListener(argumentCaptor.capture()) 133 argumentCaptor.value.onPreDraw() 134 135 verify(moveFromCenterAnimation).onViewsReady(any()) 136 } 137 138 @Test 139 fun onViewAttachedAndDrawn_statusBarAnimationDisabled_animationNotInitialized() { 140 whenever(featureFlags.isEnabled(Flags.ENABLE_UNFOLD_STATUS_BAR_ANIMATIONS)) 141 .thenReturn(false) 142 val view = createViewMock() 143 unfoldConfig.isEnabled = true 144 // create the controller on main thread as it requires main looper 145 InstrumentationRegistry.getInstrumentation().runOnMainSync { 146 controller = createAndInitController(view) 147 } 148 149 verify(moveFromCenterAnimation, never()).onViewsReady(any()) 150 } 151 152 @Test 153 fun handleTouchEventFromStatusBar_panelsNotEnabled_returnsFalseAndNoViewEvent() { 154 `when`(centralSurfacesImpl.commandQueuePanelsEnabled).thenReturn(false) 155 val returnVal = view.onTouchEvent( 156 MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0)) 157 assertThat(returnVal).isFalse() 158 verify(shadeViewController, never()).handleExternalTouch(any()) 159 } 160 161 @Test 162 fun handleTouchEventFromStatusBar_viewNotEnabled_returnsTrueAndNoViewEvent() { 163 `when`(centralSurfacesImpl.commandQueuePanelsEnabled).thenReturn(true) 164 `when`(shadeViewController.isViewEnabled).thenReturn(false) 165 val returnVal = view.onTouchEvent( 166 MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0)) 167 assertThat(returnVal).isTrue() 168 verify(shadeViewController, never()).handleExternalTouch(any()) 169 } 170 171 @Test 172 fun handleTouchEventFromStatusBar_viewNotEnabledButIsMoveEvent_viewReceivesEvent() { 173 `when`(centralSurfacesImpl.commandQueuePanelsEnabled).thenReturn(true) 174 `when`(shadeViewController.isViewEnabled).thenReturn(false) 175 val event = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_MOVE, 0f, 0f, 0) 176 177 view.onTouchEvent(event) 178 179 verify(shadeViewController).handleExternalTouch(event) 180 } 181 182 @Test 183 fun handleTouchEventFromStatusBar_panelAndViewEnabled_viewReceivesEvent() { 184 `when`(centralSurfacesImpl.commandQueuePanelsEnabled).thenReturn(true) 185 `when`(shadeViewController.isViewEnabled).thenReturn(true) 186 val event = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 2f, 0) 187 188 view.onTouchEvent(event) 189 190 verify(shadeViewController).handleExternalTouch(event) 191 } 192 193 @Test 194 fun handleTouchEventFromStatusBar_topEdgeTouch_viewNeverReceivesEvent() { 195 `when`(centralSurfacesImpl.commandQueuePanelsEnabled).thenReturn(true) 196 `when`(shadeViewController.isFullyCollapsed).thenReturn(true) 197 val event = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0) 198 199 view.onTouchEvent(event) 200 201 verify(shadeViewController, never()).handleExternalTouch(any()) 202 } 203 204 private fun createViewMock(): PhoneStatusBarView { 205 val view = spy(view) 206 val viewTreeObserver = mock(ViewTreeObserver::class.java) 207 `when`(view.viewTreeObserver).thenReturn(viewTreeObserver) 208 `when`(view.isAttachedToWindow).thenReturn(true) 209 return view 210 } 211 212 private fun createAndInitController(view: PhoneStatusBarView): PhoneStatusBarViewController { 213 return PhoneStatusBarViewController.Factory( 214 Optional.of(sysuiUnfoldComponent), 215 Optional.of(progressProvider), 216 featureFlags, 217 userChipViewModel, 218 centralSurfacesImpl, 219 shadeControllerImpl, 220 shadeViewController, 221 windowRootView, 222 shadeLogger, 223 viewUtil, 224 configurationController, 225 mStatusOverlayHoverListenerFactory 226 ).create(view).also { 227 it.init() 228 } 229 } 230 231 private class UnfoldConfig : UnfoldTransitionConfig { 232 override var isEnabled: Boolean = false 233 override var isHingeAngleEnabled: Boolean = false 234 override val halfFoldedTimeoutMillis: Int = 0 235 } 236 } 237