Merge "Replace screenshots using internal displayToken with new API"

This commit is contained in:
Chavi Weingarten
2022-09-29 19:00:19 +00:00
committed by Android (Google) Code Review
5 changed files with 54 additions and 169 deletions

View File

@@ -16,6 +16,8 @@
package android.app;
import static android.view.Display.DEFAULT_DISPLAY;
import android.accessibilityservice.AccessibilityServiceInfo;
import android.accessibilityservice.IAccessibilityServiceClient;
import android.annotation.NonNull;
@@ -35,6 +37,7 @@ import android.os.ServiceManager;
import android.os.UserHandle;
import android.permission.IPermissionManager;
import android.util.Log;
import android.util.Pair;
import android.view.IWindowManager;
import android.view.InputDevice;
import android.view.InputEvent;
@@ -46,6 +49,8 @@ import android.view.WindowContentFrameStats;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.IAccessibilityManager;
import android.window.ScreenCapture;
import android.window.ScreenCapture.CaptureArgs;
import android.window.ScreenCapture.ScreenCaptureListener;
import libcore.io.IoUtils;
@@ -218,20 +223,22 @@ public final class UiAutomationConnection extends IUiAutomationConnection.Stub {
}
final long identity = Binder.clearCallingIdentity();
try {
int width = crop.width();
int height = crop.height();
final IBinder displayToken = SurfaceControl.getInternalDisplayToken();
final ScreenCapture.DisplayCaptureArgs captureArgs =
new ScreenCapture.DisplayCaptureArgs.Builder(displayToken)
.setSourceCrop(crop)
.setSize(width, height)
.build();
final CaptureArgs captureArgs = new CaptureArgs.Builder<>()
.setSourceCrop(crop)
.build();
Pair<ScreenCaptureListener, ScreenCapture.ScreenshotSync> syncScreenCapture =
ScreenCapture.createSyncCaptureListener();
mWindowManager.captureDisplay(DEFAULT_DISPLAY, captureArgs,
syncScreenCapture.first);
final ScreenCapture.ScreenshotHardwareBuffer screenshotBuffer =
ScreenCapture.captureDisplay(captureArgs);
syncScreenCapture.second.get();
return screenshotBuffer == null ? null : screenshotBuffer.asBitmap();
} catch (RemoteException re) {
re.rethrowAsRuntimeException();
} finally {
Binder.restoreCallingIdentity(identity);
}
return null;
}
@Nullable

View File

@@ -16,11 +16,17 @@
package com.android.shell;
import static android.view.Display.DEFAULT_DISPLAY;
import android.graphics.Bitmap;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
import android.view.SurfaceControl;
import android.util.Pair;
import android.view.WindowManagerGlobal;
import android.window.ScreenCapture;
import android.window.ScreenCapture.ScreenCaptureListener;
import android.window.ScreenCapture.ScreenshotHardwareBuffer;
import android.window.ScreenCapture.ScreenshotSync;
/**
* Helper class used to take screenshots.
@@ -40,12 +46,15 @@ final class Screenshooter {
static Bitmap takeScreenshot() {
Log.d(TAG, "Taking fullscreen screenshot");
// Take the screenshot
final IBinder displayToken = SurfaceControl.getInternalDisplayToken();
final ScreenCapture.DisplayCaptureArgs captureArgs =
new ScreenCapture.DisplayCaptureArgs.Builder(displayToken)
.build();
final ScreenCapture.ScreenshotHardwareBuffer screenshotBuffer =
ScreenCapture.captureDisplay(captureArgs);
final Pair<ScreenCaptureListener, ScreenshotSync> syncScreenCapture =
ScreenCapture.createSyncCaptureListener();
try {
WindowManagerGlobal.getWindowManagerService().captureDisplay(DEFAULT_DISPLAY, null,
syncScreenCapture.first);
} catch (RemoteException e) {
e.rethrowAsRuntimeException();
}
final ScreenshotHardwareBuffer screenshotBuffer = syncScreenCapture.second.get();
final Bitmap screenShot = screenshotBuffer == null ? null : screenshotBuffer.asBitmap();
if (screenShot == null) {
Log.e(TAG, "Failed to take fullscreen screenshot");

View File

@@ -19,15 +19,9 @@ package com.android.systemui.screenshot
import android.app.IActivityTaskManager
import android.graphics.Bitmap
import android.graphics.Rect
import android.hardware.display.DisplayManager
import android.os.IBinder
import android.util.Log
import android.view.DisplayAddress
import android.view.SurfaceControl
import android.view.IWindowManager
import android.window.ScreenCapture
import android.window.ScreenCapture.DisplayCaptureArgs
import android.window.ScreenCapture.ScreenshotHardwareBuffer
import androidx.annotation.VisibleForTesting
import android.window.ScreenCapture.CaptureArgs
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import javax.inject.Inject
@@ -38,18 +32,18 @@ private const val TAG = "ImageCaptureImpl"
@SysUISingleton
open class ImageCaptureImpl @Inject constructor(
private val displayManager: DisplayManager,
private val windowManager: IWindowManager,
private val atmService: IActivityTaskManager,
@Background private val bgContext: CoroutineDispatcher
) : ImageCapture {
override fun captureDisplay(displayId: Int, crop: Rect?): Bitmap? {
val width = crop?.width() ?: 0
val height = crop?.height() ?: 0
val sourceCrop = crop ?: Rect()
val displayToken = physicalDisplayToken(displayId) ?: return null
val buffer = captureDisplay(displayToken, width, height, sourceCrop)
val captureArgs = CaptureArgs.Builder()
.setSourceCrop(crop)
.build()
val syncScreenCapture = ScreenCapture.createSyncCaptureListener()
windowManager.captureDisplay(displayId, captureArgs, syncScreenCapture.first)
val buffer = syncScreenCapture.second.get()
return buffer?.asBitmap()
}
@@ -57,34 +51,4 @@ open class ImageCaptureImpl @Inject constructor(
val snapshot = withContext(bgContext) { atmService.takeTaskSnapshot(taskId) } ?: return null
return Bitmap.wrapHardwareBuffer(snapshot.hardwareBuffer, snapshot.colorSpace)
}
@VisibleForTesting
open fun physicalDisplayToken(displayId: Int): IBinder? {
val display = displayManager.getDisplay(displayId)
if (display == null) {
Log.e(TAG, "No display with id: $displayId")
return null
}
val address = display.address
if (address !is DisplayAddress.Physical) {
Log.e(TAG, "Display does not have a physical address: $display")
return null
}
return SurfaceControl.getPhysicalDisplayToken(address.physicalDisplayId)
}
@VisibleForTesting
open fun captureDisplay(
displayToken: IBinder,
width: Int,
height: Int,
crop: Rect
): ScreenshotHardwareBuffer? {
val captureArgs =
DisplayCaptureArgs.Builder(displayToken)
.setSize(width, height)
.setSourceCrop(crop)
.build()
return ScreenCapture.captureDisplay(captureArgs)
}
}

View File

@@ -1,95 +0,0 @@
/*
* 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.IActivityTaskManager
import android.graphics.Rect
import android.hardware.display.DisplayManager
import android.os.Binder
import android.os.IBinder
import android.testing.AndroidTestingRunner
import android.view.Display
import android.window.ScreenCapture.ScreenshotHardwareBuffer
import com.android.systemui.SysuiTestCase
import com.android.systemui.util.mockito.mock
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.Dispatchers
import org.junit.Test
import org.junit.runner.RunWith
/**
* Test the logic within ImageCaptureImpl
*/
@RunWith(AndroidTestingRunner::class)
class ImageCaptureImplTest : SysuiTestCase() {
private val displayManager = mock<DisplayManager>()
private val atmService = mock<IActivityTaskManager>()
private val capture = TestableImageCaptureImpl(
displayManager,
atmService,
Dispatchers.Unconfined)
@Test
fun captureDisplayWithCrop() {
capture.captureDisplay(Display.DEFAULT_DISPLAY, Rect(1, 2, 3, 4))
assertThat(capture.token).isNotNull()
assertThat(capture.width!!).isEqualTo(2)
assertThat(capture.height!!).isEqualTo(2)
assertThat(capture.crop!!).isEqualTo(Rect(1, 2, 3, 4))
}
@Test
fun captureDisplayWithNullCrop() {
capture.captureDisplay(Display.DEFAULT_DISPLAY, null)
assertThat(capture.token).isNotNull()
assertThat(capture.width!!).isEqualTo(0)
assertThat(capture.height!!).isEqualTo(0)
assertThat(capture.crop!!).isEqualTo(Rect())
}
class TestableImageCaptureImpl(
displayManager: DisplayManager,
atmService: IActivityTaskManager,
bgDispatcher: CoroutineDispatcher
) :
ImageCaptureImpl(displayManager, atmService, bgDispatcher) {
var token: IBinder? = null
var width: Int? = null
var height: Int? = null
var crop: Rect? = null
override fun physicalDisplayToken(displayId: Int): IBinder {
return Binder()
}
override fun captureDisplay(displayToken: IBinder, width: Int, height: Int, crop: Rect):
ScreenshotHardwareBuffer {
this.token = displayToken
this.width = width
this.height = height
this.crop = crop
return ScreenshotHardwareBuffer(
null,
null,
false,
false
)
}
}
}

View File

@@ -196,6 +196,7 @@ import android.util.ArraySet;
import android.util.DisplayMetrics;
import android.util.DisplayUtils;
import android.util.IntArray;
import android.util.Pair;
import android.util.RotationUtils;
import android.util.Size;
import android.util.Slog;
@@ -4890,20 +4891,19 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
return null;
}
final ScreenRotationAnimation screenRotationAnimation =
mWmService.mRoot.getDisplayContent(DEFAULT_DISPLAY).getRotationAnimation();
final boolean inRotation = screenRotationAnimation != null &&
screenRotationAnimation.isAnimating();
if (DEBUG_SCREENSHOT && inRotation) Slog.v(TAG_WM, "Taking screenshot while rotating");
Pair<ScreenCapture.ScreenCaptureListener, ScreenCapture.ScreenshotSync> syncScreenCapture =
ScreenCapture.createSyncCaptureListener();
getBounds(mTmpRect);
mTmpRect.offsetTo(0, 0);
ScreenCapture.LayerCaptureArgs args =
new ScreenCapture.LayerCaptureArgs.Builder(getSurfaceControl())
.setSourceCrop(mTmpRect).build();
ScreenCapture.captureLayers(args, syncScreenCapture.first);
// Send invalid rect and no width and height since it will screenshot the entire display.
final IBinder displayToken = SurfaceControl.getInternalDisplayToken();
final ScreenCapture.DisplayCaptureArgs captureArgs =
new ScreenCapture.DisplayCaptureArgs.Builder(displayToken)
.setUseIdentityTransform(inRotation)
.build();
final ScreenCapture.ScreenshotHardwareBuffer screenshotBuffer =
ScreenCapture.captureDisplay(captureArgs);
syncScreenCapture.second.get();
final Bitmap bitmap = screenshotBuffer == null ? null : screenshotBuffer.asBitmap();
if (bitmap == null) {
Slog.w(TAG_WM, "Failed to take screenshot");