Merge "Adds test coverage for TakeScreenshotService" into tm-qpr-dev

This commit is contained in:
Mark Renouf
2022-08-24 17:36:06 +00:00
committed by Android (Google) Code Review
2 changed files with 261 additions and 22 deletions

View File

@@ -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;
@@ -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);
@@ -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.
@@ -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)) {
@@ -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:

View File

@@ -0,0 +1,237 @@
/*
* 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))!!
}