<lambda>null1 package com.android.systemui.screenshot
2
3 import android.content.ComponentName
4 import android.graphics.Bitmap
5 import android.net.Uri
6 import android.testing.AndroidTestingRunner
7 import android.view.Display
8 import android.view.Display.TYPE_EXTERNAL
9 import android.view.Display.TYPE_INTERNAL
10 import android.view.Display.TYPE_OVERLAY
11 import android.view.Display.TYPE_VIRTUAL
12 import android.view.Display.TYPE_WIFI
13 import android.view.WindowManager
14 import android.view.WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE
15 import androidx.test.filters.SmallTest
16 import com.android.internal.logging.testing.UiEventLoggerFake
17 import com.android.internal.util.ScreenshotRequest
18 import com.android.systemui.SysuiTestCase
19 import com.android.systemui.display.data.repository.FakeDisplayRepository
20 import com.android.systemui.display.data.repository.display
21 import com.android.systemui.util.mockito.any
22 import com.android.systemui.util.mockito.eq
23 import com.android.systemui.util.mockito.kotlinArgumentCaptor as ArgumentCaptor
24 import com.android.systemui.util.mockito.mock
25 import com.android.systemui.util.mockito.whenever
26 import com.google.common.truth.Truth.assertThat
27 import java.lang.IllegalStateException
28 import java.util.function.Consumer
29 import kotlinx.coroutines.test.TestScope
30 import kotlinx.coroutines.test.UnconfinedTestDispatcher
31 import kotlinx.coroutines.test.runCurrent
32 import kotlinx.coroutines.test.runTest
33 import org.junit.Before
34 import org.junit.Test
35 import org.junit.runner.RunWith
36 import org.mockito.Mockito.never
37 import org.mockito.Mockito.times
38 import org.mockito.Mockito.verify
39 import org.mockito.Mockito.verifyNoMoreInteractions
40
41 @RunWith(AndroidTestingRunner::class)
42 @SmallTest
43 class TakeScreenshotExecutorTest : SysuiTestCase() {
44
45 private val controller = mock<ScreenshotController>()
46 private val notificationsController0 = mock<ScreenshotNotificationsController>()
47 private val notificationsController1 = mock<ScreenshotNotificationsController>()
48 private val controllerFactory = mock<ScreenshotController.Factory>()
49 private val callback = mock<TakeScreenshotService.RequestCallback>()
50 private val notificationControllerFactory = mock<ScreenshotNotificationsController.Factory>()
51
52 private val fakeDisplayRepository = FakeDisplayRepository()
53 private val requestProcessor = FakeRequestProcessor()
54 private val topComponent = ComponentName(mContext, TakeScreenshotExecutorTest::class.java)
55 private val testScope = TestScope(UnconfinedTestDispatcher())
56 private val eventLogger = UiEventLoggerFake()
57 private val headlessHandler = mock<HeadlessScreenshotHandler>()
58
59 private val screenshotExecutor =
60 TakeScreenshotExecutorImpl(
61 controllerFactory,
62 fakeDisplayRepository,
63 testScope,
64 requestProcessor,
65 eventLogger,
66 notificationControllerFactory,
67 headlessHandler,
68 )
69
70 @Before
71 fun setUp() {
72 whenever(controllerFactory.create(any(), any())).thenReturn(controller)
73 whenever(notificationControllerFactory.create(eq(0))).thenReturn(notificationsController0)
74 whenever(notificationControllerFactory.create(eq(1))).thenReturn(notificationsController1)
75 }
76
77 @Test
78 fun executeScreenshots_severalDisplays_callsControllerForEachOne() =
79 testScope.runTest {
80 val internalDisplay = display(TYPE_INTERNAL, id = 0)
81 val externalDisplay = display(TYPE_EXTERNAL, id = 1)
82 setDisplays(internalDisplay, externalDisplay)
83 val onSaved = { _: Uri? -> }
84 screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback)
85
86 verify(controllerFactory).create(eq(internalDisplay), any())
87 verify(controllerFactory, never()).create(eq(externalDisplay), any())
88
89 val capturer = ArgumentCaptor<ScreenshotData>()
90
91 verify(controller).handleScreenshot(capturer.capture(), any(), any())
92 assertThat(capturer.value.displayId).isEqualTo(0)
93 // OnSaved callback should be different.
94 verify(headlessHandler).handleScreenshot(capturer.capture(), any(), any())
95 assertThat(capturer.value.displayId).isEqualTo(1)
96
97 assertThat(eventLogger.numLogs()).isEqualTo(2)
98 assertThat(eventLogger.get(0).eventId)
99 .isEqualTo(ScreenshotEvent.SCREENSHOT_REQUESTED_KEY_OTHER.id)
100 assertThat(eventLogger.get(0).packageName).isEqualTo(topComponent.packageName)
101 assertThat(eventLogger.get(1).eventId)
102 .isEqualTo(ScreenshotEvent.SCREENSHOT_REQUESTED_KEY_OTHER.id)
103 assertThat(eventLogger.get(1).packageName).isEqualTo(topComponent.packageName)
104
105 screenshotExecutor.onDestroy()
106 }
107
108 @Test
109 fun executeScreenshots_providedImageType_callsOnlyDefaultDisplayController() =
110 testScope.runTest {
111 val internalDisplay = display(TYPE_INTERNAL, id = 0)
112 val externalDisplay = display(TYPE_EXTERNAL, id = 1)
113 setDisplays(internalDisplay, externalDisplay)
114 val onSaved = { _: Uri? -> }
115 screenshotExecutor.executeScreenshots(
116 createScreenshotRequest(TAKE_SCREENSHOT_PROVIDED_IMAGE),
117 onSaved,
118 callback
119 )
120
121 verify(controllerFactory).create(eq(internalDisplay), any())
122 verify(controllerFactory, never()).create(eq(externalDisplay), any())
123
124 val capturer = ArgumentCaptor<ScreenshotData>()
125
126 verify(controller).handleScreenshot(capturer.capture(), any(), any())
127 assertThat(capturer.value.displayId).isEqualTo(0)
128 // OnSaved callback should be different.
129 verify(headlessHandler, never()).handleScreenshot(any(), any(), any())
130
131 assertThat(eventLogger.numLogs()).isEqualTo(1)
132 assertThat(eventLogger.get(0).eventId)
133 .isEqualTo(ScreenshotEvent.SCREENSHOT_REQUESTED_KEY_OTHER.id)
134 assertThat(eventLogger.get(0).packageName).isEqualTo(topComponent.packageName)
135
136 screenshotExecutor.onDestroy()
137 }
138
139 @Test
140 fun executeScreenshots_onlyVirtualDisplays_noInteractionsWithControllers() =
141 testScope.runTest {
142 setDisplays(display(TYPE_VIRTUAL, id = 0), display(TYPE_VIRTUAL, id = 1))
143 val onSaved = { _: Uri? -> }
144 screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback)
145
146 verifyNoMoreInteractions(controllerFactory)
147 verify(headlessHandler, never()).handleScreenshot(any(), any(), any())
148 screenshotExecutor.onDestroy()
149 }
150
151 @Test
152 fun executeScreenshots_allowedTypes_allCaptured() =
153 testScope.runTest {
154 whenever(controllerFactory.create(any(), any())).thenReturn(controller)
155
156 setDisplays(
157 display(TYPE_INTERNAL, id = 0),
158 display(TYPE_EXTERNAL, id = 1),
159 display(TYPE_OVERLAY, id = 2),
160 display(TYPE_WIFI, id = 3)
161 )
162 val onSaved = { _: Uri? -> }
163 screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback)
164
165 verify(controller, times(1)).handleScreenshot(any(), any(), any())
166 verify(headlessHandler, times(3)).handleScreenshot(any(), any(), any())
167 screenshotExecutor.onDestroy()
168 }
169
170 @Test
171 fun executeScreenshots_reportsOnFinishedOnlyWhenBothFinished() =
172 testScope.runTest {
173 setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1))
174 val onSaved = { _: Uri? -> }
175 screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback)
176
177 val capturer0 = ArgumentCaptor<TakeScreenshotService.RequestCallback>()
178 val capturer1 = ArgumentCaptor<TakeScreenshotService.RequestCallback>()
179
180 verify(controller).handleScreenshot(any(), any(), capturer0.capture())
181 verify(headlessHandler).handleScreenshot(any(), any(), capturer1.capture())
182
183 verify(callback, never()).onFinish()
184
185 capturer0.value.onFinish()
186
187 verify(callback, never()).onFinish()
188
189 capturer1.value.onFinish()
190
191 verify(callback).onFinish()
192 screenshotExecutor.onDestroy()
193 }
194
195 @Test
196 fun executeScreenshots_oneFinishesOtherFails_reportFailsOnlyAtTheEnd() =
197 testScope.runTest {
198 setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1))
199 val onSaved = { _: Uri? -> }
200 screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback)
201
202 val capturer0 = ArgumentCaptor<TakeScreenshotService.RequestCallback>()
203 val capturer1 = ArgumentCaptor<TakeScreenshotService.RequestCallback>()
204
205 verify(controller).handleScreenshot(any(), any(), capturer0.capture())
206 verify(headlessHandler).handleScreenshot(any(), any(), capturer1.capture())
207
208 verify(callback, never()).onFinish()
209
210 capturer0.value.onFinish()
211
212 verify(callback, never()).onFinish()
213
214 capturer1.value.reportError()
215
216 verify(callback, never()).onFinish()
217 verify(callback).reportError()
218
219 screenshotExecutor.onDestroy()
220 }
221
222 @Test
223 fun executeScreenshots_allDisplaysFail_reportsFail() =
224 testScope.runTest {
225 setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1))
226 val onSaved = { _: Uri? -> }
227 screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback)
228
229 val capturer0 = ArgumentCaptor<TakeScreenshotService.RequestCallback>()
230 val capturer1 = ArgumentCaptor<TakeScreenshotService.RequestCallback>()
231
232 verify(controller).handleScreenshot(any(), any(), capturer0.capture())
233 verify(headlessHandler).handleScreenshot(any(), any(), capturer1.capture())
234
235 verify(callback, never()).onFinish()
236
237 capturer0.value.reportError()
238
239 verify(callback, never()).onFinish()
240 verify(callback, never()).reportError()
241
242 capturer1.value.reportError()
243
244 verify(callback, never()).onFinish()
245 verify(callback).reportError()
246 screenshotExecutor.onDestroy()
247 }
248
249 @Test
250 fun onDestroy_propagatedToControllers() =
251 testScope.runTest {
252 setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1))
253 val onSaved = { _: Uri? -> }
254 screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback)
255
256 screenshotExecutor.onDestroy()
257 verify(controller).onDestroy()
258 }
259
260 @Test
261 fun removeWindows_propagatedToController() =
262 testScope.runTest {
263 setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1))
264 val onSaved = { _: Uri? -> }
265 screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback)
266
267 screenshotExecutor.removeWindows()
268 verify(controller).removeWindow()
269
270 screenshotExecutor.onDestroy()
271 }
272
273 @Test
274 fun onCloseSystemDialogsReceived_propagatedToController() =
275 testScope.runTest {
276 setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1))
277 val onSaved = { _: Uri? -> }
278 screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback)
279
280 screenshotExecutor.onCloseSystemDialogsReceived()
281 verify(controller).requestDismissal(any())
282
283 screenshotExecutor.onDestroy()
284 }
285
286 @Test
287 fun onCloseSystemDialogsReceived_controllerHasPendingTransitions() =
288 testScope.runTest {
289 setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1))
290 whenever(controller.isPendingSharedTransition).thenReturn(true)
291 val onSaved = { _: Uri? -> }
292 screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback)
293
294 screenshotExecutor.onCloseSystemDialogsReceived()
295 verify(controller, never()).requestDismissal(any())
296
297 screenshotExecutor.onDestroy()
298 }
299
300 @Test
301 fun executeScreenshots_controllerCalledWithRequestProcessorReturnValue() =
302 testScope.runTest {
303 setDisplays(display(TYPE_INTERNAL, id = 0))
304 val screenshotRequest = createScreenshotRequest()
305 val toBeReturnedByProcessor = ScreenshotData.forTesting()
306 requestProcessor.toReturn = toBeReturnedByProcessor
307
308 val onSaved = { _: Uri? -> }
309 screenshotExecutor.executeScreenshots(screenshotRequest, onSaved, callback)
310
311 assertThat(requestProcessor.processed)
312 .isEqualTo(ScreenshotData.fromRequest(screenshotRequest))
313
314 val capturer = ArgumentCaptor<ScreenshotData>()
315 verify(controller).handleScreenshot(capturer.capture(), any(), any())
316 assertThat(capturer.value).isEqualTo(toBeReturnedByProcessor)
317
318 screenshotExecutor.onDestroy()
319 }
320
321 @Test
322 fun executeScreenshots_errorFromProcessor_logsScreenshotRequested() =
323 testScope.runTest {
324 setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1))
325 val onSaved = { _: Uri? -> }
326 requestProcessor.shouldThrowException = true
327
328 screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback)
329
330 val screenshotRequested =
331 eventLogger.logs.filter {
332 it.eventId == ScreenshotEvent.SCREENSHOT_REQUESTED_KEY_OTHER.id
333 }
334 assertThat(screenshotRequested).hasSize(2)
335 screenshotExecutor.onDestroy()
336 }
337
338 @Test
339 fun executeScreenshots_errorFromProcessor_logsUiError() =
340 testScope.runTest {
341 setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1))
342 val onSaved = { _: Uri? -> }
343 requestProcessor.shouldThrowException = true
344
345 screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback)
346
347 val screenshotRequested =
348 eventLogger.logs.filter {
349 it.eventId == ScreenshotEvent.SCREENSHOT_CAPTURE_FAILED.id
350 }
351 assertThat(screenshotRequested).hasSize(2)
352 screenshotExecutor.onDestroy()
353 }
354
355 @Test
356 fun executeScreenshots_errorFromProcessorOnDefaultDisplay_showsErrorNotification() =
357 testScope.runTest {
358 setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1))
359 val onSaved = { _: Uri? -> }
360 requestProcessor.shouldThrowException = true
361
362 screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback)
363
364 verify(notificationsController0).notifyScreenshotError(any())
365 screenshotExecutor.onDestroy()
366 }
367
368 @Test
369 fun executeScreenshots_errorFromProcessorOnSecondaryDisplay_showsErrorNotification() =
370 testScope.runTest {
371 setDisplays(display(TYPE_INTERNAL, id = 0))
372 val onSaved = { _: Uri? -> }
373 requestProcessor.shouldThrowException = true
374
375 screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback)
376
377 verify(notificationsController0).notifyScreenshotError(any())
378 screenshotExecutor.onDestroy()
379 }
380
381 @Test
382 fun executeScreenshots_errorFromScreenshotController_reportsRequested() =
383 testScope.runTest {
384 setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1))
385 val onSaved = { _: Uri? -> }
386 whenever(controller.handleScreenshot(any(), any(), any()))
387 .thenThrow(IllegalStateException::class.java)
388 whenever(headlessHandler.handleScreenshot(any(), any(), any()))
389 .thenThrow(IllegalStateException::class.java)
390
391 screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback)
392
393 val screenshotRequested =
394 eventLogger.logs.filter {
395 it.eventId == ScreenshotEvent.SCREENSHOT_REQUESTED_KEY_OTHER.id
396 }
397 assertThat(screenshotRequested).hasSize(2)
398 screenshotExecutor.onDestroy()
399 }
400
401 @Test
402 fun executeScreenshots_errorFromScreenshotController_reportsError() =
403 testScope.runTest {
404 setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1))
405 val onSaved = { _: Uri? -> }
406 whenever(controller.handleScreenshot(any(), any(), any()))
407 .thenThrow(IllegalStateException::class.java)
408 whenever(headlessHandler.handleScreenshot(any(), any(), any()))
409 .thenThrow(IllegalStateException::class.java)
410
411 screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback)
412
413 val screenshotRequested =
414 eventLogger.logs.filter {
415 it.eventId == ScreenshotEvent.SCREENSHOT_CAPTURE_FAILED.id
416 }
417 assertThat(screenshotRequested).hasSize(2)
418 screenshotExecutor.onDestroy()
419 }
420
421 @Test
422 fun executeScreenshots_errorFromScreenshotController_showsErrorNotification() =
423 testScope.runTest {
424 setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1))
425 val onSaved = { _: Uri? -> }
426 whenever(controller.handleScreenshot(any(), any(), any()))
427 .thenThrow(IllegalStateException::class.java)
428 whenever(headlessHandler.handleScreenshot(any(), any(), any()))
429 .thenThrow(IllegalStateException::class.java)
430
431 screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback)
432
433 verify(notificationsController0).notifyScreenshotError(any())
434 verify(notificationsController1).notifyScreenshotError(any())
435 screenshotExecutor.onDestroy()
436 }
437
438 @Test
439 fun executeScreenshots_finisherCalledWithNullUri_succeeds() =
440 testScope.runTest {
441 setDisplays(display(TYPE_INTERNAL, id = 0))
442 var onSavedCallCount = 0
443 val onSaved: (Uri?) -> Unit = {
444 assertThat(it).isNull()
445 onSavedCallCount += 1
446 }
447 whenever(controller.handleScreenshot(any(), any(), any())).thenAnswer {
448 (it.getArgument(1) as Consumer<Uri?>).accept(null)
449 }
450
451 screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback)
452 assertThat(onSavedCallCount).isEqualTo(1)
453
454 screenshotExecutor.onDestroy()
455 }
456
457 private suspend fun TestScope.setDisplays(vararg displays: Display) {
458 fakeDisplayRepository.emit(displays.toSet())
459 runCurrent()
460 }
461
462 private fun createScreenshotRequest(type: Int = WindowManager.TAKE_SCREENSHOT_FULLSCREEN) =
463 ScreenshotRequest.Builder(type, WindowManager.ScreenshotSource.SCREENSHOT_KEY_OTHER)
464 .setTopComponent(topComponent)
465 .also {
466 if (type == TAKE_SCREENSHOT_PROVIDED_IMAGE) {
467 it.setBitmap(Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888))
468 }
469 }
470 .build()
471
472 private class FakeRequestProcessor : ScreenshotRequestProcessor {
473 var processed: ScreenshotData? = null
474 var toReturn: ScreenshotData? = null
475 var shouldThrowException = false
476
477 override suspend fun process(screenshot: ScreenshotData): ScreenshotData {
478 if (shouldThrowException) throw RequestProcessorException("")
479 processed = screenshot
480 return toReturn ?: screenshot
481 }
482 }
483 }
484