1 /*
2  * Copyright (C) 2022 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 package com.android.systemui.screenshot
17 
18 import android.app.Application
19 import android.app.admin.DevicePolicyManager
20 import android.app.admin.DevicePolicyResources.Strings.SystemUi.SCREENSHOT_BLOCKED_BY_ADMIN
21 import android.app.admin.DevicePolicyResourcesManager
22 import android.content.ComponentName
23 import android.graphics.Bitmap
24 import android.graphics.Bitmap.Config.HARDWARE
25 import android.graphics.ColorSpace
26 import android.hardware.HardwareBuffer
27 import android.os.UserHandle
28 import android.os.UserManager
29 import android.testing.AndroidTestingRunner
30 import android.view.WindowManager.ScreenshotSource.SCREENSHOT_KEY_OTHER
31 import android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN
32 import androidx.test.filters.SmallTest
33 import com.android.internal.logging.testing.UiEventLoggerFake
34 import com.android.internal.util.ScreenshotRequest
35 import com.android.systemui.SysuiTestCase
36 import com.android.systemui.flags.FakeFeatureFlags
37 import com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_CAPTURE_FAILED
38 import com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_REQUESTED_KEY_OTHER
39 import com.android.systemui.screenshot.TakeScreenshotService.RequestCallback
40 import com.android.systemui.util.mockito.any
41 import com.android.systemui.util.mockito.eq
42 import com.android.systemui.util.mockito.mock
43 import com.android.systemui.util.mockito.whenever
44 import java.util.function.Consumer
45 import org.junit.Assert.assertEquals
46 import org.junit.Before
47 import org.junit.Test
48 import org.junit.runner.RunWith
49 import org.mockito.ArgumentMatchers.anyInt
50 import org.mockito.ArgumentMatchers.isNull
51 import org.mockito.Mockito.doAnswer
52 import org.mockito.Mockito.doThrow
53 import org.mockito.Mockito.times
54 import org.mockito.Mockito.verify
55 import org.mockito.Mockito.verifyZeroInteractions
56 
57 @RunWith(AndroidTestingRunner::class)
58 @SmallTest
59 class TakeScreenshotServiceTest : SysuiTestCase() {
60 
61     private val application = mock<Application>()
62     private val controller = mock<ScreenshotController>()
63     private val userManager = mock<UserManager>()
64     private val requestProcessor = mock<RequestProcessor>()
65     private val devicePolicyManager = mock<DevicePolicyManager>()
66     private val devicePolicyResourcesManager = mock<DevicePolicyResourcesManager>()
67     private val notificationsController = mock<ScreenshotNotificationsController>()
68     private val callback = mock<RequestCallback>()
69 
70     private val eventLogger = UiEventLoggerFake()
71     private val flags = FakeFeatureFlags()
72     private val topComponent = ComponentName(mContext, TakeScreenshotServiceTest::class.java)
73 
74     private val service =
75         TakeScreenshotService(
76             controller,
77             userManager,
78             devicePolicyManager,
79             eventLogger,
80             notificationsController,
81             mContext,
82             Runnable::run,
83             flags,
84             requestProcessor
85         )
86 
87     @Before
88     fun setUp() {
89         whenever(devicePolicyManager.resources).thenReturn(devicePolicyResourcesManager)
90         whenever(
91                 devicePolicyManager.getScreenCaptureDisabled(
92                     /* admin component (null: any admin) */ isNull(),
93                     eq(UserHandle.USER_ALL)
94                 )
95             )
96             .thenReturn(false)
97         whenever(userManager.isUserUnlocked).thenReturn(true)
98 
99         // Stub request processor as a synchronous no-op for tests with the flag enabled
100         doAnswer {
101                 val request: ScreenshotRequest = it.getArgument(0) as ScreenshotRequest
102                 val consumer: Consumer<ScreenshotRequest> = it.getArgument(1)
103                 consumer.accept(request)
104             }
105             .whenever(requestProcessor)
106             .processAsync(/* request= */ any(ScreenshotRequest::class.java), /* callback= */ any())
107 
108         doAnswer {
109                 val request: ScreenshotData = it.getArgument(0) as ScreenshotData
110                 val consumer: Consumer<ScreenshotData> = it.getArgument(1)
111                 consumer.accept(request)
112             }
113             .whenever(requestProcessor)
114             .processAsync(/* screenshot= */ any(ScreenshotData::class.java), /* callback= */ any())
115 
116         service.attach(
117             mContext,
118             /* thread = */ null,
119             /* className = */ null,
120             /* token = */ null,
121             application,
122             /* activityManager = */ null
123         )
124     }
125 
126     @Test
127     fun testServiceLifecycle() {
128         service.onCreate()
129         service.onBind(null /* unused: Intent */)
130 
131         service.onUnbind(null /* unused: Intent */)
132         verify(controller, times(1)).removeWindow()
133 
134         service.onDestroy()
135         verify(controller, times(1)).onDestroy()
136     }
137 
138     @Test
139     fun takeScreenshotFullscreen() {
140         val request =
141             ScreenshotRequest.Builder(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_OTHER)
142                 .setTopComponent(topComponent)
143                 .build()
144 
145         service.handleRequest(request, { /* onSaved */}, callback)
146 
147         verify(controller, times(1))
148             .handleScreenshot(
149                 eq(ScreenshotData.fromRequest(request)),
150                 /* onSavedListener = */ any(),
151                 /* requestCallback = */ any()
152             )
153 
154         assertEquals("Expected one UiEvent", eventLogger.numLogs(), 1)
155         val logEvent = eventLogger.get(0)
156 
157         assertEquals(
158             "Expected SCREENSHOT_REQUESTED UiEvent",
159             logEvent.eventId,
160             SCREENSHOT_REQUESTED_KEY_OTHER.id
161         )
162         assertEquals(
163             "Expected supplied package name",
164             topComponent.packageName,
165             eventLogger.get(0).packageName
166         )
167     }
168 
169     @Test
170     fun takeScreenshotFullscreen_userLocked() {
171         whenever(userManager.isUserUnlocked).thenReturn(false)
172 
173         val request =
174             ScreenshotRequest.Builder(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_OTHER)
175                 .setTopComponent(topComponent)
176                 .build()
177 
178         service.handleRequest(request, { /* onSaved */}, callback)
179 
180         verify(notificationsController, times(1)).notifyScreenshotError(anyInt())
181         verify(callback, times(1)).reportError()
182         verifyZeroInteractions(controller)
183 
184         assertEquals("Expected two UiEvents", 2, eventLogger.numLogs())
185         val requestEvent = eventLogger.get(0)
186         assertEquals(
187             "Expected SCREENSHOT_REQUESTED_* UiEvent",
188             SCREENSHOT_REQUESTED_KEY_OTHER.id,
189             requestEvent.eventId
190         )
191         assertEquals(
192             "Expected supplied package name",
193             topComponent.packageName,
194             requestEvent.packageName
195         )
196         val failureEvent = eventLogger.get(1)
197         assertEquals(
198             "Expected SCREENSHOT_CAPTURE_FAILED UiEvent",
199             SCREENSHOT_CAPTURE_FAILED.id,
200             failureEvent.eventId
201         )
202         assertEquals(
203             "Expected supplied package name",
204             topComponent.packageName,
205             failureEvent.packageName
206         )
207     }
208 
209     @Test
210     fun takeScreenshotFullscreen_screenCaptureDisabled_allUsers() {
211         whenever(devicePolicyManager.getScreenCaptureDisabled(isNull(), eq(UserHandle.USER_ALL)))
212             .thenReturn(true)
213 
214         whenever(
215                 devicePolicyResourcesManager.getString(
216                     eq(SCREENSHOT_BLOCKED_BY_ADMIN),
217                     /* Supplier<String> */
218                     any(),
219                 )
220             )
221             .thenReturn("SCREENSHOT_BLOCKED_BY_ADMIN")
222 
223         val request =
224             ScreenshotRequest.Builder(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_OTHER)
225                 .setTopComponent(topComponent)
226                 .build()
227 
228         service.handleRequest(request, { /* onSaved */}, callback)
229 
230         // error shown: Toast.makeText(...).show(), untestable
231         verify(callback, times(1)).reportError()
232         verifyZeroInteractions(controller)
233         assertEquals("Expected two UiEvents", 2, eventLogger.numLogs())
234         val requestEvent = eventLogger.get(0)
235         assertEquals(
236             "Expected SCREENSHOT_REQUESTED_* UiEvent",
237             SCREENSHOT_REQUESTED_KEY_OTHER.id,
238             requestEvent.eventId
239         )
240         assertEquals(
241             "Expected supplied package name",
242             topComponent.packageName,
243             requestEvent.packageName
244         )
245         val failureEvent = eventLogger.get(1)
246         assertEquals(
247             "Expected SCREENSHOT_CAPTURE_FAILED UiEvent",
248             SCREENSHOT_CAPTURE_FAILED.id,
249             failureEvent.eventId
250         )
251         assertEquals(
252             "Expected supplied package name",
253             topComponent.packageName,
254             failureEvent.packageName
255         )
256     }
257 
258     @Test
259     fun takeScreenshot_workProfile_nullBitmap() {
260         val request =
261             ScreenshotRequest.Builder(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_OTHER)
262                 .setTopComponent(topComponent)
263                 .build()
264 
265         doThrow(IllegalStateException::class.java)
266             .whenever(requestProcessor)
267             .processAsync(any(ScreenshotData::class.java), any())
268 
269         service.handleRequest(request, { /* onSaved */}, callback)
270 
271         verify(callback, times(1)).reportError()
272         verify(notificationsController, times(1)).notifyScreenshotError(anyInt())
273         verifyZeroInteractions(controller)
274         assertEquals("Expected two UiEvents", 2, eventLogger.numLogs())
275         val requestEvent = eventLogger.get(0)
276         assertEquals(
277             "Expected SCREENSHOT_REQUESTED_* UiEvent",
278             SCREENSHOT_REQUESTED_KEY_OTHER.id,
279             requestEvent.eventId
280         )
281         assertEquals(
282             "Expected supplied package name",
283             topComponent.packageName,
284             requestEvent.packageName
285         )
286         val failureEvent = eventLogger.get(1)
287         assertEquals(
288             "Expected SCREENSHOT_CAPTURE_FAILED UiEvent",
289             SCREENSHOT_CAPTURE_FAILED.id,
290             failureEvent.eventId
291         )
292         assertEquals(
293             "Expected supplied package name",
294             topComponent.packageName,
295             failureEvent.packageName
296         )
297     }
298 }
299 
300 private fun Bitmap.equalsHardwareBitmap(other: Bitmap): Boolean {
301     return config == HARDWARE &&
302         other.config == HARDWARE &&
303         hardwareBuffer == other.hardwareBuffer &&
304         colorSpace == other.colorSpace
305 }
306 
307 /** A hardware Bitmap is mandated by use of ScreenshotHelper.HardwareBitmapBundler */
308 private fun makeHardwareBitmap(width: Int, height: Int): Bitmap {
309     val buffer =
310         HardwareBuffer.create(
311             width,
312             height,
313             HardwareBuffer.RGBA_8888,
314             1,
315             HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE
316         )
317     return Bitmap.wrapHardwareBuffer(buffer, ColorSpace.get(ColorSpace.Named.SRGB))!!
318 }
319