Merge changes from topic "revert_capture_display"

* changes:
  Revert "Add captureDisplay API in WMS."
  Revert "Convert invalid crop to display bounds for captureDisplay"
This commit is contained in:
Chavi Weingarten
2022-09-22 14:10:37 +00:00
committed by Android (Google) Code Review
9 changed files with 209 additions and 527 deletions

View File

@@ -67,7 +67,6 @@ import android.view.SurfaceControl;
import android.view.displayhash.DisplayHash;
import android.view.displayhash.VerifiedDisplayHash;
import android.window.ITaskFpsCallback;
import android.window.ScreenCapture;
/**
* System private interface to the window manager.
@@ -969,11 +968,4 @@ interface IWindowManager
* treatment.
*/
boolean isLetterboxBackgroundMultiColored();
/**
* Captures the entire display specified by the displayId using the args provided. If the args
* are null or if the sourceCrop is invalid or null, the entire display bounds will be captured.
*/
oneway void captureDisplay(int displayId, in @nullable ScreenCapture.CaptureArgs captureArgs,
in ScreenCapture.ScreenCaptureListener listener);
}

View File

@@ -1,26 +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 android.window;
/** @hide */
parcelable ScreenCapture.CaptureArgs;
/** @hide */
parcelable ScreenCapture.ScreenshotHardwareBuffer;
/** @hide */
parcelable ScreenCapture.ScreenCaptureListener;

View File

@@ -24,16 +24,11 @@ import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.hardware.HardwareBuffer;
import android.os.IBinder;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.Log;
import android.view.SurfaceControl;
import libcore.util.NativeAllocationRegistry;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
/**
* Handles display and layer captures for the system.
@@ -44,23 +39,18 @@ public class ScreenCapture {
private static final String TAG = "ScreenCapture";
private static native int nativeCaptureDisplay(DisplayCaptureArgs captureArgs,
long captureListener);
ScreenCaptureListener captureListener);
private static native int nativeCaptureLayers(LayerCaptureArgs captureArgs,
long captureListener);
private static native long nativeCreateScreenCaptureListener(
Consumer<ScreenshotHardwareBuffer> consumer);
private static native void nativeWriteListenerToParcel(long nativeObject, Parcel out);
private static native long nativeReadListenerFromParcel(Parcel in);
private static native long getNativeListenerFinalizer();
ScreenCaptureListener captureListener);
/**
* @param captureArgs Arguments about how to take the screenshot
* @param captureArgs Arguments about how to take the screenshot
* @param captureListener A listener to receive the screenshot callback
* @hide
*/
public static int captureDisplay(@NonNull DisplayCaptureArgs captureArgs,
@NonNull ScreenCaptureListener captureListener) {
return nativeCaptureDisplay(captureArgs, captureListener.mNativeObject);
return nativeCaptureDisplay(captureArgs, captureListener);
}
/**
@@ -71,8 +61,10 @@ public class ScreenCapture {
*/
public static ScreenshotHardwareBuffer captureDisplay(
DisplayCaptureArgs captureArgs) {
SyncScreenCaptureListener screenCaptureListener = new SyncScreenCaptureListener();
int status = captureDisplay(captureArgs, screenCaptureListener.getScreenCaptureListener());
SyncScreenCaptureListener
screenCaptureListener = new SyncScreenCaptureListener();
int status = captureDisplay(captureArgs, screenCaptureListener);
if (status != 0) {
return null;
}
@@ -83,13 +75,14 @@ public class ScreenCapture {
/**
* Captures a layer and its children and returns a {@link HardwareBuffer} with the content.
*
* @param layer The root layer to capture.
* @param sourceCrop The portion of the root surface to capture; caller may pass in 'new
* Rect()' or null if no cropping is desired. If the root layer does not
* have a buffer or a crop set, then a non-empty source crop must be
* specified.
* @param frameScale The desired scale of the returned buffer; the raw screen will be scaled
* up/down.
* @param layer The root layer to capture.
* @param sourceCrop The portion of the root surface to capture; caller may pass in 'new
* Rect()' or null if no cropping is desired. If the root layer does not
* have a buffer or a crop set, then a non-empty source crop must be
* specified.
* @param frameScale The desired scale of the returned buffer; the raw
* screen will be scaled up/down.
*
* @return Returns a HardwareBuffer that contains the layer capture.
* @hide
*/
@@ -101,14 +94,15 @@ public class ScreenCapture {
/**
* Captures a layer and its children and returns a {@link HardwareBuffer} with the content.
*
* @param layer The root layer to capture.
* @param sourceCrop The portion of the root surface to capture; caller may pass in 'new
* Rect()' or null if no cropping is desired. If the root layer does not
* have a buffer or a crop set, then a non-empty source crop must be
* specified.
* @param frameScale The desired scale of the returned buffer; the raw screen will be scaled
* up/down.
* @param format The desired pixel format of the returned buffer.
* @param layer The root layer to capture.
* @param sourceCrop The portion of the root surface to capture; caller may pass in 'new
* Rect()' or null if no cropping is desired. If the root layer does not
* have a buffer or a crop set, then a non-empty source crop must be
* specified.
* @param frameScale The desired scale of the returned buffer; the raw
* screen will be scaled up/down.
* @param format The desired pixel format of the returned buffer.
*
* @return Returns a HardwareBuffer that contains the layer capture.
* @hide
*/
@@ -130,7 +124,7 @@ public class ScreenCapture {
LayerCaptureArgs captureArgs) {
SyncScreenCaptureListener screenCaptureListener = new SyncScreenCaptureListener();
int status = captureLayers(captureArgs, screenCaptureListener.getScreenCaptureListener());
int status = captureLayers(captureArgs, screenCaptureListener);
if (status != 0) {
return null;
}
@@ -141,7 +135,6 @@ public class ScreenCapture {
/**
* Like {@link #captureLayers(SurfaceControl, Rect, float, int)} but with an array of layer
* handles to exclude.
*
* @hide
*/
public static ScreenshotHardwareBuffer captureLayersExcluding(SurfaceControl layer,
@@ -157,13 +150,24 @@ public class ScreenCapture {
}
/**
* @param captureArgs Arguments about how to take the screenshot
* @param captureArgs Arguments about how to take the screenshot
* @param captureListener A listener to receive the screenshot callback
* @hide
*/
public static int captureLayers(@NonNull LayerCaptureArgs captureArgs,
@NonNull ScreenCaptureListener captureListener) {
return nativeCaptureLayers(captureArgs, captureListener.mNativeObject);
return nativeCaptureLayers(captureArgs, captureListener);
}
/**
* @hide
*/
public interface ScreenCaptureListener {
/**
* The callback invoked when the screen capture is complete.
* @param hardwareBuffer Data containing info about the screen capture.
*/
void onScreenCaptureComplete(ScreenshotHardwareBuffer hardwareBuffer);
}
/**
@@ -186,16 +190,15 @@ public class ScreenCapture {
mContainsHdrLayers = containsHdrLayers;
}
/**
* Create ScreenshotHardwareBuffer from an existing HardwareBuffer object.
*
* @param hardwareBuffer The existing HardwareBuffer object
* @param namedColorSpace Integer value of a named color space {@link ColorSpace.Named}
* @param containsSecureLayers Indicates whether this graphic buffer contains captured
* contents of secure layers, in which case the screenshot
* should not be persisted.
* @param containsHdrLayers Indicates whether this graphic buffer contains HDR content.
*/
/**
* Create ScreenshotHardwareBuffer from an existing HardwareBuffer object.
* @param hardwareBuffer The existing HardwareBuffer object
* @param namedColorSpace Integer value of a named color space {@link ColorSpace.Named}
* @param containsSecureLayers Indicates whether this graphic buffer contains captured
* contents of secure layers, in which case the screenshot
* should not be persisted.
* @param containsHdrLayers Indicates whether this graphic buffer contains HDR content.
*/
private static ScreenshotHardwareBuffer createFromNative(HardwareBuffer hardwareBuffer,
int namedColorSpace, boolean containsSecureLayers, boolean containsHdrLayers) {
ColorSpace colorSpace = ColorSpace.get(ColorSpace.Named.values()[namedColorSpace]);
@@ -217,7 +220,6 @@ public class ScreenCapture {
public boolean containsSecureLayers() {
return mContainsSecureLayers;
}
/**
* Returns whether the screenshot contains at least one HDR layer.
* This information may be useful for informing the display whether this screenshot
@@ -232,7 +234,7 @@ public class ScreenCapture {
* Note: If you want to modify the Bitmap in software, you will need to copy the Bitmap
* into
* a software Bitmap using {@link Bitmap#copy(Bitmap.Config, boolean)}
* <p>
*
* CAVEAT: This can be extremely slow; avoid use unless absolutely necessary; prefer to
* directly
* use the {@link HardwareBuffer} directly.
@@ -248,23 +250,44 @@ public class ScreenCapture {
}
}
private static class SyncScreenCaptureListener implements ScreenCaptureListener {
private static final int SCREENSHOT_WAIT_TIME_S = 1;
private ScreenshotHardwareBuffer mScreenshotHardwareBuffer;
private final CountDownLatch mCountDownLatch = new CountDownLatch(1);
@Override
public void onScreenCaptureComplete(ScreenshotHardwareBuffer hardwareBuffer) {
mScreenshotHardwareBuffer = hardwareBuffer;
mCountDownLatch.countDown();
}
private ScreenshotHardwareBuffer waitForScreenshot() {
try {
mCountDownLatch.await(SCREENSHOT_WAIT_TIME_S, TimeUnit.SECONDS);
} catch (Exception e) {
Log.e(TAG, "Failed to wait for screen capture result", e);
}
return mScreenshotHardwareBuffer;
}
}
/**
* A common arguments class used for various screenshot requests. This contains arguments that
* are shared between {@link DisplayCaptureArgs} and {@link LayerCaptureArgs}
*
* @hide
*/
public static class CaptureArgs implements Parcelable {
public final int mPixelFormat;
public final Rect mSourceCrop = new Rect();
public final float mFrameScaleX;
public final float mFrameScaleY;
public final boolean mCaptureSecureLayers;
public final boolean mAllowProtected;
public final long mUid;
public final boolean mGrayscale;
private abstract static class CaptureArgs {
private final int mPixelFormat;
private final Rect mSourceCrop = new Rect();
private final float mFrameScaleX;
private final float mFrameScaleY;
private final boolean mCaptureSecureLayers;
private final boolean mAllowProtected;
private final long mUid;
private final boolean mGrayscale;
private CaptureArgs(CaptureArgs.Builder<? extends CaptureArgs.Builder<?>> builder) {
private CaptureArgs(Builder<? extends Builder<?>> builder) {
mPixelFormat = builder.mPixelFormat;
mSourceCrop.set(builder.mSourceCrop);
mFrameScaleX = builder.mFrameScaleX;
@@ -275,23 +298,12 @@ public class ScreenCapture {
mGrayscale = builder.mGrayscale;
}
private CaptureArgs(Parcel in) {
mPixelFormat = in.readInt();
mSourceCrop.readFromParcel(in);
mFrameScaleX = in.readFloat();
mFrameScaleY = in.readFloat();
mCaptureSecureLayers = in.readBoolean();
mAllowProtected = in.readBoolean();
mUid = in.readLong();
mGrayscale = in.readBoolean();
}
/**
* The Builder class used to construct {@link CaptureArgs}
*
* @param <T> A builder that extends {@link CaptureArgs.Builder}
* @param <T> A builder that extends {@link Builder}
*/
public static class Builder<T extends CaptureArgs.Builder<T>> {
abstract static class Builder<T extends Builder<T>> {
private int mPixelFormat = PixelFormat.RGBA_8888;
private final Rect mSourceCrop = new Rect();
private float mFrameScaleX = 1;
@@ -301,14 +313,6 @@ public class ScreenCapture {
private long mUid = -1;
private boolean mGrayscale;
/**
* Construct a new {@link CaptureArgs} with the set parameters. The builder remains
* valid.
*/
public CaptureArgs build() {
return new CaptureArgs(this);
}
/**
* The desired pixel format of the returned buffer.
*/
@@ -391,47 +395,15 @@ public class ScreenCapture {
/**
* Each sub class should return itself to allow the builder to chain properly
*/
T getThis() {
return (T) this;
}
abstract T getThis();
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeInt(mPixelFormat);
mSourceCrop.writeToParcel(dest, flags);
dest.writeFloat(mFrameScaleX);
dest.writeFloat(mFrameScaleY);
dest.writeBoolean(mCaptureSecureLayers);
dest.writeBoolean(mAllowProtected);
dest.writeLong(mUid);
dest.writeBoolean(mGrayscale);
}
public static final Parcelable.Creator<CaptureArgs> CREATOR =
new Parcelable.Creator<CaptureArgs>() {
@Override
public CaptureArgs createFromParcel(Parcel in) {
return new CaptureArgs(in);
}
@Override
public CaptureArgs[] newArray(int size) {
return new CaptureArgs[size];
}
};
}
/**
* The arguments class used to make display capture requests.
*
* @see #nativeCaptureDisplay(DisplayCaptureArgs, ScreenCaptureListener)
* @hide
* @see #nativeCaptureDisplay(DisplayCaptureArgs, long)
*/
public static class DisplayCaptureArgs extends CaptureArgs {
private final IBinder mDisplayToken;
@@ -516,8 +488,8 @@ public class ScreenCapture {
/**
* The arguments class used to make layer capture requests.
*
* @see #nativeCaptureLayers(LayerCaptureArgs, ScreenCaptureListener)
* @hide
* @see #nativeCaptureLayers(LayerCaptureArgs, long)
*/
public static class LayerCaptureArgs extends CaptureArgs {
private final long mNativeLayer;
@@ -558,17 +530,6 @@ public class ScreenCapture {
return new LayerCaptureArgs(this);
}
public Builder(SurfaceControl layer, CaptureArgs args) {
setLayer(layer);
setPixelFormat(args.mPixelFormat);
setSourceCrop(args.mSourceCrop);
setFrameScale(args.mFrameScaleX, args.mFrameScaleY);
setCaptureSecureLayers(args.mCaptureSecureLayers);
setAllowProtected(args.mAllowProtected);
setUid(args.mUid);
setGrayscale(args.mGrayscale);
}
public Builder(SurfaceControl layer) {
setLayer(layer);
}
@@ -581,6 +542,7 @@ public class ScreenCapture {
return this;
}
/**
* An array of layer handles to exclude.
*/
@@ -602,106 +564,8 @@ public class ScreenCapture {
Builder getThis() {
return this;
}
}
}
/**
* The object used to receive the results when invoking screen capture requests via
* {@link #captureDisplay(DisplayCaptureArgs, ScreenCaptureListener)} or
* {@link #captureLayers(LayerCaptureArgs, ScreenCaptureListener)}
*/
public static class ScreenCaptureListener implements Parcelable {
private final long mNativeObject;
private static final NativeAllocationRegistry sRegistry =
NativeAllocationRegistry.createMalloced(
ScreenCaptureListener.class.getClassLoader(), getNativeListenerFinalizer());
/**
* @param consumer The callback invoked when the screen capture is complete.
*/
public ScreenCaptureListener(Consumer<ScreenshotHardwareBuffer> consumer) {
mNativeObject = nativeCreateScreenCaptureListener(consumer);
sRegistry.registerNativeAllocation(this, mNativeObject);
}
private ScreenCaptureListener(Parcel in) {
if (in.readBoolean()) {
mNativeObject = nativeReadListenerFromParcel(in);
sRegistry.registerNativeAllocation(this, mNativeObject);
} else {
mNativeObject = 0;
}
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
if (mNativeObject == 0) {
dest.writeBoolean(false);
} else {
dest.writeBoolean(true);
nativeWriteListenerToParcel(mNativeObject, dest);
}
}
public static final Parcelable.Creator<ScreenCaptureListener> CREATOR =
new Parcelable.Creator<ScreenCaptureListener>() {
@Override
public ScreenCaptureListener createFromParcel(Parcel in) {
return new ScreenCaptureListener(in);
}
@Override
public ScreenCaptureListener[] newArray(int size) {
return new ScreenCaptureListener[0];
}
};
}
/**
* A helper class to handle the async screencapture callbacks synchronously. This should only
* be used if the screencapture caller doesn't care that it blocks waiting for a screenshot.
*/
public static class SyncScreenCaptureListener {
private static final int SCREENSHOT_WAIT_TIME_S = 1;
private ScreenshotHardwareBuffer mScreenshotHardwareBuffer;
private final CountDownLatch mCountDownLatch = new CountDownLatch(1);
private final ScreenCaptureListener mScreenCaptureListener;
public SyncScreenCaptureListener() {
mScreenCaptureListener = new ScreenCaptureListener(screenshotHardwareBuffer -> {
mScreenshotHardwareBuffer = screenshotHardwareBuffer;
mCountDownLatch.countDown();
});
}
/**
* @return The underlying {@link ScreenCaptureListener}
*/
public ScreenCaptureListener getScreenCaptureListener() {
return mScreenCaptureListener;
}
/**
* Waits until the screenshot callback has been invoked and the screenshot is ready. This
* can return {@code null} if the screenshot callback wasn't invoked after
* {@link #SCREENSHOT_WAIT_TIME_S} or the screencapture request resulted in an error
*
* @return A ScreenshotHardwareBuffer for the content that was captured.
*/
@Nullable
public ScreenshotHardwareBuffer waitForScreenshot() {
try {
mCountDownLatch.await(SCREENSHOT_WAIT_TIME_S, TimeUnit.SECONDS);
} catch (Exception e) {
Log.e(TAG, "Failed to wait for screen capture result", e);
}
return mScreenshotHardwareBuffer;
}
}
}

View File

@@ -61,8 +61,9 @@ static struct {
} gLayerCaptureArgsClassInfo;
static struct {
jmethodID accept;
} gConsumerClassInfo;
jclass clazz;
jmethodID onScreenCaptureComplete;
} gScreenCaptureListenerClassInfo;
static struct {
jclass clazz;
@@ -97,14 +98,14 @@ class ScreenCaptureListenerWrapper : public gui::BnScreenCaptureListener {
public:
explicit ScreenCaptureListenerWrapper(JNIEnv* env, jobject jobject) {
env->GetJavaVM(&mVm);
mConsumerObject = env->NewGlobalRef(jobject);
LOG_ALWAYS_FATAL_IF(!mConsumerObject, "Failed to make global ref");
mScreenCaptureListenerObject = env->NewGlobalRef(jobject);
LOG_ALWAYS_FATAL_IF(!mScreenCaptureListenerObject, "Failed to make global ref");
}
~ScreenCaptureListenerWrapper() {
if (mConsumerObject) {
getenv()->DeleteGlobalRef(mConsumerObject);
mConsumerObject = nullptr;
if (mScreenCaptureListenerObject) {
getenv()->DeleteGlobalRef(mScreenCaptureListenerObject);
mScreenCaptureListenerObject = nullptr;
}
}
@@ -112,8 +113,9 @@ public:
const gui::ScreenCaptureResults& captureResults) override {
JNIEnv* env = getenv();
if (!captureResults.fenceResult.ok() || captureResults.buffer == nullptr) {
env->CallVoidMethod(mConsumerObject, gConsumerClassInfo.accept, nullptr);
checkAndClearException(env, "accept");
env->CallVoidMethod(mScreenCaptureListenerObject,
gScreenCaptureListenerClassInfo.onScreenCaptureComplete, nullptr);
checkAndClearException(env, "onScreenCaptureComplete");
return binder::Status::ok();
}
captureResults.fenceResult.value()->waitForever(LOG_TAG);
@@ -128,15 +130,17 @@ public:
captureResults.capturedSecureLayers,
captureResults.capturedHdrLayers);
checkAndClearException(env, "builder");
env->CallVoidMethod(mConsumerObject, gConsumerClassInfo.accept, screenshotHardwareBuffer);
checkAndClearException(env, "accept");
env->CallVoidMethod(mScreenCaptureListenerObject,
gScreenCaptureListenerClassInfo.onScreenCaptureComplete,
screenshotHardwareBuffer);
checkAndClearException(env, "onScreenCaptureComplete");
env->DeleteLocalRef(jhardwareBuffer);
env->DeleteLocalRef(screenshotHardwareBuffer);
return binder::Status::ok();
}
private:
jobject mConsumerObject;
jobject mScreenCaptureListenerObject;
JavaVM* mVm;
JNIEnv* getenv() {
@@ -190,7 +194,7 @@ static DisplayCaptureArgs displayCaptureArgsFromObject(JNIEnv* env,
}
static jint nativeCaptureDisplay(JNIEnv* env, jclass clazz, jobject displayCaptureArgsObject,
jlong screenCaptureListenerObject) {
jobject screenCaptureListenerObject) {
const DisplayCaptureArgs captureArgs =
displayCaptureArgsFromObject(env, displayCaptureArgsObject);
@@ -198,13 +202,13 @@ static jint nativeCaptureDisplay(JNIEnv* env, jclass clazz, jobject displayCaptu
return BAD_VALUE;
}
sp<gui::IScreenCaptureListener> captureListener =
reinterpret_cast<gui::IScreenCaptureListener*>(screenCaptureListenerObject);
sp<IScreenCaptureListener> captureListener =
sp<ScreenCaptureListenerWrapper>::make(env, screenCaptureListenerObject);
return ScreenshotClient::captureDisplay(captureArgs, captureListener);
}
static jint nativeCaptureLayers(JNIEnv* env, jclass clazz, jobject layerCaptureArgsObject,
jlong screenCaptureListenerObject) {
jobject screenCaptureListenerObject) {
LayerCaptureArgs captureArgs;
getCaptureArgs(env, layerCaptureArgsObject, captureArgs);
SurfaceControl* layer = reinterpret_cast<SurfaceControl*>(
@@ -234,70 +238,21 @@ static jint nativeCaptureLayers(JNIEnv* env, jclass clazz, jobject layerCaptureA
}
}
sp<gui::IScreenCaptureListener> captureListener =
reinterpret_cast<gui::IScreenCaptureListener*>(screenCaptureListenerObject);
sp<IScreenCaptureListener> captureListener =
sp<ScreenCaptureListenerWrapper>::make(env, screenCaptureListenerObject);
return ScreenshotClient::captureLayers(captureArgs, captureListener);
}
static jlong nativeCreateScreenCaptureListener(JNIEnv* env, jclass clazz, jobject consumerObj) {
sp<gui::IScreenCaptureListener> listener =
sp<ScreenCaptureListenerWrapper>::make(env, consumerObj);
listener->incStrong((void*)nativeCreateScreenCaptureListener);
return reinterpret_cast<jlong>(listener.get());
}
static void nativeWriteListenerToParcel(JNIEnv* env, jclass clazz, jlong nativeObject,
jobject parcelObj) {
Parcel* parcel = parcelForJavaObject(env, parcelObj);
if (parcel == NULL) {
jniThrowNullPointerException(env, NULL);
return;
}
ScreenCaptureListenerWrapper* const self =
reinterpret_cast<ScreenCaptureListenerWrapper*>(nativeObject);
if (self != nullptr) {
parcel->writeStrongBinder(IInterface::asBinder(self));
}
}
static jlong nativeReadListenerFromParcel(JNIEnv* env, jclass clazz, jobject parcelObj) {
Parcel* parcel = parcelForJavaObject(env, parcelObj);
if (parcel == NULL) {
jniThrowNullPointerException(env, NULL);
return 0;
}
sp<gui::IScreenCaptureListener> listener =
interface_cast<gui::IScreenCaptureListener>(parcel->readStrongBinder());
if (listener == nullptr) {
return 0;
}
listener->incStrong((void*)nativeCreateScreenCaptureListener);
return reinterpret_cast<jlong>(listener.get());
}
void destroyNativeListener(void* ptr) {
ScreenCaptureListenerWrapper* listener = reinterpret_cast<ScreenCaptureListenerWrapper*>(ptr);
listener->decStrong((void*)nativeCreateScreenCaptureListener);
}
static jlong getNativeListenerFinalizer(JNIEnv* env, jclass clazz) {
return static_cast<jlong>(reinterpret_cast<uintptr_t>(&destroyNativeListener));
}
// ----------------------------------------------------------------------------
static const JNINativeMethod sScreenCaptureMethods[] = {
// clang-format off
{"nativeCaptureDisplay", "(Landroid/window/ScreenCapture$DisplayCaptureArgs;J)I",
{"nativeCaptureDisplay",
"(Landroid/window/ScreenCapture$DisplayCaptureArgs;Landroid/window/ScreenCapture$ScreenCaptureListener;)I",
(void*)nativeCaptureDisplay },
{"nativeCaptureLayers", "(Landroid/window/ScreenCapture$LayerCaptureArgs;J)I",
{"nativeCaptureLayers",
"(Landroid/window/ScreenCapture$LayerCaptureArgs;Landroid/window/ScreenCapture$ScreenCaptureListener;)I",
(void*)nativeCaptureLayers },
{"nativeCreateScreenCaptureListener", "(Ljava/util/function/Consumer;)J",
(void*)nativeCreateScreenCaptureListener },
{"nativeWriteListenerToParcel", "(JLandroid/os/Parcel;)V", (void*)nativeWriteListenerToParcel },
{"nativeReadListenerFromParcel", "(Landroid/os/Parcel;)J",
(void*)nativeReadListenerFromParcel },
{"getNativeListenerFinalizer", "()J", (void*)getNativeListenerFinalizer },
// clang-format on
};
@@ -338,8 +293,12 @@ int register_android_window_ScreenCapture(JNIEnv* env) {
gLayerCaptureArgsClassInfo.childrenOnly =
GetFieldIDOrDie(env, layerCaptureArgsClazz, "mChildrenOnly", "Z");
jclass consumer = FindClassOrDie(env, "java/util/function/Consumer");
gConsumerClassInfo.accept = GetMethodIDOrDie(env, consumer, "accept", "(Ljava/lang/Object;)V");
jclass screenCaptureListenerClazz =
FindClassOrDie(env, "android/window/ScreenCapture$ScreenCaptureListener");
gScreenCaptureListenerClassInfo.clazz = MakeGlobalRefOrDie(env, screenCaptureListenerClazz);
gScreenCaptureListenerClassInfo.onScreenCaptureComplete =
GetMethodIDOrDie(env, screenCaptureListenerClazz, "onScreenCaptureComplete",
"(Landroid/window/ScreenCapture$ScreenshotHardwareBuffer;)V");
jclass screenshotGraphicsBufferClazz =
FindClassOrDie(env, "android/window/ScreenCapture$ScreenshotHardwareBuffer");

View File

@@ -9279,46 +9279,4 @@ public class WindowManagerService extends IWindowManager.Stub
"Unexpected letterbox background type: " + letterboxBackgroundType);
}
}
@Override
public void captureDisplay(int displayId, @Nullable ScreenCapture.CaptureArgs captureArgs,
ScreenCapture.ScreenCaptureListener listener) {
Slog.d(TAG, "captureDisplay");
if (!checkCallingPermission(READ_FRAME_BUFFER, "captureDisplay()")) {
throw new SecurityException("Requires READ_FRAME_BUFFER permission");
}
ScreenCapture.captureLayers(getCaptureArgs(displayId, captureArgs), listener);
}
@VisibleForTesting
ScreenCapture.LayerCaptureArgs getCaptureArgs(int displayId,
@Nullable ScreenCapture.CaptureArgs captureArgs) {
final SurfaceControl displaySurfaceControl;
synchronized (mGlobalLock) {
DisplayContent displayContent = mRoot.getDisplayContent(displayId);
if (displayContent == null) {
throw new IllegalArgumentException("Trying to screenshot and invalid display: "
+ displayId);
}
displaySurfaceControl = displayContent.getSurfaceControl();
if (captureArgs == null) {
captureArgs = new ScreenCapture.CaptureArgs.Builder<>()
.build();
}
if (captureArgs.mSourceCrop.isEmpty()) {
displayContent.getBounds(mTmpRect);
mTmpRect.offsetTo(0, 0);
} else {
mTmpRect.set(captureArgs.mSourceCrop);
}
}
return new ScreenCapture.LayerCaptureArgs.Builder(displaySurfaceControl, captureArgs)
.setSourceCrop(mTmpRect)
.build();
}
}

View File

@@ -79,10 +79,7 @@
<activity android:name="com.android.server.wm.ActivityOptionsTest$MainActivity"
android:turnScreenOn="true"
android:showWhenLocked="true" />
<activity android:name="com.android.server.wm.ScreenshotTests$ScreenshotActivity"
android:theme="@style/WhiteBackgroundTheme"
android:turnScreenOn="true"
android:showWhenLocked="true"/>
<activity android:name="com.android.server.wm.ScreenshotTests$ScreenshotActivity" />
<activity android:name="android.view.cts.surfacevalidator.CapturedActivity"/>
<service android:name="android.view.cts.surfacevalidator.LocalMediaProjectionService"

View File

@@ -1,24 +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.
-->
<resources>
<style name="WhiteBackgroundTheme" parent="@android:style/Theme.DeviceDefault">
<item name="android:windowNoTitle">true</item>
<item name="android:windowFullscreen">true</item>
<item name="android:windowIsTranslucent">true</item>
<item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
</style>
</resources>

View File

@@ -16,10 +16,6 @@
package com.android.server.wm;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.WindowInsets.Type.displayCutout;
import static android.view.WindowInsets.Type.statusBars;
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
import static org.junit.Assert.assertNotNull;
@@ -27,31 +23,20 @@ import static org.junit.Assert.assertTrue;
import android.app.Activity;
import android.app.Instrumentation;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.ColorSpace;
import android.graphics.GraphicBuffer;
import android.graphics.Insets;
import android.graphics.PixelFormat;
import android.graphics.Point;
import android.graphics.Rect;
import android.hardware.DataSpace;
import android.hardware.HardwareBuffer;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.platform.test.annotations.Presubmit;
import android.view.IWindowManager;
import android.view.PointerIcon;
import android.view.SurfaceControl;
import android.view.cts.surfacevalidator.BitmapPixelChecker;
import android.view.cts.surfacevalidator.PixelColor;
import android.view.cts.surfacevalidator.SaveBitmapHelper;
import android.view.WindowManager;
import android.window.ScreenCapture;
import android.window.ScreenCapture.SyncScreenCaptureListener;
import androidx.annotation.Nullable;
import androidx.test.filters.SmallTest;
@@ -60,7 +45,6 @@ import androidx.test.rule.ActivityTestRule;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TestName;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
@@ -76,8 +60,6 @@ public class ScreenshotTests {
private static final int BUFFER_HEIGHT = 100;
private final Instrumentation mInstrumentation = getInstrumentation();
@Rule
public TestName mTestName = new TestName();
@Rule
public ActivityTestRule<ScreenshotActivity> mActivityRule =
@@ -113,8 +95,8 @@ public class ScreenshotTests {
buffer.unlockCanvasAndPost(canvas);
t.show(secureSC)
.setBuffer(secureSC, HardwareBuffer.createFromGraphicBuffer(buffer))
.setDataSpace(secureSC, DataSpace.DATASPACE_SRGB)
.setBuffer(secureSC, buffer)
.setColorSpace(secureSC, ColorSpace.get(ColorSpace.Named.SRGB))
.apply(true);
ScreenCapture.LayerCaptureArgs args = new ScreenCapture.LayerCaptureArgs.Builder(secureSC)
@@ -130,69 +112,15 @@ public class ScreenshotTests {
Bitmap swBitmap = screenshot.copy(Bitmap.Config.ARGB_8888, false);
screenshot.recycle();
BitmapPixelChecker bitmapPixelChecker = new BitmapPixelChecker(PixelColor.RED);
Rect bounds = new Rect(0, 0, swBitmap.getWidth(), swBitmap.getHeight());
int numMatchingPixels = bitmapPixelChecker.getNumMatchingPixels(swBitmap, bounds);
int sizeOfBitmap = bounds.width() * bounds.height();
int numMatchingPixels = PixelChecker.getNumMatchingPixels(swBitmap,
new PixelColor(PixelColor.RED));
long sizeOfBitmap = swBitmap.getWidth() * swBitmap.getHeight();
boolean success = numMatchingPixels == sizeOfBitmap;
swBitmap.recycle();
assertTrue(success);
}
@Test
public void testCaptureDisplay() throws RemoteException {
IWindowManager windowManager = IWindowManager.Stub.asInterface(
ServiceManager.getService(Context.WINDOW_SERVICE));
SurfaceControl sc = new SurfaceControl.Builder()
.setName("Layer")
.setCallsite("testCaptureDisplay")
.build();
SurfaceControl.Transaction t = mActivity.addChildSc(sc);
mInstrumentation.waitForIdleSync();
GraphicBuffer buffer = GraphicBuffer.create(BUFFER_WIDTH, BUFFER_HEIGHT,
PixelFormat.RGBA_8888,
GraphicBuffer.USAGE_HW_TEXTURE | GraphicBuffer.USAGE_HW_COMPOSER
| GraphicBuffer.USAGE_SW_WRITE_RARELY);
Canvas canvas = buffer.lockCanvas();
canvas.drawColor(Color.RED);
buffer.unlockCanvasAndPost(canvas);
Point point = mActivity.getPositionBelowStatusBar();
t.show(sc)
.setBuffer(sc, HardwareBuffer.createFromGraphicBuffer(buffer))
.setDataSpace(sc, DataSpace.DATASPACE_SRGB)
.setPosition(sc, point.x, point.y)
.apply(true);
SyncScreenCaptureListener listener = new SyncScreenCaptureListener();
windowManager.captureDisplay(DEFAULT_DISPLAY, null, listener.getScreenCaptureListener());
ScreenCapture.ScreenshotHardwareBuffer hardwareBuffer = listener.waitForScreenshot();
assertNotNull(hardwareBuffer);
Bitmap screenshot = hardwareBuffer.asBitmap();
assertNotNull(screenshot);
Bitmap swBitmap = screenshot.copy(Bitmap.Config.ARGB_8888, false);
screenshot.recycle();
BitmapPixelChecker bitmapPixelChecker = new BitmapPixelChecker(PixelColor.RED);
Rect bounds = new Rect(point.x, point.y, BUFFER_WIDTH + point.x, BUFFER_HEIGHT + point.y);
int numMatchingPixels = bitmapPixelChecker.getNumMatchingPixels(swBitmap, bounds);
int pixelMatchSize = bounds.width() * bounds.height();
boolean success = numMatchingPixels == pixelMatchSize;
if (!success) {
SaveBitmapHelper.saveBitmap(swBitmap, getClass(), mTestName, "failedImage");
}
swBitmap.recycle();
assertTrue("numMatchingPixels=" + numMatchingPixels + " pixelMatchSize=" + pixelMatchSize,
success);
}
public static class ScreenshotActivity extends Activity {
private static final long WAIT_TIMEOUT_S = 5;
private final Handler mHandler = new Handler(Looper.getMainLooper());
@@ -202,6 +130,7 @@ public class ScreenshotTests {
super.onCreate(savedInstanceState);
getWindow().getDecorView().setPointerIcon(
PointerIcon.getSystemIcon(this, PointerIcon.TYPE_NULL));
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
}
SurfaceControl.Transaction addChildSc(SurfaceControl surfaceControl) {
@@ -219,14 +148,88 @@ public class ScreenshotTests {
}
return t;
}
}
public Point getPositionBelowStatusBar() {
Insets statusBarInsets = getWindow()
.getDecorView()
.getRootWindowInsets()
.getInsets(statusBars() | displayCutout());
public abstract static class PixelChecker {
static int getNumMatchingPixels(Bitmap bitmap, PixelColor pixelColor) {
int numMatchingPixels = 0;
for (int x = 0; x < bitmap.getWidth(); x++) {
for (int y = 0; y < bitmap.getHeight(); y++) {
int color = bitmap.getPixel(x, y);
if (matchesColor(pixelColor, color)) {
numMatchingPixels++;
}
}
}
return numMatchingPixels;
}
return new Point(statusBarInsets.left, statusBarInsets.top);
static boolean matchesColor(PixelColor expectedColor, int color) {
final float red = Color.red(color);
final float green = Color.green(color);
final float blue = Color.blue(color);
final float alpha = Color.alpha(color);
return alpha <= expectedColor.mMaxAlpha
&& alpha >= expectedColor.mMinAlpha
&& red <= expectedColor.mMaxRed
&& red >= expectedColor.mMinRed
&& green <= expectedColor.mMaxGreen
&& green >= expectedColor.mMinGreen
&& blue <= expectedColor.mMaxBlue
&& blue >= expectedColor.mMinBlue;
}
}
public static class PixelColor {
public static final int BLACK = 0xFF000000;
public static final int RED = 0xFF0000FF;
public static final int GREEN = 0xFF00FF00;
public static final int BLUE = 0xFFFF0000;
public static final int YELLOW = 0xFF00FFFF;
public static final int MAGENTA = 0xFFFF00FF;
public static final int WHITE = 0xFFFFFFFF;
public static final int TRANSPARENT_RED = 0x7F0000FF;
public static final int TRANSPARENT_BLUE = 0x7FFF0000;
public static final int TRANSPARENT = 0x00000000;
// Default to black
public short mMinAlpha;
public short mMaxAlpha;
public short mMinRed;
public short mMaxRed;
public short mMinBlue;
public short mMaxBlue;
public short mMinGreen;
public short mMaxGreen;
public PixelColor(int color) {
short alpha = (short) ((color >> 24) & 0xFF);
short blue = (short) ((color >> 16) & 0xFF);
short green = (short) ((color >> 8) & 0xFF);
short red = (short) (color & 0xFF);
mMinAlpha = (short) getMinValue(alpha);
mMaxAlpha = (short) getMaxValue(alpha);
mMinRed = (short) getMinValue(red);
mMaxRed = (short) getMaxValue(red);
mMinBlue = (short) getMinValue(blue);
mMaxBlue = (short) getMaxValue(blue);
mMinGreen = (short) getMinValue(green);
mMaxGreen = (short) getMaxValue(green);
}
public PixelColor() {
this(BLACK);
}
private int getMinValue(short color) {
return Math.max(color - 4, 0);
}
private int getMaxValue(short color) {
return Math.min(color + 4, 0xFF);
}
}
}

View File

@@ -41,7 +41,6 @@ import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_BACKGROUND_
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.mockito.ArgumentMatchers.any;
@@ -69,7 +68,6 @@ import android.view.SurfaceControl;
import android.view.View;
import android.view.WindowManager;
import android.window.ClientWindowFrames;
import android.window.ScreenCapture;
import android.window.WindowContainerToken;
import androidx.test.filters.SmallTest;
@@ -425,45 +423,6 @@ public class WindowManagerServiceTests extends WindowTestsBase {
LETTERBOX_BACKGROUND_SOLID_COLOR)).isFalse();
}
@Test
public void testCaptureDisplay() {
Rect displayBounds = new Rect(0, 0, 100, 200);
spyOn(mDisplayContent);
when(mDisplayContent.getBounds()).thenReturn(displayBounds);
// Null captureArgs
ScreenCapture.LayerCaptureArgs resultingArgs =
mWm.getCaptureArgs(DEFAULT_DISPLAY, null /* captureArgs */);
assertEquals(displayBounds, resultingArgs.mSourceCrop);
// Non null captureArgs, didn't set rect
ScreenCapture.CaptureArgs captureArgs = new ScreenCapture.CaptureArgs.Builder<>().build();
resultingArgs = mWm.getCaptureArgs(DEFAULT_DISPLAY, captureArgs);
assertEquals(displayBounds, resultingArgs.mSourceCrop);
// Non null captureArgs, invalid rect
captureArgs = new ScreenCapture.CaptureArgs.Builder<>()
.setSourceCrop(new Rect(0, 0, -1, -1))
.build();
resultingArgs = mWm.getCaptureArgs(DEFAULT_DISPLAY, captureArgs);
assertEquals(displayBounds, resultingArgs.mSourceCrop);
// Non null captureArgs, null rect
captureArgs = new ScreenCapture.CaptureArgs.Builder<>()
.setSourceCrop(null)
.build();
resultingArgs = mWm.getCaptureArgs(DEFAULT_DISPLAY, captureArgs);
assertEquals(displayBounds, resultingArgs.mSourceCrop);
// Non null captureArgs, valid rect
Rect validRect = new Rect(0, 0, 10, 50);
captureArgs = new ScreenCapture.CaptureArgs.Builder<>()
.setSourceCrop(validRect)
.build();
resultingArgs = mWm.getCaptureArgs(DEFAULT_DISPLAY, captureArgs);
assertEquals(validRect, resultingArgs.mSourceCrop);
}
private void setupActivityWithLaunchCookie(IBinder launchCookie, WindowContainerToken wct) {
final WindowContainer.RemoteToken remoteToken = mock(WindowContainer.RemoteToken.class);
when(remoteToken.toWindowContainerToken()).thenReturn(wct);