Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 26006c72 authored by Miranda Kephart's avatar Miranda Kephart Committed by Automerger Merge Worker
Browse files

Merge "Make ScreenshotRequest handle hardware bitmap conversion" into tm-qpr-dev am: a5e45d31

parents 61fc8ede a5e45d31
Loading
Loading
Loading
Loading
+32 −265
Original line number Diff line number Diff line
package com.android.internal.util;

import static android.content.Intent.ACTION_USER_SWITCHED;
import static android.view.WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE;
import static android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN;

import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -11,29 +11,18 @@ import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.ServiceConnection;
import android.graphics.Bitmap;
import android.graphics.ColorSpace;
import android.graphics.Insets;
import android.graphics.ParcelableColorSpace;
import android.graphics.Rect;
import android.hardware.HardwareBuffer;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.Messenger;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.RemoteException;
import android.os.UserHandle;
import android.util.Log;
import android.view.WindowManager.ScreenshotSource;
import android.view.WindowManager.ScreenshotType;

import com.android.internal.annotations.VisibleForTesting;

import java.util.Objects;
import java.util.function.Consumer;

public class ScreenshotHelper {
@@ -41,212 +30,6 @@ public class ScreenshotHelper {
    public static final int SCREENSHOT_MSG_URI = 1;
    public static final int SCREENSHOT_MSG_PROCESS_COMPLETE = 2;

    /**
     * Describes a screenshot request.
     */
    public static class ScreenshotRequest implements Parcelable {
        @ScreenshotType
        private final int mType;

        @ScreenshotSource
        private final int mSource;

        private final Bundle mBitmapBundle;
        private final Rect mBoundsInScreen;
        private final Insets mInsets;
        private final int mTaskId;
        private final int mUserId;
        private final ComponentName mTopComponent;


        public ScreenshotRequest(@ScreenshotType int type, @ScreenshotSource int source) {
            this(type, source, /* topComponent */ null);
        }

        public ScreenshotRequest(@ScreenshotType int type, @ScreenshotSource int source,
                ComponentName topComponent) {
            this(type,
                source,
                /* bitmapBundle*/ null,
                /* boundsInScreen */ null,
                /* insets */ null,
                /* taskId */ -1,
                /* userId */ -1,
                topComponent);
        }

        public ScreenshotRequest(@ScreenshotType int type, @ScreenshotSource int source,
                Bundle bitmapBundle, Rect boundsInScreen, Insets insets, int taskId, int userId,
                ComponentName topComponent) {
            mType = type;
            mSource = source;
            mBitmapBundle = bitmapBundle;
            mBoundsInScreen = boundsInScreen;
            mInsets = insets;
            mTaskId = taskId;
            mUserId = userId;
            mTopComponent = topComponent;
        }

        ScreenshotRequest(Parcel in) {
            mType = in.readInt();
            mSource = in.readInt();
            if (in.readInt() == 1) {
                mBitmapBundle = in.readBundle(getClass().getClassLoader());
                mBoundsInScreen = in.readParcelable(Rect.class.getClassLoader(), Rect.class);
                mInsets = in.readParcelable(Insets.class.getClassLoader(), Insets.class);
                mTaskId = in.readInt();
                mUserId = in.readInt();
                mTopComponent = in.readParcelable(ComponentName.class.getClassLoader(),
                        ComponentName.class);
            } else {
                mBitmapBundle = null;
                mBoundsInScreen = null;
                mInsets = null;
                mTaskId = -1;
                mUserId = -1;
                mTopComponent = null;
            }
        }

        @ScreenshotType
        public int getType() {
            return mType;
        }

        @ScreenshotSource
        public int getSource() {
            return mSource;
        }

        public Bundle getBitmapBundle() {
            return mBitmapBundle;
        }

        public Rect getBoundsInScreen() {
            return mBoundsInScreen;
        }

        public Insets getInsets() {
            return mInsets;
        }

        public int getTaskId() {
            return mTaskId;
        }

        public int getUserId() {
            return mUserId;
        }

        public ComponentName getTopComponent() {
            return mTopComponent;
        }

        @Override
        public int describeContents() {
            return 0;
        }

        @Override
        public void writeToParcel(Parcel dest, int flags) {
            dest.writeInt(mType);
            dest.writeInt(mSource);
            if (mBitmapBundle == null) {
                dest.writeInt(0);
            } else {
                dest.writeInt(1);
                dest.writeBundle(mBitmapBundle);
                dest.writeParcelable(mBoundsInScreen, 0);
                dest.writeParcelable(mInsets, 0);
                dest.writeInt(mTaskId);
                dest.writeInt(mUserId);
                dest.writeParcelable(mTopComponent, 0);
            }
        }

        @NonNull
        public static final Parcelable.Creator<ScreenshotRequest> CREATOR =
                new Parcelable.Creator<ScreenshotRequest>() {

                    @Override
                    public ScreenshotRequest createFromParcel(Parcel source) {
                        return new ScreenshotRequest(source);
                    }

                    @Override
                    public ScreenshotRequest[] newArray(int size) {
                        return new ScreenshotRequest[size];
                    }
                };
    }

    /**
     * Bundler used to convert between a hardware bitmap and a bundle without copying the internal
     * content. This is expected to be used together with {@link #provideScreenshot} to handle a
     * hardware bitmap as a screenshot.
     */
    public static final class HardwareBitmapBundler {
        private static final String KEY_BUFFER = "bitmap_util_buffer";
        private static final String KEY_COLOR_SPACE = "bitmap_util_color_space";

        private HardwareBitmapBundler() {
        }

        /**
         * Creates a Bundle that represents the given Bitmap.
         * <p>The Bundle will contain a wrapped version of the Bitmaps HardwareBuffer, so will avoid
         * copies when passing across processes, only pass to processes you trust.
         *
         * <p>Returns a new Bundle rather than modifying an exiting one to avoid key collisions, the
         * returned Bundle should be treated as a standalone object.
         *
         * @param bitmap to convert to bundle
         * @return a Bundle representing the bitmap, should only be parsed by
         * {@link #bundleToHardwareBitmap(Bundle)}
         */
        public static Bundle hardwareBitmapToBundle(Bitmap bitmap) {
            if (bitmap.getConfig() != Bitmap.Config.HARDWARE) {
                throw new IllegalArgumentException(
                        "Passed bitmap must have hardware config, found: " + bitmap.getConfig());
            }

            // Bitmap assumes SRGB for null color space
            ParcelableColorSpace colorSpace =
                    bitmap.getColorSpace() == null
                            ? new ParcelableColorSpace(ColorSpace.get(ColorSpace.Named.SRGB))
                            : new ParcelableColorSpace(bitmap.getColorSpace());

            Bundle bundle = new Bundle();
            bundle.putParcelable(KEY_BUFFER, bitmap.getHardwareBuffer());
            bundle.putParcelable(KEY_COLOR_SPACE, colorSpace);

            return bundle;
        }

        /**
         * Extracts the Bitmap added to a Bundle with {@link #hardwareBitmapToBundle(Bitmap)} .}
         *
         * <p>This Bitmap contains the HardwareBuffer from the original caller, be careful passing
         * this Bitmap on to any other source.
         *
         * @param bundle containing the bitmap
         * @return a hardware Bitmap
         */
        public static Bitmap bundleToHardwareBitmap(Bundle bundle) {
            if (!bundle.containsKey(KEY_BUFFER) || !bundle.containsKey(KEY_COLOR_SPACE)) {
                throw new IllegalArgumentException("Bundle does not contain a hardware bitmap");
            }

            HardwareBuffer buffer = bundle.getParcelable(KEY_BUFFER, HardwareBuffer.class);
            ParcelableColorSpace colorSpace = bundle.getParcelable(KEY_COLOR_SPACE,
                    ParcelableColorSpace.class);

            return Bitmap.wrapHardwareBuffer(Objects.requireNonNull(buffer),
                    colorSpace.getColorSpace());
        }
    }

    private static final String TAG = "ScreenshotHelper";

    // Time until we give up on the screenshot & show an error instead.
@@ -277,68 +60,53 @@ public class ScreenshotHelper {
    /**
     * Request a screenshot be taken.
     * <p>
     * Added to support reducing unit test duration; the method variant without a timeout argument
     * is recommended for general use.
     * Convenience method for taking a full screenshot with provided source.
     *
     * @param type The type of screenshot, defined by {@link ScreenshotType}
     * @param source The source of the screenshot request, defined by {@link ScreenshotSource}
     * @param source             source of the screenshot request, defined by {@link
     *                           ScreenshotSource}
     * @param handler            used to process messages received from the screenshot service
     * @param completionConsumer receives the URI of the captured screenshot, once saved or
     *                           null if no screenshot was saved
     */
    public void takeScreenshot(@ScreenshotType int type, @ScreenshotSource int source,
            @NonNull Handler handler, @Nullable Consumer<Uri> completionConsumer) {
        ScreenshotRequest screenshotRequest = new ScreenshotRequest(type, source);
        takeScreenshot(handler, screenshotRequest, SCREENSHOT_TIMEOUT_MS,
                completionConsumer);
    public void takeScreenshot(@ScreenshotSource int source, @NonNull Handler handler,
            @Nullable Consumer<Uri> completionConsumer) {
        ScreenshotRequest request =
                new ScreenshotRequest.Builder(TAKE_SCREENSHOT_FULLSCREEN, source).build();
        takeScreenshot(request, handler, completionConsumer);
    }

    /**
     * Request a screenshot be taken.
     * <p>
     * Added to support reducing unit test duration; the method variant without a timeout argument
     * is recommended for general use.
     *
     * @param type The type of screenshot, defined by {@link ScreenshotType}
     * @param source The source of the screenshot request, defined by {@link ScreenshotSource}
     * @param request            description of the screenshot request, either for taking a
     *                           screenshot or
     *                           providing a bitmap
     * @param handler            used to process messages received from the screenshot service
     * @param timeoutMs time limit for processing, intended only for testing
     * @param completionConsumer receives the URI of the captured screenshot, once saved or
     *                           null if no screenshot was saved
     */
    @VisibleForTesting
    public void takeScreenshot(@ScreenshotType int type, @ScreenshotSource int source,
            @NonNull Handler handler, long timeoutMs, @Nullable Consumer<Uri> completionConsumer) {
        ScreenshotRequest screenshotRequest = new ScreenshotRequest(type, source);
        takeScreenshot(handler, screenshotRequest, timeoutMs, completionConsumer);
    public void takeScreenshot(ScreenshotRequest request, @NonNull Handler handler,
            @Nullable Consumer<Uri> completionConsumer) {
        takeScreenshotInternal(request, handler, completionConsumer, SCREENSHOT_TIMEOUT_MS);
    }

    /**
     * Request that provided image be handled as if it was a screenshot.
     * Request a screenshot be taken.
     * <p>
     * Added to support reducing unit test duration; the method variant without a timeout argument
     * is recommended for general use.
     *
     * @param screenshotBundle Bundle containing the buffer and color space of the screenshot.
     * @param boundsInScreen The bounds in screen coordinates that the bitmap originated from.
     * @param insets The insets that the image was shown with, inside the screen bounds.
     * @param taskId The taskId of the task that the screen shot was taken of.
     * @param userId The userId of user running the task provided in taskId.
     * @param topComponent The component name of the top component running in the task.
     * @param source The source of the screenshot request, defined by {@link ScreenshotSource}
     * @param handler A handler used in case the screenshot times out
     * @param request            description of the screenshot request, either for taking a
     *                           screenshot or providing a bitmap
     * @param handler            used to process messages received from the screenshot service
     * @param timeoutMs          time limit for processing, intended only for testing
     * @param completionConsumer receives the URI of the captured screenshot, once saved or
     *                           null if no screenshot was saved
     */
    public void provideScreenshot(@NonNull Bundle screenshotBundle, @NonNull Rect boundsInScreen,
            @NonNull Insets insets, int taskId, int userId, ComponentName topComponent,
            @ScreenshotSource int source, @NonNull Handler handler,
            @Nullable Consumer<Uri> completionConsumer) {
        ScreenshotRequest screenshotRequest = new ScreenshotRequest(TAKE_SCREENSHOT_PROVIDED_IMAGE,
                source, screenshotBundle, boundsInScreen, insets, taskId, userId, topComponent);
        takeScreenshot(handler, screenshotRequest, SCREENSHOT_TIMEOUT_MS, completionConsumer);
    }

    private void takeScreenshot(@NonNull Handler handler,
            ScreenshotRequest screenshotRequest, long timeoutMs,
            @Nullable Consumer<Uri> completionConsumer) {
    @VisibleForTesting
    public void takeScreenshotInternal(ScreenshotRequest request, @NonNull Handler handler,
            @Nullable Consumer<Uri> completionConsumer, long timeoutMs) {
        synchronized (mScreenshotLock) {

            final Runnable mScreenshotTimeout = () -> {
@@ -354,7 +122,7 @@ public class ScreenshotHelper {
                }
            };

            Message msg = Message.obtain(null, 0, screenshotRequest);
            Message msg = Message.obtain(null, 0, request);

            Handler h = new Handler(handler.getLooper()) {
                @Override
@@ -471,5 +239,4 @@ public class ScreenshotHelper {
                Intent.FLAG_RECEIVER_FOREGROUND);
        mContext.sendBroadcastAsUser(errorIntent, UserHandle.CURRENT);
    }

}
+19 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2023 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.internal.util;

parcelable ScreenshotRequest;
 No newline at end of file
+332 −0

File added.

Preview size limit exceeded, changes collapsed.

+2 −1
Original line number Diff line number Diff line
@@ -21,6 +21,7 @@ android_test {
        "androidx.test.ext.junit",
        "mockito-target-minus-junit4",
        "platform-test-annotations",
        "testng",
    ],

    libs: [
+31 −10
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@
package com.android.internal.util;

import static android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN;
import static android.view.WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE;

import static junit.framework.Assert.assertNull;
import static junit.framework.Assert.fail;
@@ -31,9 +32,11 @@ import static org.mockito.Mockito.mock;
import android.content.ComponentName;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.ColorSpace;
import android.graphics.Insets;
import android.graphics.Rect;
import android.os.Bundle;
import android.hardware.HardwareBuffer;
import android.os.Handler;
import android.os.Looper;
import android.view.WindowManager;
@@ -79,30 +82,48 @@ public final class ScreenshotHelperTest {

    @Test
    public void testFullscreenScreenshot() {
        mScreenshotHelper.takeScreenshot(TAKE_SCREENSHOT_FULLSCREEN,
        mScreenshotHelper.takeScreenshot(
                WindowManager.ScreenshotSource.SCREENSHOT_OTHER, mHandler, null);
    }

    @Test
    public void testFullscreenScreenshotRequest() {
        ScreenshotRequest request = new ScreenshotRequest.Builder(
                TAKE_SCREENSHOT_FULLSCREEN, WindowManager.ScreenshotSource.SCREENSHOT_OTHER)
                .build();
        mScreenshotHelper.takeScreenshot(request, mHandler, null);
    }

    @Test
    public void testProvidedImageScreenshot() {
        mScreenshotHelper.provideScreenshot(
                new Bundle(), new Rect(), Insets.of(0, 0, 0, 0), 1, 1, new ComponentName("", ""),
                WindowManager.ScreenshotSource.SCREENSHOT_OTHER, mHandler, null);
        HardwareBuffer buffer = HardwareBuffer.create(
                10, 10, HardwareBuffer.RGBA_8888, 1, HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE);
        Bitmap bitmap = Bitmap.wrapHardwareBuffer(buffer, ColorSpace.get(ColorSpace.Named.SRGB));
        ScreenshotRequest request = new ScreenshotRequest.Builder(
                TAKE_SCREENSHOT_PROVIDED_IMAGE, WindowManager.ScreenshotSource.SCREENSHOT_OTHER)
                .setTopComponent(new ComponentName("", ""))
                .setTaskId(1)
                .setUserId(1)
                .setBitmap(bitmap)
                .setBoundsOnScreen(new Rect())
                .setInsets(Insets.NONE)
                .build();
        mScreenshotHelper.takeScreenshot(request, mHandler, null);
    }

    @Test
    public void testScreenshotTimesOut() {
        long timeoutMs = 10;
        ScreenshotRequest request = new ScreenshotRequest.Builder(
                TAKE_SCREENSHOT_FULLSCREEN, WindowManager.ScreenshotSource.SCREENSHOT_OTHER)
                .build();

        CountDownLatch lock = new CountDownLatch(1);
        mScreenshotHelper.takeScreenshot(TAKE_SCREENSHOT_FULLSCREEN,
                WindowManager.ScreenshotSource.SCREENSHOT_OTHER,
                mHandler,
                timeoutMs,
        mScreenshotHelper.takeScreenshotInternal(request, mHandler,
                uri -> {
                    assertNull(uri);
                    lock.countDown();
                });
                }, timeoutMs);

        try {
            // Add tolerance for delay to prevent flakes.
Loading