1 /* 2 * Copyright (C) 2024 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.wm.shell.windowdecor.education 18 19 import android.annotation.LayoutRes 20 import android.content.Context 21 import android.graphics.Point 22 import android.testing.AndroidTestingRunner 23 import android.testing.TestableContext 24 import android.testing.TestableLooper 25 import android.testing.TestableResources 26 import android.view.MotionEvent 27 import android.view.Surface.ROTATION_180 28 import android.view.Surface.ROTATION_90 29 import android.view.View 30 import android.view.WindowManager 31 import android.widget.TextView 32 import android.window.WindowContainerTransaction 33 import androidx.compose.ui.graphics.Color 34 import androidx.compose.ui.graphics.toArgb 35 import androidx.test.filters.SmallTest 36 import com.android.wm.shell.R 37 import com.android.wm.shell.ShellTestCase 38 import com.android.wm.shell.common.DisplayController 39 import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalSystemViewContainer 40 import com.android.wm.shell.windowdecor.education.DesktopWindowingEducationTooltipController.TooltipArrowDirection 41 import com.android.wm.shell.windowdecor.education.DesktopWindowingEducationTooltipController.TooltipColorScheme 42 import com.google.common.truth.Truth.assertThat 43 import org.junit.Before 44 import org.junit.Test 45 import org.junit.runner.RunWith 46 import org.mockito.ArgumentMatchers.anyInt 47 import org.mockito.Mock 48 import org.mockito.MockitoAnnotations 49 import org.mockito.kotlin.any 50 import org.mockito.kotlin.anyOrNull 51 import org.mockito.kotlin.argumentCaptor 52 import org.mockito.kotlin.atLeastOnce 53 import org.mockito.kotlin.mock 54 import org.mockito.kotlin.times 55 import org.mockito.kotlin.verify 56 import org.mockito.kotlin.whenever 57 58 @SmallTest 59 @TestableLooper.RunWithLooper(setAsMainLooper = true) 60 @RunWith(AndroidTestingRunner::class) 61 class DesktopWindowingEducationTooltipControllerTest : ShellTestCase() { 62 @Mock private lateinit var mockWindowManager: WindowManager 63 @Mock private lateinit var mockViewContainerFactory: AdditionalSystemViewContainer.Factory 64 @Mock private lateinit var mockDisplayController: DisplayController 65 @Mock private lateinit var mockPopupWindow: AdditionalSystemViewContainer 66 private lateinit var testableResources: TestableResources 67 private lateinit var testableContext: TestableContext 68 private lateinit var tooltipController: DesktopWindowingEducationTooltipController 69 private val tooltipViewArgumentCaptor = argumentCaptor<View>() 70 71 @Before setUpnull72 fun setUp() { 73 MockitoAnnotations.initMocks(this) 74 testableContext = TestableContext(mContext) 75 testableResources = 76 testableContext.orCreateTestableResources.apply { 77 addOverride(R.dimen.desktop_windowing_education_tooltip_padding, 10) 78 } 79 testableContext.addMockSystemService( 80 Context.LAYOUT_INFLATER_SERVICE, context.getSystemService(Context.LAYOUT_INFLATER_SERVICE)) 81 testableContext.addMockSystemService(WindowManager::class.java, mockWindowManager) 82 tooltipController = 83 DesktopWindowingEducationTooltipController( 84 testableContext, mockViewContainerFactory, mockDisplayController) 85 } 86 87 @Test showEducationTooltip_createsTooltipWithCorrectTextnull88 fun showEducationTooltip_createsTooltipWithCorrectText() { 89 val tooltipText = "This is a tooltip" 90 val tooltipViewConfig = createTooltipConfig(tooltipText = tooltipText) 91 92 tooltipController.showEducationTooltip(tooltipViewConfig = tooltipViewConfig, taskId = 123) 93 94 verify(mockViewContainerFactory, times(1)) 95 .create( 96 windowManagerWrapper = any(), 97 taskId = anyInt(), 98 x = anyInt(), 99 y = anyInt(), 100 width = anyInt(), 101 height = anyInt(), 102 flags = anyInt(), 103 view = tooltipViewArgumentCaptor.capture()) 104 val tooltipTextView = 105 tooltipViewArgumentCaptor.lastValue.findViewById<TextView>(R.id.tooltip_text) 106 assertThat(tooltipTextView.text).isEqualTo(tooltipText) 107 } 108 109 @Test showEducationTooltip_usesCorrectTaskIdForWindownull110 fun showEducationTooltip_usesCorrectTaskIdForWindow() { 111 val tooltipViewConfig = createTooltipConfig() 112 val taskIdArgumentCaptor = argumentCaptor<Int>() 113 114 tooltipController.showEducationTooltip(tooltipViewConfig = tooltipViewConfig, taskId = 123) 115 116 verify(mockViewContainerFactory, times(1)) 117 .create( 118 windowManagerWrapper = any(), 119 taskId = taskIdArgumentCaptor.capture(), 120 x = anyInt(), 121 y = anyInt(), 122 width = anyInt(), 123 height = anyInt(), 124 flags = anyInt(), 125 view = anyOrNull()) 126 assertThat(taskIdArgumentCaptor.lastValue).isEqualTo(123) 127 } 128 129 @Test showEducationTooltip_tooltipPointsUpwards_horizontallyPositionTooltipnull130 fun showEducationTooltip_tooltipPointsUpwards_horizontallyPositionTooltip() { 131 val initialTooltipX = 0 132 val initialTooltipY = 0 133 val tooltipViewConfig = 134 createTooltipConfig( 135 arrowDirection = TooltipArrowDirection.UP, 136 tooltipViewGlobalCoordinates = Point(initialTooltipX, initialTooltipY)) 137 val tooltipXArgumentCaptor = argumentCaptor<Int>() 138 val tooltipWidthArgumentCaptor = argumentCaptor<Int>() 139 140 tooltipController.showEducationTooltip(tooltipViewConfig = tooltipViewConfig, taskId = 123) 141 142 verify(mockViewContainerFactory, times(1)) 143 .create( 144 windowManagerWrapper = any(), 145 taskId = anyInt(), 146 x = tooltipXArgumentCaptor.capture(), 147 y = anyInt(), 148 width = tooltipWidthArgumentCaptor.capture(), 149 height = anyInt(), 150 flags = anyInt(), 151 view = tooltipViewArgumentCaptor.capture()) 152 val expectedTooltipX = initialTooltipX - tooltipWidthArgumentCaptor.lastValue / 2 153 assertThat(tooltipXArgumentCaptor.lastValue).isEqualTo(expectedTooltipX) 154 } 155 156 @Test showEducationTooltip_tooltipPointsLeft_verticallyPositionTooltipnull157 fun showEducationTooltip_tooltipPointsLeft_verticallyPositionTooltip() { 158 val initialTooltipX = 0 159 val initialTooltipY = 0 160 val tooltipViewConfig = 161 createTooltipConfig( 162 arrowDirection = TooltipArrowDirection.LEFT, 163 tooltipViewGlobalCoordinates = Point(initialTooltipX, initialTooltipY)) 164 val tooltipYArgumentCaptor = argumentCaptor<Int>() 165 val tooltipHeightArgumentCaptor = argumentCaptor<Int>() 166 167 tooltipController.showEducationTooltip(tooltipViewConfig = tooltipViewConfig, taskId = 123) 168 169 verify(mockViewContainerFactory, times(1)) 170 .create( 171 windowManagerWrapper = any(), 172 taskId = anyInt(), 173 x = anyInt(), 174 y = tooltipYArgumentCaptor.capture(), 175 width = anyInt(), 176 height = tooltipHeightArgumentCaptor.capture(), 177 flags = anyInt(), 178 view = tooltipViewArgumentCaptor.capture()) 179 val expectedTooltipY = initialTooltipY - tooltipHeightArgumentCaptor.lastValue / 2 180 assertThat(tooltipYArgumentCaptor.lastValue).isEqualTo(expectedTooltipY) 181 } 182 183 @Test showEducationTooltip_touchEventActionOutside_dismissActionPerformednull184 fun showEducationTooltip_touchEventActionOutside_dismissActionPerformed() { 185 val mockLambda: () -> Unit = mock() 186 val tooltipViewConfig = createTooltipConfig(onDismissAction = mockLambda) 187 188 tooltipController.showEducationTooltip(tooltipViewConfig = tooltipViewConfig, taskId = 123) 189 verify(mockViewContainerFactory, times(1)) 190 .create( 191 windowManagerWrapper = any(), 192 taskId = anyInt(), 193 x = anyInt(), 194 y = anyInt(), 195 width = anyInt(), 196 height = anyInt(), 197 flags = anyInt(), 198 view = tooltipViewArgumentCaptor.capture()) 199 val motionEvent = 200 MotionEvent.obtain( 201 /* downTime= */ 0L, 202 /* eventTime= */ 0L, 203 MotionEvent.ACTION_OUTSIDE, 204 /* x= */ 0f, 205 /* y= */ 0f, 206 /* metaState= */ 0) 207 tooltipViewArgumentCaptor.lastValue.dispatchTouchEvent(motionEvent) 208 209 verify(mockLambda).invoke() 210 } 211 212 @Test showEducationTooltip_tooltipClicked_onClickActionPerformednull213 fun showEducationTooltip_tooltipClicked_onClickActionPerformed() { 214 val mockLambda: () -> Unit = mock() 215 val tooltipViewConfig = createTooltipConfig(onEducationClickAction = mockLambda) 216 217 tooltipController.showEducationTooltip(tooltipViewConfig = tooltipViewConfig, taskId = 123) 218 verify(mockViewContainerFactory, times(1)) 219 .create( 220 windowManagerWrapper = any(), 221 taskId = anyInt(), 222 x = anyInt(), 223 y = anyInt(), 224 width = anyInt(), 225 height = anyInt(), 226 flags = anyInt(), 227 view = tooltipViewArgumentCaptor.capture()) 228 tooltipViewArgumentCaptor.lastValue.performClick() 229 230 verify(mockLambda).invoke() 231 } 232 233 @Test showEducationTooltip_displayRotationChanged_hidesTooltipnull234 fun showEducationTooltip_displayRotationChanged_hidesTooltip() { 235 whenever( 236 mockViewContainerFactory.create(any(), any(), any(), any(), any(), any(), any(), any())) 237 .thenReturn(mockPopupWindow) 238 val tooltipViewConfig = createTooltipConfig() 239 240 tooltipController.showEducationTooltip(tooltipViewConfig = tooltipViewConfig, taskId = 123) 241 tooltipController.onDisplayChange( 242 /* displayId= */ 123, 243 /* fromRotation= */ ROTATION_90, 244 /* toRotation= */ ROTATION_180, 245 /* newDisplayAreaInfo= */ null, 246 WindowContainerTransaction(), 247 ) 248 249 verify(mockPopupWindow, times(1)).releaseView() 250 verify(mockDisplayController, atLeastOnce()).removeDisplayChangingController(any()) 251 } 252 253 @Test showEducationTooltip_setTooltipColorScheme_correctColorsAreSetnull254 fun showEducationTooltip_setTooltipColorScheme_correctColorsAreSet() { 255 val tooltipColorScheme = 256 TooltipColorScheme( 257 container = Color.Red.toArgb(), text = Color.Blue.toArgb(), icon = Color.Green.toArgb()) 258 val tooltipViewConfig = createTooltipConfig(tooltipColorScheme = tooltipColorScheme) 259 260 tooltipController.showEducationTooltip(tooltipViewConfig = tooltipViewConfig, taskId = 123) 261 262 verify(mockViewContainerFactory, times(1)) 263 .create( 264 windowManagerWrapper = any(), 265 taskId = anyInt(), 266 x = anyInt(), 267 y = anyInt(), 268 width = anyInt(), 269 height = anyInt(), 270 flags = anyInt(), 271 view = tooltipViewArgumentCaptor.capture()) 272 val tooltipTextView = 273 tooltipViewArgumentCaptor.lastValue.findViewById<TextView>(R.id.tooltip_text) 274 assertThat(tooltipTextView.textColors.defaultColor).isEqualTo(Color.Blue.toArgb()) 275 } 276 createTooltipConfignull277 private fun createTooltipConfig( 278 @LayoutRes tooltipViewLayout: Int = R.layout.desktop_windowing_education_top_arrow_tooltip, 279 tooltipColorScheme: TooltipColorScheme = 280 TooltipColorScheme( 281 container = Color.Red.toArgb(), text = Color.Red.toArgb(), icon = Color.Red.toArgb()), 282 tooltipViewGlobalCoordinates: Point = Point(0, 0), 283 tooltipText: String = "This is a tooltip", 284 arrowDirection: TooltipArrowDirection = TooltipArrowDirection.UP, 285 onEducationClickAction: () -> Unit = {}, <lambda>null286 onDismissAction: () -> Unit = {} 287 ) = 288 DesktopWindowingEducationTooltipController.TooltipEducationViewConfig( 289 tooltipViewLayout = tooltipViewLayout, 290 tooltipColorScheme = tooltipColorScheme, 291 tooltipViewGlobalCoordinates = tooltipViewGlobalCoordinates, 292 tooltipText = tooltipText, 293 arrowDirection = arrowDirection, 294 onEducationClickAction = onEducationClickAction, 295 onDismissAction = onDismissAction, 296 ) 297 } 298