Loading packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java +24 −22 Original line number Diff line number Diff line Loading @@ -53,8 +53,7 @@ import android.util.Log; import android.view.WindowManager; import android.widget.Toast; import androidx.annotation.NonNull; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.logging.UiEventLogger; import com.android.internal.util.ScreenshotHelper; import com.android.systemui.R; Loading Loading @@ -137,7 +136,7 @@ public class TakeScreenshotService extends Service { } @Override public IBinder onBind(@NonNull Intent intent) { public IBinder onBind(Intent intent) { registerReceiver(mCloseSystemDialogs, new IntentFilter(ACTION_CLOSE_SYSTEM_DIALOGS), Context.RECEIVER_EXPORTED); final Messenger m = new Messenger(mHandler); Loading Loading @@ -184,13 +183,23 @@ public class TakeScreenshotService extends Service { } } /** Respond to incoming Message via Binder (Messenger) */ @MainThread private boolean handleMessage(Message msg) { final Messenger replyTo = msg.replyTo; final Consumer<Uri> uriConsumer = (uri) -> reportUri(replyTo, uri); RequestCallback requestCallback = new RequestCallbackImpl(replyTo); final Consumer<Uri> onSaved = (uri) -> reportUri(replyTo, uri); RequestCallback callback = new RequestCallbackImpl(replyTo); ScreenshotHelper.ScreenshotRequest request = (ScreenshotHelper.ScreenshotRequest) msg.obj; handleRequest(request, onSaved, callback); return true; } @MainThread @VisibleForTesting void handleRequest(ScreenshotHelper.ScreenshotRequest request, Consumer<Uri> onSaved, RequestCallback callback) { // If the storage for this user is locked, we have no place to store // the screenshot, so skip taking it instead of showing a misleading // animation and error notification. Loading @@ -198,8 +207,8 @@ public class TakeScreenshotService extends Service { Log.w(TAG, "Skipping screenshot because storage is locked!"); mNotificationsController.notifyScreenshotError( R.string.screenshot_failed_to_save_user_locked_text); requestCallback.reportError(); return true; callback.reportError(); return; } if (mDevicePolicyManager.getScreenCaptureDisabled(null, UserHandle.USER_ALL)) { Loading @@ -211,33 +220,26 @@ public class TakeScreenshotService extends Service { () -> mContext.getString(R.string.screenshot_blocked_by_admin)); mHandler.post(() -> Toast.makeText(mContext, blockedByAdminText, Toast.LENGTH_SHORT).show()); requestCallback.reportError(); callback.reportError(); }); return true; return; } ScreenshotHelper.ScreenshotRequest screenshotRequest = (ScreenshotHelper.ScreenshotRequest) msg.obj; ComponentName topComponent = screenshotRequest.getTopComponent(); mUiEventLogger.log(ScreenshotEvent.getScreenshotSource(screenshotRequest.getSource()), 0, topComponent == null ? "" : topComponent.getPackageName()); if (mFeatureFlags.isEnabled(SCREENSHOT_REQUEST_PROCESSOR)) { Log.d(TAG, "handleMessage: Using request processor"); mProcessor.processAsync(screenshotRequest, (request) -> dispatchToController(request, uriConsumer, requestCallback)); return true; mProcessor.processAsync(request, (r) -> dispatchToController(r, onSaved, callback)); } dispatchToController(screenshotRequest, uriConsumer, requestCallback); return true; dispatchToController(request, onSaved, callback); } private void dispatchToController(ScreenshotHelper.ScreenshotRequest request, Consumer<Uri> uriConsumer, RequestCallback callback) { ComponentName topComponent = request.getTopComponent(); mUiEventLogger.log(ScreenshotEvent.getScreenshotSource(request.getSource()), 0, topComponent == null ? "" : topComponent.getPackageName()); switch (request.getType()) { case WindowManager.TAKE_SCREENSHOT_FULLSCREEN: Loading packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt 0 → 100644 +237 −0 Original line number Diff line number Diff line /* * Copyright (C) 2022 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.systemui.screenshot import android.app.Application import android.app.admin.DevicePolicyManager import android.app.admin.DevicePolicyResources.Strings.SystemUi.SCREENSHOT_BLOCKED_BY_ADMIN import android.app.admin.DevicePolicyResourcesManager import android.content.ComponentName import android.graphics.Bitmap import android.graphics.Bitmap.Config.HARDWARE import android.graphics.ColorSpace import android.graphics.Insets import android.graphics.Rect import android.hardware.HardwareBuffer import android.os.UserHandle import android.os.UserManager import android.testing.AndroidTestingRunner import android.view.WindowManager.ScreenshotSource.SCREENSHOT_KEY_CHORD import android.view.WindowManager.ScreenshotSource.SCREENSHOT_OVERVIEW import android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN import android.view.WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE import android.view.WindowManager.TAKE_SCREENSHOT_SELECTED_REGION import com.android.internal.logging.testing.UiEventLoggerFake import com.android.internal.util.ScreenshotHelper import com.android.internal.util.ScreenshotHelper.ScreenshotRequest import com.android.systemui.SysuiTestCase import com.android.systemui.flags.FakeFeatureFlags import com.android.systemui.flags.Flags.SCREENSHOT_REQUEST_PROCESSOR import com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_REQUESTED_KEY_CHORD import com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_REQUESTED_OVERVIEW import com.android.systemui.screenshot.TakeScreenshotService.RequestCallback import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.argThat import com.android.systemui.util.mockito.eq import com.android.systemui.util.mockito.mock import org.junit.Assert.assertEquals import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentMatchers.anyInt import org.mockito.ArgumentMatchers.isNull import org.mockito.Mockito.verify import org.mockito.Mockito.verifyZeroInteractions import org.mockito.Mockito.`when` as whenever private const val USER_ID = 1 private const val TASK_ID = 1 @RunWith(AndroidTestingRunner::class) class TakeScreenshotServiceTest : SysuiTestCase() { private val application = mock<Application>() private val controller = mock<ScreenshotController>() private val userManager = mock<UserManager>() private val requestProcessor = mock<RequestProcessor>() private val devicePolicyManager = mock<DevicePolicyManager>() private val devicePolicyResourcesManager = mock<DevicePolicyResourcesManager>() private val notificationsController = mock<ScreenshotNotificationsController>() private val callback = mock<RequestCallback>() private val eventLogger = UiEventLoggerFake() private val flags = FakeFeatureFlags() private val topComponent = ComponentName(mContext, TakeScreenshotServiceTest::class.java) private val service = TakeScreenshotService( controller, userManager, devicePolicyManager, eventLogger, notificationsController, mContext, Runnable::run, flags, requestProcessor) @Before fun setUp() { whenever(devicePolicyManager.resources).thenReturn(devicePolicyResourcesManager) whenever(devicePolicyManager.getScreenCaptureDisabled( /* admin component (null: any admin) */ isNull(), eq(UserHandle.USER_ALL))) .thenReturn(false) whenever(userManager.isUserUnlocked).thenReturn(true) flags.set(SCREENSHOT_REQUEST_PROCESSOR, false) service.attach( mContext, /* thread = */ null, /* className = */ null, /* token = */ null, application, /* activityManager = */ null) } @Test fun testServiceLifecycle() { service.onCreate() service.onBind(null /* unused: Intent */) service.onUnbind(null /* unused: Intent */) verify(controller).removeWindow() service.onDestroy() verify(controller).onDestroy() } @Test fun takeScreenshotFullscreen() { val request = ScreenshotRequest( TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_CHORD, topComponent) service.handleRequest(request, { /* onSaved */ }, callback) verify(controller).takeScreenshotFullscreen( eq(topComponent), /* onSavedListener = */ any(), /* requestCallback = */ any()) assertEquals("Expected one UiEvent", eventLogger.numLogs(), 1) val logEvent = eventLogger.get(0) assertEquals("Expected SCREENSHOT_REQUESTED UiEvent", logEvent.eventId, SCREENSHOT_REQUESTED_KEY_CHORD.id) assertEquals("Expected supplied package name", topComponent.packageName, eventLogger.get(0).packageName) } @Test fun takeScreenshotPartial() { val request = ScreenshotRequest( TAKE_SCREENSHOT_SELECTED_REGION, SCREENSHOT_KEY_CHORD, /* topComponent = */ null) service.handleRequest(request, { /* onSaved */ }, callback) verify(controller).takeScreenshotPartial( /* topComponent = */ isNull(), /* onSavedListener = */ any(), /* requestCallback = */ any()) assertEquals("Expected one UiEvent", eventLogger.numLogs(), 1) val logEvent = eventLogger.get(0) assertEquals("Expected SCREENSHOT_REQUESTED UiEvent", logEvent.eventId, SCREENSHOT_REQUESTED_KEY_CHORD.id) assertEquals("Expected empty package name in UiEvent", "", eventLogger.get(0).packageName) } @Test fun takeScreenshotProvidedImage() { val bounds = Rect(50, 50, 150, 150) val bitmap = makeHardwareBitmap(100, 100) val bitmapBundle = ScreenshotHelper.HardwareBitmapBundler.hardwareBitmapToBundle(bitmap) val request = ScreenshotRequest(TAKE_SCREENSHOT_PROVIDED_IMAGE, SCREENSHOT_OVERVIEW, bitmapBundle, bounds, Insets.NONE, TASK_ID, USER_ID, topComponent) service.handleRequest(request, { /* onSaved */ }, callback) verify(controller).handleImageAsScreenshot( argThat { b -> b.equalsHardwareBitmap(bitmap) }, eq(bounds), eq(Insets.NONE), eq(TASK_ID), eq(USER_ID), eq(topComponent), /* onSavedListener = */ any(), /* requestCallback = */ any()) assertEquals("Expected one UiEvent", eventLogger.numLogs(), 1) val logEvent = eventLogger.get(0) assertEquals("Expected SCREENSHOT_REQUESTED_* UiEvent", logEvent.eventId, SCREENSHOT_REQUESTED_OVERVIEW.id) assertEquals("Expected supplied package name", topComponent.packageName, eventLogger.get(0).packageName) } @Test fun takeScreenshotFullscreen_userLocked() { whenever(userManager.isUserUnlocked).thenReturn(false) val request = ScreenshotRequest( TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_CHORD, topComponent) service.handleRequest(request, { /* onSaved */ }, callback) verify(notificationsController).notifyScreenshotError(anyInt()) verify(callback).reportError() verifyZeroInteractions(controller) } @Test fun takeScreenshotFullscreen_screenCaptureDisabled_allUsers() { whenever(devicePolicyManager.getScreenCaptureDisabled( isNull(), eq(UserHandle.USER_ALL)) ).thenReturn(true) whenever(devicePolicyResourcesManager.getString( eq(SCREENSHOT_BLOCKED_BY_ADMIN), /* Supplier<String> */ any(), )).thenReturn("SCREENSHOT_BLOCKED_BY_ADMIN") val request = ScreenshotRequest( TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_CHORD, topComponent) service.handleRequest(request, { /* onSaved */ }, callback) // error shown: Toast.makeText(...).show(), untestable verify(callback).reportError() verifyZeroInteractions(controller) } } private fun Bitmap.equalsHardwareBitmap(other: Bitmap): Boolean { return config == HARDWARE && other.config == HARDWARE && hardwareBuffer == other.hardwareBuffer && colorSpace == other.colorSpace } /** A hardware Bitmap is mandated by use of ScreenshotHelper.HardwareBitmapBundler */ private fun makeHardwareBitmap(width: Int, height: Int): Bitmap { val buffer = HardwareBuffer.create(width, height, HardwareBuffer.RGBA_8888, 1, HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE) return Bitmap.wrapHardwareBuffer(buffer, ColorSpace.get(ColorSpace.Named.SRGB))!! } Loading
packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java +24 −22 Original line number Diff line number Diff line Loading @@ -53,8 +53,7 @@ import android.util.Log; import android.view.WindowManager; import android.widget.Toast; import androidx.annotation.NonNull; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.logging.UiEventLogger; import com.android.internal.util.ScreenshotHelper; import com.android.systemui.R; Loading Loading @@ -137,7 +136,7 @@ public class TakeScreenshotService extends Service { } @Override public IBinder onBind(@NonNull Intent intent) { public IBinder onBind(Intent intent) { registerReceiver(mCloseSystemDialogs, new IntentFilter(ACTION_CLOSE_SYSTEM_DIALOGS), Context.RECEIVER_EXPORTED); final Messenger m = new Messenger(mHandler); Loading Loading @@ -184,13 +183,23 @@ public class TakeScreenshotService extends Service { } } /** Respond to incoming Message via Binder (Messenger) */ @MainThread private boolean handleMessage(Message msg) { final Messenger replyTo = msg.replyTo; final Consumer<Uri> uriConsumer = (uri) -> reportUri(replyTo, uri); RequestCallback requestCallback = new RequestCallbackImpl(replyTo); final Consumer<Uri> onSaved = (uri) -> reportUri(replyTo, uri); RequestCallback callback = new RequestCallbackImpl(replyTo); ScreenshotHelper.ScreenshotRequest request = (ScreenshotHelper.ScreenshotRequest) msg.obj; handleRequest(request, onSaved, callback); return true; } @MainThread @VisibleForTesting void handleRequest(ScreenshotHelper.ScreenshotRequest request, Consumer<Uri> onSaved, RequestCallback callback) { // If the storage for this user is locked, we have no place to store // the screenshot, so skip taking it instead of showing a misleading // animation and error notification. Loading @@ -198,8 +207,8 @@ public class TakeScreenshotService extends Service { Log.w(TAG, "Skipping screenshot because storage is locked!"); mNotificationsController.notifyScreenshotError( R.string.screenshot_failed_to_save_user_locked_text); requestCallback.reportError(); return true; callback.reportError(); return; } if (mDevicePolicyManager.getScreenCaptureDisabled(null, UserHandle.USER_ALL)) { Loading @@ -211,33 +220,26 @@ public class TakeScreenshotService extends Service { () -> mContext.getString(R.string.screenshot_blocked_by_admin)); mHandler.post(() -> Toast.makeText(mContext, blockedByAdminText, Toast.LENGTH_SHORT).show()); requestCallback.reportError(); callback.reportError(); }); return true; return; } ScreenshotHelper.ScreenshotRequest screenshotRequest = (ScreenshotHelper.ScreenshotRequest) msg.obj; ComponentName topComponent = screenshotRequest.getTopComponent(); mUiEventLogger.log(ScreenshotEvent.getScreenshotSource(screenshotRequest.getSource()), 0, topComponent == null ? "" : topComponent.getPackageName()); if (mFeatureFlags.isEnabled(SCREENSHOT_REQUEST_PROCESSOR)) { Log.d(TAG, "handleMessage: Using request processor"); mProcessor.processAsync(screenshotRequest, (request) -> dispatchToController(request, uriConsumer, requestCallback)); return true; mProcessor.processAsync(request, (r) -> dispatchToController(r, onSaved, callback)); } dispatchToController(screenshotRequest, uriConsumer, requestCallback); return true; dispatchToController(request, onSaved, callback); } private void dispatchToController(ScreenshotHelper.ScreenshotRequest request, Consumer<Uri> uriConsumer, RequestCallback callback) { ComponentName topComponent = request.getTopComponent(); mUiEventLogger.log(ScreenshotEvent.getScreenshotSource(request.getSource()), 0, topComponent == null ? "" : topComponent.getPackageName()); switch (request.getType()) { case WindowManager.TAKE_SCREENSHOT_FULLSCREEN: Loading
packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt 0 → 100644 +237 −0 Original line number Diff line number Diff line /* * Copyright (C) 2022 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.systemui.screenshot import android.app.Application import android.app.admin.DevicePolicyManager import android.app.admin.DevicePolicyResources.Strings.SystemUi.SCREENSHOT_BLOCKED_BY_ADMIN import android.app.admin.DevicePolicyResourcesManager import android.content.ComponentName import android.graphics.Bitmap import android.graphics.Bitmap.Config.HARDWARE import android.graphics.ColorSpace import android.graphics.Insets import android.graphics.Rect import android.hardware.HardwareBuffer import android.os.UserHandle import android.os.UserManager import android.testing.AndroidTestingRunner import android.view.WindowManager.ScreenshotSource.SCREENSHOT_KEY_CHORD import android.view.WindowManager.ScreenshotSource.SCREENSHOT_OVERVIEW import android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN import android.view.WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE import android.view.WindowManager.TAKE_SCREENSHOT_SELECTED_REGION import com.android.internal.logging.testing.UiEventLoggerFake import com.android.internal.util.ScreenshotHelper import com.android.internal.util.ScreenshotHelper.ScreenshotRequest import com.android.systemui.SysuiTestCase import com.android.systemui.flags.FakeFeatureFlags import com.android.systemui.flags.Flags.SCREENSHOT_REQUEST_PROCESSOR import com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_REQUESTED_KEY_CHORD import com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_REQUESTED_OVERVIEW import com.android.systemui.screenshot.TakeScreenshotService.RequestCallback import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.argThat import com.android.systemui.util.mockito.eq import com.android.systemui.util.mockito.mock import org.junit.Assert.assertEquals import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentMatchers.anyInt import org.mockito.ArgumentMatchers.isNull import org.mockito.Mockito.verify import org.mockito.Mockito.verifyZeroInteractions import org.mockito.Mockito.`when` as whenever private const val USER_ID = 1 private const val TASK_ID = 1 @RunWith(AndroidTestingRunner::class) class TakeScreenshotServiceTest : SysuiTestCase() { private val application = mock<Application>() private val controller = mock<ScreenshotController>() private val userManager = mock<UserManager>() private val requestProcessor = mock<RequestProcessor>() private val devicePolicyManager = mock<DevicePolicyManager>() private val devicePolicyResourcesManager = mock<DevicePolicyResourcesManager>() private val notificationsController = mock<ScreenshotNotificationsController>() private val callback = mock<RequestCallback>() private val eventLogger = UiEventLoggerFake() private val flags = FakeFeatureFlags() private val topComponent = ComponentName(mContext, TakeScreenshotServiceTest::class.java) private val service = TakeScreenshotService( controller, userManager, devicePolicyManager, eventLogger, notificationsController, mContext, Runnable::run, flags, requestProcessor) @Before fun setUp() { whenever(devicePolicyManager.resources).thenReturn(devicePolicyResourcesManager) whenever(devicePolicyManager.getScreenCaptureDisabled( /* admin component (null: any admin) */ isNull(), eq(UserHandle.USER_ALL))) .thenReturn(false) whenever(userManager.isUserUnlocked).thenReturn(true) flags.set(SCREENSHOT_REQUEST_PROCESSOR, false) service.attach( mContext, /* thread = */ null, /* className = */ null, /* token = */ null, application, /* activityManager = */ null) } @Test fun testServiceLifecycle() { service.onCreate() service.onBind(null /* unused: Intent */) service.onUnbind(null /* unused: Intent */) verify(controller).removeWindow() service.onDestroy() verify(controller).onDestroy() } @Test fun takeScreenshotFullscreen() { val request = ScreenshotRequest( TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_CHORD, topComponent) service.handleRequest(request, { /* onSaved */ }, callback) verify(controller).takeScreenshotFullscreen( eq(topComponent), /* onSavedListener = */ any(), /* requestCallback = */ any()) assertEquals("Expected one UiEvent", eventLogger.numLogs(), 1) val logEvent = eventLogger.get(0) assertEquals("Expected SCREENSHOT_REQUESTED UiEvent", logEvent.eventId, SCREENSHOT_REQUESTED_KEY_CHORD.id) assertEquals("Expected supplied package name", topComponent.packageName, eventLogger.get(0).packageName) } @Test fun takeScreenshotPartial() { val request = ScreenshotRequest( TAKE_SCREENSHOT_SELECTED_REGION, SCREENSHOT_KEY_CHORD, /* topComponent = */ null) service.handleRequest(request, { /* onSaved */ }, callback) verify(controller).takeScreenshotPartial( /* topComponent = */ isNull(), /* onSavedListener = */ any(), /* requestCallback = */ any()) assertEquals("Expected one UiEvent", eventLogger.numLogs(), 1) val logEvent = eventLogger.get(0) assertEquals("Expected SCREENSHOT_REQUESTED UiEvent", logEvent.eventId, SCREENSHOT_REQUESTED_KEY_CHORD.id) assertEquals("Expected empty package name in UiEvent", "", eventLogger.get(0).packageName) } @Test fun takeScreenshotProvidedImage() { val bounds = Rect(50, 50, 150, 150) val bitmap = makeHardwareBitmap(100, 100) val bitmapBundle = ScreenshotHelper.HardwareBitmapBundler.hardwareBitmapToBundle(bitmap) val request = ScreenshotRequest(TAKE_SCREENSHOT_PROVIDED_IMAGE, SCREENSHOT_OVERVIEW, bitmapBundle, bounds, Insets.NONE, TASK_ID, USER_ID, topComponent) service.handleRequest(request, { /* onSaved */ }, callback) verify(controller).handleImageAsScreenshot( argThat { b -> b.equalsHardwareBitmap(bitmap) }, eq(bounds), eq(Insets.NONE), eq(TASK_ID), eq(USER_ID), eq(topComponent), /* onSavedListener = */ any(), /* requestCallback = */ any()) assertEquals("Expected one UiEvent", eventLogger.numLogs(), 1) val logEvent = eventLogger.get(0) assertEquals("Expected SCREENSHOT_REQUESTED_* UiEvent", logEvent.eventId, SCREENSHOT_REQUESTED_OVERVIEW.id) assertEquals("Expected supplied package name", topComponent.packageName, eventLogger.get(0).packageName) } @Test fun takeScreenshotFullscreen_userLocked() { whenever(userManager.isUserUnlocked).thenReturn(false) val request = ScreenshotRequest( TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_CHORD, topComponent) service.handleRequest(request, { /* onSaved */ }, callback) verify(notificationsController).notifyScreenshotError(anyInt()) verify(callback).reportError() verifyZeroInteractions(controller) } @Test fun takeScreenshotFullscreen_screenCaptureDisabled_allUsers() { whenever(devicePolicyManager.getScreenCaptureDisabled( isNull(), eq(UserHandle.USER_ALL)) ).thenReturn(true) whenever(devicePolicyResourcesManager.getString( eq(SCREENSHOT_BLOCKED_BY_ADMIN), /* Supplier<String> */ any(), )).thenReturn("SCREENSHOT_BLOCKED_BY_ADMIN") val request = ScreenshotRequest( TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_CHORD, topComponent) service.handleRequest(request, { /* onSaved */ }, callback) // error shown: Toast.makeText(...).show(), untestable verify(callback).reportError() verifyZeroInteractions(controller) } } private fun Bitmap.equalsHardwareBitmap(other: Bitmap): Boolean { return config == HARDWARE && other.config == HARDWARE && hardwareBuffer == other.hardwareBuffer && colorSpace == other.colorSpace } /** A hardware Bitmap is mandated by use of ScreenshotHelper.HardwareBitmapBundler */ private fun makeHardwareBitmap(width: Int, height: Int): Bitmap { val buffer = HardwareBuffer.create(width, height, HardwareBuffer.RGBA_8888, 1, HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE) return Bitmap.wrapHardwareBuffer(buffer, ColorSpace.get(ColorSpace.Named.SRGB))!! }