| /* |
| * 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; |
| |
| import android.annotation.NonNull; |
| import android.annotation.Nullable; |
| import android.graphics.Bitmap; |
| import android.graphics.ColorSpace; |
| import android.graphics.PixelFormat; |
| import android.graphics.Rect; |
| import android.hardware.HardwareBuffer; |
| import android.os.Build; |
| import android.os.IBinder; |
| import android.os.Parcel; |
| import android.os.Parcelable; |
| import android.util.Log; |
| import android.view.SurfaceControl; |
| |
| import com.android.window.flags.Flags; |
| |
| import libcore.util.NativeAllocationRegistry; |
| |
| import java.util.concurrent.CountDownLatch; |
| import java.util.concurrent.TimeUnit; |
| import java.util.function.ObjIntConsumer; |
| |
| /** |
| * Handles display and layer captures for the system. |
| * |
| * @hide |
| */ |
| public class ScreenCapture { |
| private static final String TAG = "ScreenCapture"; |
| private static final int SCREENSHOT_WAIT_TIME_S = 4 * Build.HW_TIMEOUT_MULTIPLIER; |
| |
| private static native int nativeCaptureDisplay(DisplayCaptureArgs captureArgs, |
| long captureListener); |
| private static native int nativeCaptureLayers(LayerCaptureArgs captureArgs, |
| long captureListener, boolean sync); |
| private static native long nativeCreateScreenCaptureListener( |
| ObjIntConsumer<ScreenshotHardwareBuffer> consumer); |
| private static native void nativeWriteListenerToParcel(long nativeObject, Parcel out); |
| private static native long nativeReadListenerFromParcel(Parcel in); |
| private static native long getNativeListenerFinalizer(); |
| |
| /** |
| * @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); |
| } |
| |
| /** |
| * Captures all the surfaces in a display and returns a {@link ScreenshotHardwareBuffer} with |
| * the content. |
| * |
| * @hide |
| */ |
| public static ScreenshotHardwareBuffer captureDisplay( |
| DisplayCaptureArgs captureArgs) { |
| SynchronousScreenCaptureListener syncScreenCapture = createSyncCaptureListener(); |
| int status = captureDisplay(captureArgs, syncScreenCapture); |
| if (status != 0) { |
| return null; |
| } |
| |
| try { |
| return syncScreenCapture.getBuffer(); |
| } catch (Exception e) { |
| return null; |
| } |
| } |
| |
| /** |
| * 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. |
| * @return Returns a HardwareBuffer that contains the layer capture. |
| * @hide |
| */ |
| public static ScreenshotHardwareBuffer captureLayers(SurfaceControl layer, Rect sourceCrop, |
| float frameScale) { |
| return captureLayers(layer, sourceCrop, frameScale, PixelFormat.RGBA_8888); |
| } |
| |
| /** |
| * 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. |
| * @return Returns a HardwareBuffer that contains the layer capture. |
| * @hide |
| */ |
| public static ScreenshotHardwareBuffer captureLayers(@NonNull SurfaceControl layer, |
| @Nullable Rect sourceCrop, float frameScale, int format) { |
| LayerCaptureArgs captureArgs = new LayerCaptureArgs.Builder(layer) |
| .setSourceCrop(sourceCrop) |
| .setFrameScale(frameScale) |
| .setPixelFormat(format) |
| .build(); |
| |
| return captureLayers(captureArgs); |
| } |
| |
| /** |
| * @hide |
| */ |
| public static ScreenshotHardwareBuffer captureLayers(LayerCaptureArgs captureArgs) { |
| SynchronousScreenCaptureListener syncScreenCapture = createSyncCaptureListener(); |
| int status = nativeCaptureLayers(captureArgs, syncScreenCapture.mNativeObject, |
| Flags.syncScreenCapture()); |
| if (status != 0) { |
| return null; |
| } |
| |
| try { |
| return syncScreenCapture.getBuffer(); |
| } catch (Exception e) { |
| return null; |
| } |
| } |
| |
| /** |
| * Like {@link #captureLayers(SurfaceControl, Rect, float, int)} but with an array of layer |
| * handles to exclude. |
| * |
| * @hide |
| */ |
| public static ScreenshotHardwareBuffer captureLayersExcluding(SurfaceControl layer, |
| Rect sourceCrop, float frameScale, int format, SurfaceControl[] exclude) { |
| LayerCaptureArgs captureArgs = new LayerCaptureArgs.Builder(layer) |
| .setSourceCrop(sourceCrop) |
| .setFrameScale(frameScale) |
| .setPixelFormat(format) |
| .setExcludeLayers(exclude) |
| .build(); |
| |
| return captureLayers(captureArgs); |
| } |
| |
| /** |
| * @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, false /* sync */); |
| } |
| |
| /** |
| * A wrapper around HardwareBuffer that contains extra information about how to |
| * interpret the screenshot HardwareBuffer. |
| * |
| * @hide |
| */ |
| public static class ScreenshotHardwareBuffer { |
| private final HardwareBuffer mHardwareBuffer; |
| private final ColorSpace mColorSpace; |
| private final boolean mContainsSecureLayers; |
| private final boolean mContainsHdrLayers; |
| |
| public ScreenshotHardwareBuffer(HardwareBuffer hardwareBuffer, ColorSpace colorSpace, |
| boolean containsSecureLayers, boolean containsHdrLayers) { |
| mHardwareBuffer = hardwareBuffer; |
| mColorSpace = colorSpace; |
| mContainsSecureLayers = containsSecureLayers; |
| mContainsHdrLayers = containsHdrLayers; |
| } |
| |
| /** |
| * Create ScreenshotHardwareBuffer from an existing HardwareBuffer object. |
| * |
| * @param hardwareBuffer The existing HardwareBuffer object |
| * @param dataspace Dataspace describing the content. |
| * {@see android.hardware.DataSpace} |
| * @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 dataspace, boolean containsSecureLayers, boolean containsHdrLayers) { |
| ColorSpace colorSpace = ColorSpace.getFromDataSpace(dataspace); |
| return new ScreenshotHardwareBuffer( |
| hardwareBuffer, |
| colorSpace != null ? colorSpace : ColorSpace.get(ColorSpace.Named.SRGB), |
| containsSecureLayers, |
| containsHdrLayers); |
| } |
| |
| public ColorSpace getColorSpace() { |
| return mColorSpace; |
| } |
| |
| public HardwareBuffer getHardwareBuffer() { |
| return mHardwareBuffer; |
| } |
| |
| /** |
| * Whether this screenshot contains secure layers |
| */ |
| 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 |
| * is allowed to be dimmed to SDR white. |
| */ |
| public boolean containsHdrLayers() { |
| return mContainsHdrLayers; |
| } |
| |
| /** |
| * Copy content of ScreenshotHardwareBuffer into a hardware bitmap and return it. |
| * 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. |
| * |
| * @return Bitmap generated from the {@link HardwareBuffer} |
| */ |
| public Bitmap asBitmap() { |
| if (mHardwareBuffer == null) { |
| Log.w(TAG, "Failed to take screenshot. Null screenshot object"); |
| return null; |
| } |
| return Bitmap.wrapHardwareBuffer(mHardwareBuffer, mColorSpace); |
| } |
| } |
| |
| /** |
| * 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; |
| final SurfaceControl[] mExcludeLayers; |
| public final boolean mHintForSeamlessTransition; |
| |
| private CaptureArgs(CaptureArgs.Builder<? extends CaptureArgs.Builder<?>> builder) { |
| mPixelFormat = builder.mPixelFormat; |
| mSourceCrop.set(builder.mSourceCrop); |
| mFrameScaleX = builder.mFrameScaleX; |
| mFrameScaleY = builder.mFrameScaleY; |
| mCaptureSecureLayers = builder.mCaptureSecureLayers; |
| mAllowProtected = builder.mAllowProtected; |
| mUid = builder.mUid; |
| mGrayscale = builder.mGrayscale; |
| mExcludeLayers = builder.mExcludeLayers; |
| mHintForSeamlessTransition = builder.mHintForSeamlessTransition; |
| } |
| |
| 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(); |
| |
| int excludeLayersLength = in.readInt(); |
| if (excludeLayersLength > 0) { |
| mExcludeLayers = new SurfaceControl[excludeLayersLength]; |
| for (int index = 0; index < excludeLayersLength; index++) { |
| mExcludeLayers[index] = SurfaceControl.CREATOR.createFromParcel(in); |
| } |
| } else { |
| mExcludeLayers = null; |
| } |
| mHintForSeamlessTransition = in.readBoolean(); |
| } |
| |
| /** Release any layers if set using {@link Builder#setExcludeLayers(SurfaceControl[])}. */ |
| public void release() { |
| if (mExcludeLayers == null || mExcludeLayers.length == 0) { |
| return; |
| } |
| |
| for (SurfaceControl surfaceControl : mExcludeLayers) { |
| if (surfaceControl != null) { |
| surfaceControl.release(); |
| } |
| } |
| } |
| |
| /** |
| * Returns an array of {@link SurfaceControl#mNativeObject} corresponding to |
| * {@link #mExcludeLayers}. Used only in native code. |
| */ |
| private long[] getNativeExcludeLayers() { |
| if (mExcludeLayers == null || mExcludeLayers.length == 0) { |
| return new long[0]; |
| } |
| |
| long[] nativeExcludeLayers = new long[mExcludeLayers.length]; |
| for (int index = 0; index < mExcludeLayers.length; index++) { |
| nativeExcludeLayers[index] = mExcludeLayers[index].mNativeObject; |
| } |
| |
| return nativeExcludeLayers; |
| } |
| |
| /** |
| * The Builder class used to construct {@link CaptureArgs} |
| * |
| * @param <T> A builder that extends {@link CaptureArgs.Builder} |
| */ |
| public static class Builder<T extends CaptureArgs.Builder<T>> { |
| private int mPixelFormat = PixelFormat.RGBA_8888; |
| private final Rect mSourceCrop = new Rect(); |
| private float mFrameScaleX = 1; |
| private float mFrameScaleY = 1; |
| private boolean mCaptureSecureLayers; |
| private boolean mAllowProtected; |
| private long mUid = -1; |
| private boolean mGrayscale; |
| private SurfaceControl[] mExcludeLayers; |
| private boolean mHintForSeamlessTransition; |
| |
| /** |
| * 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. |
| */ |
| public T setPixelFormat(int pixelFormat) { |
| mPixelFormat = pixelFormat; |
| return getThis(); |
| } |
| |
| /** |
| * The portion of the screen to capture into the buffer. Caller may pass in |
| * 'new Rect()' or null if no cropping is desired. |
| */ |
| public T setSourceCrop(@Nullable Rect sourceCrop) { |
| if (sourceCrop == null) { |
| mSourceCrop.setEmpty(); |
| } else { |
| mSourceCrop.set(sourceCrop); |
| } |
| return getThis(); |
| } |
| |
| /** |
| * The desired scale of the returned buffer. The raw screen will be scaled up/down. |
| */ |
| public T setFrameScale(float frameScale) { |
| mFrameScaleX = frameScale; |
| mFrameScaleY = frameScale; |
| return getThis(); |
| } |
| |
| /** |
| * The desired scale of the returned buffer, allowing separate values for x and y scale. |
| * The raw screen will be scaled up/down. |
| */ |
| public T setFrameScale(float frameScaleX, float frameScaleY) { |
| mFrameScaleX = frameScaleX; |
| mFrameScaleY = frameScaleY; |
| return getThis(); |
| } |
| |
| /** |
| * Whether to allow the screenshot of secure layers. Warning: This should only be done |
| * if the content will be placed in a secure SurfaceControl. |
| * |
| * @see ScreenshotHardwareBuffer#containsSecureLayers() |
| */ |
| public T setCaptureSecureLayers(boolean captureSecureLayers) { |
| mCaptureSecureLayers = captureSecureLayers; |
| return getThis(); |
| } |
| |
| /** |
| * Whether to allow the screenshot of protected (DRM) content. Warning: The screenshot |
| * cannot be read in unprotected space. |
| * |
| * @see HardwareBuffer#USAGE_PROTECTED_CONTENT |
| */ |
| public T setAllowProtected(boolean allowProtected) { |
| mAllowProtected = allowProtected; |
| return getThis(); |
| } |
| |
| /** |
| * Set the uid of the content that should be screenshot. The code will skip any surfaces |
| * that don't belong to the specified uid. |
| */ |
| public T setUid(long uid) { |
| mUid = uid; |
| return getThis(); |
| } |
| |
| /** |
| * Set whether the screenshot should use grayscale or not. |
| */ |
| public T setGrayscale(boolean grayscale) { |
| mGrayscale = grayscale; |
| return getThis(); |
| } |
| |
| /** |
| * An array of {@link SurfaceControl} layer handles to exclude. |
| */ |
| public T setExcludeLayers(@Nullable SurfaceControl[] excludeLayers) { |
| mExcludeLayers = excludeLayers; |
| return getThis(); |
| } |
| |
| /** |
| * Set whether the screenshot will be used in a system animation. |
| * This hint is used for picking the "best" colorspace for the screenshot, in particular |
| * for mixing HDR and SDR content. |
| * E.g., hintForSeamlessTransition is false, then a colorspace suitable for file |
| * encoding, such as BT2100, may be chosen. Otherwise, then the display's color space |
| * would be chosen, with the possibility of having an extended brightness range. This |
| * is important for screenshots that are directly re-routed to a SurfaceControl in |
| * order to preserve accurate colors. |
| */ |
| public T setHintForSeamlessTransition(boolean hintForSeamlessTransition) { |
| mHintForSeamlessTransition = hintForSeamlessTransition; |
| return getThis(); |
| } |
| |
| /** |
| * Each sub class should return itself to allow the builder to chain properly |
| */ |
| T getThis() { |
| return (T) this; |
| } |
| } |
| |
| @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); |
| if (mExcludeLayers != null) { |
| dest.writeInt(mExcludeLayers.length); |
| for (SurfaceControl excludeLayer : mExcludeLayers) { |
| excludeLayer.writeToParcel(dest, flags); |
| } |
| } else { |
| dest.writeInt(0); |
| } |
| dest.writeBoolean(mHintForSeamlessTransition); |
| } |
| |
| 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. |
| * |
| * @hide |
| * @see #nativeCaptureDisplay(DisplayCaptureArgs, long) |
| */ |
| public static class DisplayCaptureArgs extends CaptureArgs { |
| private final IBinder mDisplayToken; |
| private final int mWidth; |
| private final int mHeight; |
| |
| private DisplayCaptureArgs(Builder builder) { |
| super(builder); |
| mDisplayToken = builder.mDisplayToken; |
| mWidth = builder.mWidth; |
| mHeight = builder.mHeight; |
| } |
| |
| /** |
| * The Builder class used to construct {@link DisplayCaptureArgs} |
| */ |
| public static class Builder extends CaptureArgs.Builder<Builder> { |
| private IBinder mDisplayToken; |
| private int mWidth; |
| private int mHeight; |
| |
| /** |
| * Construct a new {@link LayerCaptureArgs} with the set parameters. The builder |
| * remains valid. |
| */ |
| public DisplayCaptureArgs build() { |
| if (mDisplayToken == null) { |
| throw new IllegalStateException( |
| "Can't take screenshot with null display token"); |
| } |
| return new DisplayCaptureArgs(this); |
| } |
| |
| public Builder(IBinder displayToken) { |
| setDisplayToken(displayToken); |
| } |
| |
| /** |
| * The display to take the screenshot of. |
| */ |
| public Builder setDisplayToken(IBinder displayToken) { |
| mDisplayToken = displayToken; |
| return this; |
| } |
| |
| /** |
| * Set the desired size of the returned buffer. The raw screen will be scaled down to |
| * this size |
| * |
| * @param width The desired width of the returned buffer. Caller may pass in 0 if no |
| * scaling is desired. |
| * @param height The desired height of the returned buffer. Caller may pass in 0 if no |
| * scaling is desired. |
| */ |
| public Builder setSize(int width, int height) { |
| mWidth = width; |
| mHeight = height; |
| return this; |
| } |
| |
| @Override |
| Builder getThis() { |
| return this; |
| } |
| } |
| } |
| |
| /** |
| * The arguments class used to make layer capture requests. |
| * |
| * @hide |
| * @see #nativeCaptureLayers(LayerCaptureArgs, long) |
| */ |
| public static class LayerCaptureArgs extends CaptureArgs { |
| private final long mNativeLayer; |
| private final boolean mChildrenOnly; |
| |
| private LayerCaptureArgs(Builder builder) { |
| super(builder); |
| mChildrenOnly = builder.mChildrenOnly; |
| mNativeLayer = builder.mLayer.mNativeObject; |
| } |
| |
| /** |
| * The Builder class used to construct {@link LayerCaptureArgs} |
| */ |
| public static class Builder extends CaptureArgs.Builder<Builder> { |
| private SurfaceControl mLayer; |
| private boolean mChildrenOnly = true; |
| |
| /** |
| * Construct a new {@link LayerCaptureArgs} with the set parameters. The builder |
| * remains valid. |
| */ |
| public LayerCaptureArgs build() { |
| if (mLayer == null) { |
| throw new IllegalStateException( |
| "Can't take screenshot with null layer"); |
| } |
| 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); |
| setExcludeLayers(args.mExcludeLayers); |
| setHintForSeamlessTransition(args.mHintForSeamlessTransition); |
| } |
| |
| public Builder(SurfaceControl layer) { |
| setLayer(layer); |
| } |
| |
| /** |
| * The root layer to capture. |
| */ |
| public Builder setLayer(SurfaceControl layer) { |
| mLayer = layer; |
| return this; |
| } |
| |
| /** |
| * Whether to include the layer itself in the screenshot or just the children and their |
| * descendants. |
| */ |
| public Builder setChildrenOnly(boolean childrenOnly) { |
| mChildrenOnly = childrenOnly; |
| return this; |
| } |
| |
| @Override |
| 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)} |
| * |
| * This listener can only be used for a single call to capture content call. |
| */ |
| public static class ScreenCaptureListener implements Parcelable { |
| 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(ObjIntConsumer<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 method 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. |
| * |
| * @return a {@link SynchronousScreenCaptureListener} that should be used for capture |
| * calls into SurfaceFlinger. |
| */ |
| public static SynchronousScreenCaptureListener createSyncCaptureListener() { |
| ScreenshotHardwareBuffer[] bufferRef = new ScreenshotHardwareBuffer[1]; |
| CountDownLatch latch = new CountDownLatch(1); |
| ObjIntConsumer<ScreenshotHardwareBuffer> consumer = (buffer, status) -> { |
| if (status != 0) { |
| bufferRef[0] = null; |
| Log.e(TAG, "Failed to generate screen capture. Error code: " + status); |
| } else { |
| bufferRef[0] = buffer; |
| } |
| latch.countDown(); |
| }; |
| |
| return new SynchronousScreenCaptureListener(consumer) { |
| // In order to avoid requiring two GC cycles to clean up the consumer and the buffer |
| // it references, the underlying JNI listener holds a weak reference to the consumer. |
| // This property exists to ensure the consumer stays alive during the listener's |
| // lifetime. |
| private ObjIntConsumer<ScreenshotHardwareBuffer> mConsumer = consumer; |
| |
| @Override |
| public ScreenshotHardwareBuffer getBuffer() { |
| try { |
| if (!latch.await(SCREENSHOT_WAIT_TIME_S, TimeUnit.SECONDS)) { |
| Log.e(TAG, "Timed out waiting for screenshot results"); |
| return null; |
| } |
| return bufferRef[0]; |
| } catch (Exception e) { |
| Log.e(TAG, "Failed to wait for screen capture result", e); |
| return null; |
| } |
| } |
| }; |
| } |
| |
| /** |
| * Helper class to synchronously get the {@link ScreenshotHardwareBuffer} when calling |
| * {@link #captureLayers(LayerCaptureArgs, ScreenCaptureListener)} or |
| * {@link #captureDisplay(DisplayCaptureArgs, ScreenCaptureListener)} |
| */ |
| public abstract static class SynchronousScreenCaptureListener extends ScreenCaptureListener { |
| SynchronousScreenCaptureListener(ObjIntConsumer<ScreenshotHardwareBuffer> consumer) { |
| super(consumer); |
| } |
| |
| /** |
| * Get the {@link ScreenshotHardwareBuffer} synchronously. This can be null if the |
| * screenshot failed or if there was no callback in {@link #SCREENSHOT_WAIT_TIME_S} seconds. |
| */ |
| @Nullable |
| public abstract ScreenshotHardwareBuffer getBuffer(); |
| } |
| } |