Loading core/java/com/android/internal/util/ScreenshotHelper.java +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; Loading @@ -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 { Loading @@ -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. Loading Loading @@ -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 = () -> { Loading @@ -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 Loading Loading @@ -471,5 +239,4 @@ public class ScreenshotHelper { Intent.FLAG_RECEIVER_FOREGROUND); mContext.sendBroadcastAsUser(errorIntent, UserHandle.CURRENT); } } core/java/com/android/internal/util/ScreenshotRequest.aidl 0 → 100644 +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 core/java/com/android/internal/util/ScreenshotRequest.java 0 → 100644 +332 −0 File added.Preview size limit exceeded, changes collapsed. Show changes core/tests/screenshothelpertests/Android.bp +2 −1 Original line number Diff line number Diff line Loading @@ -21,6 +21,7 @@ android_test { "androidx.test.ext.junit", "mockito-target-minus-junit4", "platform-test-annotations", "testng", ], libs: [ Loading core/tests/screenshothelpertests/src/com/android/internal/util/ScreenshotHelperTest.java +31 −10 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading Loading @@ -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 Loading
core/java/com/android/internal/util/ScreenshotHelper.java +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; Loading @@ -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 { Loading @@ -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. Loading Loading @@ -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 = () -> { Loading @@ -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 Loading Loading @@ -471,5 +239,4 @@ public class ScreenshotHelper { Intent.FLAG_RECEIVER_FOREGROUND); mContext.sendBroadcastAsUser(errorIntent, UserHandle.CURRENT); } }
core/java/com/android/internal/util/ScreenshotRequest.aidl 0 → 100644 +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
core/java/com/android/internal/util/ScreenshotRequest.java 0 → 100644 +332 −0 File added.Preview size limit exceeded, changes collapsed. Show changes
core/tests/screenshothelpertests/Android.bp +2 −1 Original line number Diff line number Diff line Loading @@ -21,6 +21,7 @@ android_test { "androidx.test.ext.junit", "mockito-target-minus-junit4", "platform-test-annotations", "testng", ], libs: [ Loading
core/tests/screenshothelpertests/src/com/android/internal/util/ScreenshotHelperTest.java +31 −10 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading Loading @@ -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