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