diff options
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() |