summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--packages/SystemUI/src/com/android/systemui/flags/Flags.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/RequestProcessor.kt56
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java191
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotData.kt43
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java30
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/screenshot/RequestProcessorTest.kt63
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotDataTest.kt85
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt68
8 files changed, 487 insertions, 52 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index ddc69f9b27b9..ae9b3e3dccf2 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -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")
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/RequestProcessor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/RequestProcessor.kt
index f011aab9da0f..4db48ac89e2d 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/RequestProcessor.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/RequestProcessor.kt
@@ -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"
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
index 6d87922f49eb..02719e3fc5c0 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
@@ -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) {
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotData.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotData.kt
new file mode 100644
index 000000000000..1d1b96825260
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotData.kt
@@ -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,
+ )
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
index 7b271a886d68..4214c8f6ecf2 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
@@ -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,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/RequestProcessorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/RequestProcessorTest.kt
index ed3f1a059e61..541d6c25192f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/RequestProcessorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/RequestProcessorTest.kt
@@ -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 {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotDataTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotDataTest.kt
new file mode 100644
index 000000000000..43e99393b874
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotDataTest.kt
@@ -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("")
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt
index f93501928844..74969d0e4737 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt
@@ -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()