Merge "Migrate to ScreenshotData object" into tm-qpr-dev
This commit is contained in:
@@ -493,6 +493,9 @@ object Flags {
|
||||
val SCREENSHOT_WORK_PROFILE_POLICY =
|
||||
unreleasedFlag(1301, "screenshot_work_profile_policy", teamfood = true)
|
||||
|
||||
// TODO(b/264916608): Tracking Bug
|
||||
@JvmField val SCREENSHOT_METADATA = unreleasedFlag(1302, "screenshot_metadata")
|
||||
|
||||
// 1400 - columbus
|
||||
// TODO(b/254512756): Tracking Bug
|
||||
val QUICK_TAP_IN_PCC = releasedFlag(1400, "quick_tap_in_pcc")
|
||||
|
||||
@@ -45,6 +45,7 @@ class RequestProcessor @Inject constructor(
|
||||
*
|
||||
* @param request the request to process
|
||||
*/
|
||||
// TODO: Delete once SCREENSHOT_METADATA flag is launched
|
||||
suspend fun process(request: ScreenshotRequest): ScreenshotRequest {
|
||||
var result = request
|
||||
|
||||
@@ -93,12 +94,67 @@ class RequestProcessor @Inject constructor(
|
||||
* @param request the request to process
|
||||
* @param callback the callback to provide the processed request, invoked from the main thread
|
||||
*/
|
||||
// TODO: Delete once SCREENSHOT_METADATA flag is launched
|
||||
fun processAsync(request: ScreenshotRequest, callback: Consumer<ScreenshotRequest>) {
|
||||
mainScope.launch {
|
||||
val result = process(request)
|
||||
callback.accept(result)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Inspects the incoming ScreenshotData, potentially modifying it based upon policy.
|
||||
*
|
||||
* @param screenshot the screenshot to process
|
||||
*/
|
||||
suspend fun process(screenshot: ScreenshotData): ScreenshotData {
|
||||
var result = screenshot
|
||||
|
||||
// Apply work profile screenshots policy:
|
||||
//
|
||||
// If the focused app belongs to a work profile, transforms a full screen
|
||||
// (or partial) screenshot request to a task snapshot (provided image) screenshot.
|
||||
|
||||
// Whenever displayContentInfo is fetched, the topComponent is also populated
|
||||
// regardless of the managed profile status.
|
||||
|
||||
if (screenshot.type != TAKE_SCREENSHOT_PROVIDED_IMAGE &&
|
||||
flags.isEnabled(SCREENSHOT_WORK_PROFILE_POLICY)
|
||||
) {
|
||||
val info = policy.findPrimaryContent(policy.getDefaultDisplayId())
|
||||
Log.d(TAG, "findPrimaryContent: $info")
|
||||
result.taskId = info.taskId
|
||||
result.topComponent = info.component
|
||||
result.userHandle = info.user
|
||||
|
||||
if (policy.isManagedProfile(info.user.identifier)) {
|
||||
val image = capture.captureTask(info.taskId)
|
||||
?: error("Task snapshot returned a null Bitmap!")
|
||||
|
||||
// Provide the task snapshot as the screenshot
|
||||
result.type = TAKE_SCREENSHOT_PROVIDED_IMAGE
|
||||
result.bitmap = image
|
||||
result.screenBounds = info.bounds
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* Note: This is for compatibility with existing Java. Prefer the suspending function when
|
||||
* calling from a Coroutine context.
|
||||
*
|
||||
* @param screenshot the screenshot to process
|
||||
* @param callback the callback to provide the processed screenshot, invoked from the main
|
||||
* thread
|
||||
*/
|
||||
fun processAsync(screenshot: ScreenshotData, callback: Consumer<ScreenshotData>) {
|
||||
mainScope.launch {
|
||||
val result = process(screenshot)
|
||||
callback.accept(result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private const val TAG = "RequestProcessor"
|
||||
|
||||
@@ -390,16 +390,131 @@ public class ScreenshotController {
|
||||
ClipboardOverlayController.SELF_PERMISSION, null, Context.RECEIVER_NOT_EXPORTED);
|
||||
}
|
||||
|
||||
void handleScreenshot(ScreenshotData screenshot, Consumer<Uri> finisher,
|
||||
RequestCallback requestCallback) {
|
||||
Assert.isMainThread();
|
||||
mCurrentRequestCallback = requestCallback;
|
||||
if (screenshot.getType() == WindowManager.TAKE_SCREENSHOT_FULLSCREEN) {
|
||||
Rect bounds = getFullScreenRect();
|
||||
screenshot.setBitmap(mImageCapture.captureDisplay(DEFAULT_DISPLAY, bounds));
|
||||
screenshot.setScreenBounds(bounds);
|
||||
}
|
||||
|
||||
if (screenshot.getBitmap() == null) {
|
||||
Log.e(TAG, "handleScreenshot: Screenshot bitmap was null");
|
||||
mNotificationsController.notifyScreenshotError(
|
||||
R.string.screenshot_failed_to_capture_text);
|
||||
if (mCurrentRequestCallback != null) {
|
||||
mCurrentRequestCallback.reportError();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isUserSetupComplete(Process.myUserHandle())) {
|
||||
Log.w(TAG, "User setup not complete, displaying toast only");
|
||||
// User setup isn't complete, so we don't want to show any UI beyond a toast, as editing
|
||||
// and sharing shouldn't be exposed to the user.
|
||||
saveScreenshotAndToast(screenshot.getUserHandle(), finisher);
|
||||
return;
|
||||
}
|
||||
|
||||
mBroadcastSender.sendBroadcast(new Intent(ClipboardOverlayController.SCREENSHOT_ACTION),
|
||||
ClipboardOverlayController.SELF_PERMISSION);
|
||||
|
||||
mScreenshotTakenInPortrait =
|
||||
mContext.getResources().getConfiguration().orientation == ORIENTATION_PORTRAIT;
|
||||
|
||||
String oldPackageName = mPackageName;
|
||||
mPackageName = screenshot.getPackageNameString();
|
||||
|
||||
mScreenBitmap = screenshot.getBitmap();
|
||||
// Optimizations
|
||||
mScreenBitmap.setHasAlpha(false);
|
||||
mScreenBitmap.prepareToDraw();
|
||||
|
||||
prepareViewForNewScreenshot(screenshot, oldPackageName);
|
||||
|
||||
saveScreenshotInWorkerThread(screenshot.getUserHandle(), finisher,
|
||||
this::showUiOnActionsReady, this::showUiOnQuickShareActionReady);
|
||||
|
||||
// The window is focusable by default
|
||||
setWindowFocusable(true);
|
||||
mScreenshotView.requestFocus();
|
||||
|
||||
enqueueScrollCaptureRequest(screenshot.getUserHandle());
|
||||
|
||||
attachWindow();
|
||||
|
||||
boolean showFlash = true;
|
||||
if (screenshot.getType() == WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE) {
|
||||
if (screenshot.getScreenBounds() != null
|
||||
&& aspectRatiosMatch(screenshot.getBitmap(), screenshot.getInsets(),
|
||||
screenshot.getScreenBounds())) {
|
||||
showFlash = false;
|
||||
} else {
|
||||
showFlash = true;
|
||||
screenshot.setInsets(Insets.NONE);
|
||||
screenshot.setScreenBounds(new Rect(0, 0, screenshot.getBitmap().getWidth(),
|
||||
screenshot.getBitmap().getHeight()));
|
||||
}
|
||||
}
|
||||
|
||||
prepareAnimation(screenshot.getScreenBounds(), showFlash);
|
||||
|
||||
if (mFlags.isEnabled(SCREENSHOT_WORK_PROFILE_POLICY)) {
|
||||
mScreenshotView.badgeScreenshot(mContext.getPackageManager().getUserBadgedIcon(
|
||||
mContext.getDrawable(R.drawable.overlay_badge_background),
|
||||
screenshot.getUserHandle()));
|
||||
}
|
||||
mScreenshotView.setScreenshot(mScreenBitmap, screenshot.getInsets());
|
||||
if (DEBUG_WINDOW) {
|
||||
Log.d(TAG, "setContentView: " + mScreenshotView);
|
||||
}
|
||||
setContentView(mScreenshotView);
|
||||
// ignore system bar insets for the purpose of window layout
|
||||
mWindow.getDecorView().setOnApplyWindowInsetsListener(
|
||||
(v, insets) -> WindowInsets.CONSUMED);
|
||||
mScreenshotHandler.cancelTimeout(); // restarted after animation
|
||||
}
|
||||
|
||||
void prepareViewForNewScreenshot(ScreenshotData screenshot, String oldPackageName) {
|
||||
withWindowAttached(() -> {
|
||||
if (mFlags.isEnabled(SCREENSHOT_WORK_PROFILE_POLICY)
|
||||
&& mUserManager.isManagedProfile(screenshot.getUserHandle().getIdentifier())) {
|
||||
mScreenshotView.announceForAccessibility(mContext.getResources().getString(
|
||||
R.string.screenshot_saving_work_profile_title));
|
||||
} else {
|
||||
mScreenshotView.announceForAccessibility(
|
||||
mContext.getResources().getString(R.string.screenshot_saving_title));
|
||||
}
|
||||
});
|
||||
|
||||
mScreenshotView.reset();
|
||||
|
||||
if (mScreenshotView.isAttachedToWindow()) {
|
||||
// if we didn't already dismiss for another reason
|
||||
if (!mScreenshotView.isDismissing()) {
|
||||
mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_REENTERED, 0,
|
||||
oldPackageName);
|
||||
}
|
||||
if (DEBUG_WINDOW) {
|
||||
Log.d(TAG, "saveScreenshot: screenshotView is already attached, resetting. "
|
||||
+ "(dismissing=" + mScreenshotView.isDismissing() + ")");
|
||||
}
|
||||
}
|
||||
|
||||
mScreenshotView.setPackageName(mPackageName);
|
||||
|
||||
mScreenshotView.updateOrientation(
|
||||
mWindowManager.getCurrentWindowMetrics().getWindowInsets());
|
||||
}
|
||||
|
||||
@MainThread
|
||||
void takeScreenshotFullscreen(ComponentName topComponent, Consumer<Uri> finisher,
|
||||
RequestCallback requestCallback) {
|
||||
Assert.isMainThread();
|
||||
mCurrentRequestCallback = requestCallback;
|
||||
DisplayMetrics displayMetrics = new DisplayMetrics();
|
||||
getDefaultDisplay().getRealMetrics(displayMetrics);
|
||||
takeScreenshotInternal(
|
||||
topComponent, finisher,
|
||||
new Rect(0, 0, displayMetrics.widthPixels, displayMetrics.heightPixels));
|
||||
takeScreenshotInternal(topComponent, finisher, getFullScreenRect());
|
||||
}
|
||||
|
||||
@MainThread
|
||||
@@ -641,6 +756,42 @@ public class ScreenshotController {
|
||||
setWindowFocusable(true);
|
||||
mScreenshotView.requestFocus();
|
||||
|
||||
enqueueScrollCaptureRequest(owner);
|
||||
|
||||
attachWindow();
|
||||
prepareAnimation(screenRect, showFlash);
|
||||
|
||||
if (mFlags.isEnabled(SCREENSHOT_WORK_PROFILE_POLICY)) {
|
||||
mScreenshotView.badgeScreenshot(mContext.getPackageManager().getUserBadgedIcon(
|
||||
mContext.getDrawable(R.drawable.overlay_badge_background), owner));
|
||||
}
|
||||
mScreenshotView.setScreenshot(mScreenBitmap, screenInsets);
|
||||
if (DEBUG_WINDOW) {
|
||||
Log.d(TAG, "setContentView: " + mScreenshotView);
|
||||
}
|
||||
setContentView(mScreenshotView);
|
||||
// ignore system bar insets for the purpose of window layout
|
||||
mWindow.getDecorView().setOnApplyWindowInsetsListener(
|
||||
(v, insets) -> WindowInsets.CONSUMED);
|
||||
mScreenshotHandler.cancelTimeout(); // restarted after animation
|
||||
}
|
||||
|
||||
private void prepareAnimation(Rect screenRect, boolean showFlash) {
|
||||
mScreenshotView.getViewTreeObserver().addOnPreDrawListener(
|
||||
new ViewTreeObserver.OnPreDrawListener() {
|
||||
@Override
|
||||
public boolean onPreDraw() {
|
||||
if (DEBUG_WINDOW) {
|
||||
Log.d(TAG, "onPreDraw: startAnimation");
|
||||
}
|
||||
mScreenshotView.getViewTreeObserver().removeOnPreDrawListener(this);
|
||||
startAnimation(screenRect, showFlash);
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void enqueueScrollCaptureRequest(UserHandle owner) {
|
||||
// Wait until this window is attached to request because it is
|
||||
// the reference used to locate the target window (below).
|
||||
withWindowAttached(() -> {
|
||||
@@ -678,30 +829,6 @@ public class ScreenshotController {
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
attachWindow();
|
||||
mScreenshotView.getViewTreeObserver().addOnPreDrawListener(
|
||||
new ViewTreeObserver.OnPreDrawListener() {
|
||||
@Override
|
||||
public boolean onPreDraw() {
|
||||
if (DEBUG_WINDOW) {
|
||||
Log.d(TAG, "onPreDraw: startAnimation");
|
||||
}
|
||||
mScreenshotView.getViewTreeObserver().removeOnPreDrawListener(this);
|
||||
startAnimation(screenRect, showFlash);
|
||||
return true;
|
||||
}
|
||||
});
|
||||
if (mFlags.isEnabled(SCREENSHOT_WORK_PROFILE_POLICY)) {
|
||||
mScreenshotView.badgeScreenshot(mContext.getPackageManager().getUserBadgedIcon(
|
||||
mContext.getDrawable(R.drawable.overlay_badge_background), owner));
|
||||
}
|
||||
mScreenshotView.setScreenshot(mScreenBitmap, screenInsets);
|
||||
|
||||
// ignore system bar insets for the purpose of window layout
|
||||
mWindow.getDecorView().setOnApplyWindowInsetsListener(
|
||||
(v, insets) -> WindowInsets.CONSUMED);
|
||||
mScreenshotHandler.cancelTimeout(); // restarted after animation
|
||||
}
|
||||
|
||||
private void requestScrollCapture(UserHandle owner) {
|
||||
@@ -1154,6 +1281,12 @@ public class ScreenshotController {
|
||||
return !mIsLowRamDevice;
|
||||
}
|
||||
|
||||
private Rect getFullScreenRect() {
|
||||
DisplayMetrics displayMetrics = new DisplayMetrics();
|
||||
getDefaultDisplay().getRealMetrics(displayMetrics);
|
||||
return new Rect(0, 0, displayMetrics.widthPixels, displayMetrics.heightPixels);
|
||||
}
|
||||
|
||||
/** Does the aspect ratio of the bitmap with insets removed match the bounds. */
|
||||
private static boolean aspectRatiosMatch(Bitmap bitmap, Insets bitmapInsets,
|
||||
Rect screenBounds) {
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
package com.android.systemui.screenshot
|
||||
|
||||
import android.content.ComponentName
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.Insets
|
||||
import android.graphics.Rect
|
||||
import android.os.UserHandle
|
||||
import android.view.WindowManager.ScreenshotSource
|
||||
import android.view.WindowManager.ScreenshotType
|
||||
import com.android.internal.util.ScreenshotRequest
|
||||
|
||||
/** ScreenshotData represents the current state of a single screenshot being acquired. */
|
||||
data class ScreenshotData(
|
||||
@ScreenshotType var type: Int,
|
||||
@ScreenshotSource var source: Int,
|
||||
/** UserHandle for the owner of the app being screenshotted, if known. */
|
||||
var userHandle: UserHandle?,
|
||||
/** ComponentName of the top-most app in the screenshot. */
|
||||
var topComponent: ComponentName?,
|
||||
var screenBounds: Rect?,
|
||||
var taskId: Int,
|
||||
var insets: Insets,
|
||||
var bitmap: Bitmap?,
|
||||
) {
|
||||
val packageNameString: String
|
||||
get() = if (topComponent == null) "" else topComponent!!.packageName
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
fun fromRequest(request: ScreenshotRequest): ScreenshotData {
|
||||
return ScreenshotData(
|
||||
request.type,
|
||||
request.source,
|
||||
if (request.userId >= 0) UserHandle.of(request.userId) else null,
|
||||
request.topComponent,
|
||||
request.boundsInScreen,
|
||||
request.taskId,
|
||||
request.insets,
|
||||
request.bitmap,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -59,6 +59,7 @@ import com.android.systemui.R;
|
||||
import com.android.systemui.dagger.qualifiers.Background;
|
||||
import com.android.systemui.flags.FeatureFlags;
|
||||
import com.android.systemui.flags.FlagListenable.FlagEvent;
|
||||
import com.android.systemui.flags.Flags;
|
||||
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.function.Consumer;
|
||||
@@ -221,8 +222,33 @@ public class TakeScreenshotService extends Service {
|
||||
return;
|
||||
}
|
||||
|
||||
mProcessor.processAsync(request,
|
||||
(r) -> dispatchToController(r, onSaved, callback));
|
||||
if (mFeatureFlags.isEnabled(Flags.SCREENSHOT_METADATA)) {
|
||||
Log.d(TAG, "Processing screenshot data");
|
||||
ScreenshotData screenshotData = ScreenshotData.fromRequest(request);
|
||||
mProcessor.processAsync(screenshotData,
|
||||
(data) -> dispatchToController(data, onSaved, callback));
|
||||
} else {
|
||||
mProcessor.processAsync(request,
|
||||
(r) -> dispatchToController(r, onSaved, callback));
|
||||
}
|
||||
}
|
||||
|
||||
private void dispatchToController(ScreenshotData screenshot,
|
||||
Consumer<Uri> uriConsumer, RequestCallback callback) {
|
||||
|
||||
mUiEventLogger.log(ScreenshotEvent.getScreenshotSource(screenshot.getSource()), 0,
|
||||
screenshot.getPackageNameString());
|
||||
|
||||
if (screenshot.getType() == WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE
|
||||
&& screenshot.getBitmap() == null) {
|
||||
Log.e(TAG, "Got null bitmap from screenshot message");
|
||||
mNotificationsController.notifyScreenshotError(
|
||||
R.string.screenshot_failed_to_capture_text);
|
||||
callback.reportError();
|
||||
return;
|
||||
}
|
||||
|
||||
mScreenshot.handleScreenshot(screenshot, uriConsumer, callback);
|
||||
}
|
||||
|
||||
private void dispatchToController(ScreenshotRequest request,
|
||||
|
||||
@@ -73,6 +73,30 @@ class RequestProcessorTest {
|
||||
assertThat(result).isEqualTo(request)
|
||||
}
|
||||
|
||||
/** Tests the Java-compatible function wrapper, ensures callback is invoked. */
|
||||
@Test
|
||||
fun testProcessAsync_ScreenshotData() {
|
||||
flags.set(Flags.SCREENSHOT_WORK_PROFILE_POLICY, false)
|
||||
|
||||
val request = ScreenshotData.fromRequest(
|
||||
ScreenshotRequest.Builder(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_CHORD).build())
|
||||
val processor = RequestProcessor(imageCapture, policy, flags, scope)
|
||||
|
||||
var result: ScreenshotData? = null
|
||||
var callbackCount = 0
|
||||
val callback: (ScreenshotData) -> Unit = { processedRequest: ScreenshotData ->
|
||||
result = processedRequest
|
||||
callbackCount++
|
||||
}
|
||||
|
||||
// runs synchronously, using Unconfined Dispatcher
|
||||
processor.processAsync(request, callback)
|
||||
|
||||
// Callback invoked once returning the same request (no changes)
|
||||
assertThat(callbackCount).isEqualTo(1)
|
||||
assertThat(result).isEqualTo(request)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testFullScreenshot_workProfilePolicyDisabled() = runBlocking {
|
||||
flags.set(Flags.SCREENSHOT_WORK_PROFILE_POLICY, false)
|
||||
@@ -85,6 +109,11 @@ class RequestProcessorTest {
|
||||
|
||||
// No changes
|
||||
assertThat(processedRequest).isEqualTo(request)
|
||||
|
||||
val screenshotData = ScreenshotData.fromRequest(request)
|
||||
val processedData = processor.process(screenshotData)
|
||||
|
||||
assertThat(processedData).isEqualTo(screenshotData)
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -108,6 +137,13 @@ class RequestProcessorTest {
|
||||
assertThat(processedRequest.type).isEqualTo(TAKE_SCREENSHOT_FULLSCREEN)
|
||||
assertThat(processedRequest.source).isEqualTo(SCREENSHOT_OTHER)
|
||||
assertThat(processedRequest.topComponent).isEqualTo(component)
|
||||
|
||||
val processedData = processor.process(ScreenshotData.fromRequest(request))
|
||||
|
||||
// Request has topComponent added, but otherwise unchanged.
|
||||
assertThat(processedData.type).isEqualTo(TAKE_SCREENSHOT_FULLSCREEN)
|
||||
assertThat(processedData.source).isEqualTo(SCREENSHOT_OTHER)
|
||||
assertThat(processedData.topComponent).isEqualTo(component)
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -140,6 +176,18 @@ class RequestProcessorTest {
|
||||
assertThat(imageCapture.requestedTaskId).isEqualTo(TASK_ID)
|
||||
assertThat(processedRequest.userId).isEqualTo(USER_ID)
|
||||
assertThat(processedRequest.topComponent).isEqualTo(component)
|
||||
|
||||
val processedData = processor.process(ScreenshotData.fromRequest(request))
|
||||
|
||||
// Expect a task snapshot is taken, overriding the full screen mode
|
||||
assertThat(processedData.type).isEqualTo(TAKE_SCREENSHOT_PROVIDED_IMAGE)
|
||||
assertThat(processedData.bitmap).isEqualTo(bitmap)
|
||||
assertThat(processedData.screenBounds).isEqualTo(bounds)
|
||||
assertThat(processedData.insets).isEqualTo(Insets.NONE)
|
||||
assertThat(processedData.taskId).isEqualTo(TASK_ID)
|
||||
assertThat(imageCapture.requestedTaskId).isEqualTo(TASK_ID)
|
||||
assertThat(processedRequest.userId).isEqualTo(USER_ID)
|
||||
assertThat(processedRequest.topComponent).isEqualTo(component)
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -165,6 +213,11 @@ class RequestProcessorTest {
|
||||
|
||||
// No changes
|
||||
assertThat(processedRequest).isEqualTo(request)
|
||||
|
||||
val screenshotData = ScreenshotData.fromRequest(request)
|
||||
val processedData = processor.process(screenshotData)
|
||||
|
||||
assertThat(processedData).isEqualTo(screenshotData)
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -192,6 +245,11 @@ class RequestProcessorTest {
|
||||
|
||||
// No changes
|
||||
assertThat(processedRequest).isEqualTo(request)
|
||||
|
||||
val screenshotData = ScreenshotData.fromRequest(request)
|
||||
val processedData = processor.process(screenshotData)
|
||||
|
||||
assertThat(processedData).isEqualTo(screenshotData)
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -220,6 +278,11 @@ class RequestProcessorTest {
|
||||
|
||||
// Work profile, but already a task snapshot, so no changes
|
||||
assertThat(processedRequest).isEqualTo(request)
|
||||
|
||||
val screenshotData = ScreenshotData.fromRequest(request)
|
||||
val processedData = processor.process(screenshotData)
|
||||
|
||||
assertThat(processedData).isEqualTo(screenshotData)
|
||||
}
|
||||
|
||||
private fun makeHardwareBitmap(width: Int, height: Int): Bitmap {
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
* Copyright (C) 2023 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.content.ComponentName
|
||||
import android.graphics.Insets
|
||||
import android.graphics.Rect
|
||||
import android.os.UserHandle
|
||||
import android.view.WindowManager
|
||||
import com.android.internal.util.ScreenshotRequest
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import org.junit.Test
|
||||
|
||||
class ScreenshotDataTest {
|
||||
private val type = WindowManager.TAKE_SCREENSHOT_FULLSCREEN
|
||||
private val source = WindowManager.ScreenshotSource.SCREENSHOT_KEY_OTHER
|
||||
private val bounds = Rect(1, 2, 3, 4)
|
||||
private val taskId = 123
|
||||
private val userId = 1
|
||||
private val insets = Insets.of(1, 2, 3, 4)
|
||||
private val component = ComponentName("android.test", "android.test.Component")
|
||||
|
||||
@Test
|
||||
fun testConstruction() {
|
||||
val request =
|
||||
ScreenshotRequest.Builder(type, source)
|
||||
.setBoundsOnScreen(bounds)
|
||||
.setInsets(insets)
|
||||
.setTaskId(taskId)
|
||||
.setUserId(userId)
|
||||
.setTopComponent(component)
|
||||
.build()
|
||||
|
||||
val data = ScreenshotData.fromRequest(request)
|
||||
|
||||
assertThat(data.source).isEqualTo(source)
|
||||
assertThat(data.type).isEqualTo(type)
|
||||
assertThat(data.screenBounds).isEqualTo(bounds)
|
||||
assertThat(data.insets).isEqualTo(insets)
|
||||
assertThat(data.taskId).isEqualTo(taskId)
|
||||
assertThat(data.userHandle).isEqualTo(UserHandle.of(userId))
|
||||
assertThat(data.topComponent).isEqualTo(component)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testNegativeUserId() {
|
||||
val request = ScreenshotRequest.Builder(type, source).setUserId(-1).build()
|
||||
|
||||
val data = ScreenshotData.fromRequest(request)
|
||||
|
||||
assertThat(data.userHandle).isNull()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testPackageNameAsString() {
|
||||
val request = ScreenshotRequest.Builder(type, source).setTopComponent(component).build()
|
||||
|
||||
val data = ScreenshotData.fromRequest(request)
|
||||
|
||||
assertThat(data.packageNameString).isEqualTo("android.test")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testPackageNameAsString_null() {
|
||||
val request = ScreenshotRequest.Builder(type, source).build()
|
||||
|
||||
val data = ScreenshotData.fromRequest(request)
|
||||
|
||||
assertThat(data.packageNameString).isEqualTo("")
|
||||
}
|
||||
}
|
||||
@@ -29,7 +29,7 @@ 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_KEY_OTHER
|
||||
import android.view.WindowManager.ScreenshotSource.SCREENSHOT_OVERVIEW
|
||||
import android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN
|
||||
import android.view.WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE
|
||||
@@ -39,7 +39,8 @@ import com.android.internal.util.ScreenshotRequest
|
||||
import com.android.systemui.SysuiTestCase
|
||||
import com.android.systemui.flags.FakeFeatureFlags
|
||||
import com.android.systemui.flags.Flags.SCREENSHOT_WORK_PROFILE_POLICY
|
||||
import com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_REQUESTED_KEY_CHORD
|
||||
import com.android.systemui.flags.Flags.SCREENSHOT_METADATA
|
||||
import com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_REQUESTED_KEY_OTHER
|
||||
import com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_REQUESTED_OVERVIEW
|
||||
import com.android.systemui.screenshot.TakeScreenshotService.RequestCallback
|
||||
import com.android.systemui.util.mockito.any
|
||||
@@ -106,15 +107,22 @@ class TakeScreenshotServiceTest : SysuiTestCase() {
|
||||
|
||||
// Stub request processor as a synchronous no-op for tests with the flag enabled
|
||||
doAnswer {
|
||||
val request: ScreenshotRequest = it.getArgument(0) as ScreenshotRequest
|
||||
val consumer: Consumer<ScreenshotRequest> = it.getArgument(1)
|
||||
consumer.accept(request)
|
||||
}
|
||||
.`when`(requestProcessor)
|
||||
.processAsync(/* request= */ any(), /* callback= */ any())
|
||||
val request: ScreenshotRequest = it.getArgument(0) as ScreenshotRequest
|
||||
val consumer: Consumer<ScreenshotRequest> = it.getArgument(1)
|
||||
consumer.accept(request)
|
||||
}.`when`(requestProcessor).processAsync(
|
||||
/* request= */ any(ScreenshotRequest::class.java), /* callback= */ any())
|
||||
|
||||
doAnswer {
|
||||
val request: ScreenshotData = it.getArgument(0) as ScreenshotData
|
||||
val consumer: Consumer<ScreenshotData> = it.getArgument(1)
|
||||
consumer.accept(request)
|
||||
}.`when`(requestProcessor).processAsync(
|
||||
/* screenshot= */ any(ScreenshotData::class.java), /* callback= */ any())
|
||||
|
||||
// Flipped in selected test cases
|
||||
flags.set(SCREENSHOT_WORK_PROFILE_POLICY, false)
|
||||
flags.set(SCREENSHOT_METADATA, false)
|
||||
|
||||
service.attach(
|
||||
mContext,
|
||||
@@ -141,7 +149,7 @@ class TakeScreenshotServiceTest : SysuiTestCase() {
|
||||
@Test
|
||||
fun takeScreenshotFullscreen() {
|
||||
val request =
|
||||
ScreenshotRequest.Builder(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_CHORD)
|
||||
ScreenshotRequest.Builder(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_OTHER)
|
||||
.setTopComponent(topComponent)
|
||||
.build()
|
||||
|
||||
@@ -157,16 +165,34 @@ class TakeScreenshotServiceTest : SysuiTestCase() {
|
||||
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
|
||||
)
|
||||
assertEquals("Expected SCREENSHOT_REQUESTED UiEvent",
|
||||
logEvent.eventId, SCREENSHOT_REQUESTED_KEY_OTHER.id)
|
||||
assertEquals("Expected supplied package name",
|
||||
topComponent.packageName, eventLogger.get(0).packageName)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun takeScreenshotFullscreen_screenshotDataEnabled() {
|
||||
flags.set(SCREENSHOT_METADATA, true)
|
||||
|
||||
val request = ScreenshotRequest.Builder(
|
||||
TAKE_SCREENSHOT_FULLSCREEN,
|
||||
SCREENSHOT_KEY_OTHER).setTopComponent(topComponent).build()
|
||||
|
||||
service.handleRequest(request, { /* onSaved */ }, callback)
|
||||
|
||||
verify(controller, times(1)).handleScreenshot(
|
||||
eq(ScreenshotData.fromRequest(request)),
|
||||
/* 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_OTHER.id)
|
||||
assertEquals("Expected supplied package name",
|
||||
topComponent.packageName, eventLogger.get(0).packageName)
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -218,7 +244,7 @@ class TakeScreenshotServiceTest : SysuiTestCase() {
|
||||
whenever(userManager.isUserUnlocked).thenReturn(false)
|
||||
|
||||
val request =
|
||||
ScreenshotRequest.Builder(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_CHORD)
|
||||
ScreenshotRequest.Builder(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_OTHER)
|
||||
.setTopComponent(topComponent)
|
||||
.build()
|
||||
|
||||
@@ -244,7 +270,7 @@ class TakeScreenshotServiceTest : SysuiTestCase() {
|
||||
.thenReturn("SCREENSHOT_BLOCKED_BY_ADMIN")
|
||||
|
||||
val request =
|
||||
ScreenshotRequest.Builder(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_CHORD)
|
||||
ScreenshotRequest.Builder(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_OTHER)
|
||||
.setTopComponent(topComponent)
|
||||
.build()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user