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