diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl index 067946e903616..b2a26fa665f89 100644 --- a/core/java/android/view/IWindowManager.aidl +++ b/core/java/android/view/IWindowManager.aidl @@ -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); } diff --git a/core/java/android/window/ScreenCapture.aidl b/core/java/android/window/ScreenCapture.aidl deleted file mode 100644 index 267a7c60b60d6..0000000000000 --- a/core/java/android/window/ScreenCapture.aidl +++ /dev/null @@ -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; \ No newline at end of file diff --git a/core/java/android/window/ScreenCapture.java b/core/java/android/window/ScreenCapture.java index b8f39a990cdda..887d027d26a83 100644 --- a/core/java/android/window/ScreenCapture.java +++ b/core/java/android/window/ScreenCapture.java @@ -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 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)} - *

+ * * 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> builder) { + private CaptureArgs(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 A builder that extends {@link CaptureArgs.Builder} + * @param A builder that extends {@link Builder} */ - public static class Builder> { + abstract static class Builder> { 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 CREATOR = - new Parcelable.Creator() { - @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 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 CREATOR = - new Parcelable.Creator() { - @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; - } - } } diff --git a/core/jni/android_window_ScreenCapture.cpp b/core/jni/android_window_ScreenCapture.cpp index c1929c6535fb1..3bada15aa8344 100644 --- a/core/jni/android_window_ScreenCapture.cpp +++ b/core/jni/android_window_ScreenCapture.cpp @@ -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 captureListener = - reinterpret_cast(screenCaptureListenerObject); + sp captureListener = + sp::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( @@ -234,70 +238,21 @@ static jint nativeCaptureLayers(JNIEnv* env, jclass clazz, jobject layerCaptureA } } - sp captureListener = - reinterpret_cast(screenCaptureListenerObject); + sp captureListener = + sp::make(env, screenCaptureListenerObject); return ScreenshotClient::captureLayers(captureArgs, captureListener); } -static jlong nativeCreateScreenCaptureListener(JNIEnv* env, jclass clazz, jobject consumerObj) { - sp listener = - sp::make(env, consumerObj); - listener->incStrong((void*)nativeCreateScreenCaptureListener); - return reinterpret_cast(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(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 listener = - interface_cast(parcel->readStrongBinder()); - if (listener == nullptr) { - return 0; - } - listener->incStrong((void*)nativeCreateScreenCaptureListener); - return reinterpret_cast(listener.get()); -} - -void destroyNativeListener(void* ptr) { - ScreenCaptureListenerWrapper* listener = reinterpret_cast(ptr); - listener->decStrong((void*)nativeCreateScreenCaptureListener); -} - -static jlong getNativeListenerFinalizer(JNIEnv* env, jclass clazz) { - return static_cast(reinterpret_cast(&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"); diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 4e68c00ff23bf..43ef1d4d778d3 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -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(); - } } diff --git a/services/tests/wmtests/AndroidManifest.xml b/services/tests/wmtests/AndroidManifest.xml index 107bbe1c79e3a..2918365b94c87 100644 --- a/services/tests/wmtests/AndroidManifest.xml +++ b/services/tests/wmtests/AndroidManifest.xml @@ -79,10 +79,7 @@ - + - - - - diff --git a/services/tests/wmtests/src/com/android/server/wm/ScreenshotTests.java b/services/tests/wmtests/src/com/android/server/wm/ScreenshotTests.java index 8cd8e9bde1b75..0b58428e9bfbf 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ScreenshotTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ScreenshotTests.java @@ -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 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); } } } diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java index 8b63904baf312..46b4b76dc12fe 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java @@ -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);