blob: 889edb3502c4659ca13e8483fc6ce462b9994ccd [file] [log] [blame]
John Recke94cbc72016-04-25 13:03:44 -07001/*
2 * Copyright (C) 2016 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.view;
18
19import android.annotation.IntDef;
20import android.annotation.NonNull;
John Reck95801462016-09-01 09:44:09 -070021import android.annotation.Nullable;
John Recke94cbc72016-04-25 13:03:44 -070022import android.graphics.Bitmap;
John Reck4d73cb12022-07-27 10:32:52 -040023import android.graphics.HardwareRenderer;
John Reck95801462016-09-01 09:44:09 -070024import android.graphics.Rect;
John Recke94cbc72016-04-25 13:03:44 -070025import android.os.Handler;
John Reck95801462016-09-01 09:44:09 -070026import android.view.ViewTreeObserver.OnDrawListener;
John Recke94cbc72016-04-25 13:03:44 -070027
28import java.lang.annotation.Retention;
29import java.lang.annotation.RetentionPolicy;
John Reck4d73cb12022-07-27 10:32:52 -040030import java.util.concurrent.Executor;
31import java.util.function.Consumer;
John Recke94cbc72016-04-25 13:03:44 -070032
33/**
34 * Provides a mechanisms to issue pixel copy requests to allow for copy
35 * operations from {@link Surface} to {@link Bitmap}
36 */
37public final class PixelCopy {
38
39 /** @hide */
40 @Retention(RetentionPolicy.SOURCE)
41 @IntDef({SUCCESS, ERROR_UNKNOWN, ERROR_TIMEOUT, ERROR_SOURCE_NO_DATA,
42 ERROR_SOURCE_INVALID, ERROR_DESTINATION_INVALID})
43 public @interface CopyResultStatus {}
44
45 /** The pixel copy request succeeded */
46 public static final int SUCCESS = 0;
47
48 /** The pixel copy request failed with an unknown error. */
49 public static final int ERROR_UNKNOWN = 1;
50
51 /**
52 * A timeout occurred while trying to acquire a buffer from the source to
53 * copy from.
54 */
55 public static final int ERROR_TIMEOUT = 2;
56
57 /**
58 * The source has nothing to copy from. When the source is a {@link Surface}
59 * this means that no buffers have been queued yet. Wait for the source
60 * to produce a frame and try again.
61 */
62 public static final int ERROR_SOURCE_NO_DATA = 3;
63
64 /**
65 * It is not possible to copy from the source. This can happen if the source
66 * is hardware-protected or destroyed.
67 */
68 public static final int ERROR_SOURCE_INVALID = 4;
69
70 /**
71 * The destination isn't a valid copy target. If the destination is a bitmap
72 * this can occur if the bitmap is too large for the hardware to copy to.
73 * It can also occur if the destination has been destroyed.
74 */
75 public static final int ERROR_DESTINATION_INVALID = 5;
76
77 /**
78 * Listener for observing the completion of a PixelCopy request.
79 */
80 public interface OnPixelCopyFinishedListener {
81 /**
82 * Callback for when a pixel copy request has completed. This will be called
83 * regardless of whether the copy succeeded or failed.
84 *
85 * @param copyResult Contains the resulting status of the copy request.
86 * This will either be {@link PixelCopy#SUCCESS} or one of the
87 * <code>PixelCopy.ERROR_*</code> values.
88 */
89 void onPixelCopyFinished(@CopyResultStatus int copyResult);
90 }
91
92 /**
93 * Requests for the display content of a {@link SurfaceView} to be copied
94 * into a provided {@link Bitmap}.
95 *
96 * The contents of the source will be scaled to fit exactly inside the bitmap.
97 * The pixel format of the source buffer will be converted, as part of the copy,
98 * to fit the the bitmap's {@link Bitmap.Config}. The most recently queued buffer
99 * in the SurfaceView's Surface will be used as the source of the copy.
100 *
101 * @param source The source from which to copy
102 * @param dest The destination of the copy. The source will be scaled to
103 * match the width, height, and format of this bitmap.
104 * @param listener Callback for when the pixel copy request completes
105 * @param listenerThread The callback will be invoked on this Handler when
106 * the copy is finished.
107 */
108 public static void request(@NonNull SurfaceView source, @NonNull Bitmap dest,
109 @NonNull OnPixelCopyFinishedListener listener, @NonNull Handler listenerThread) {
110 request(source.getHolder().getSurface(), dest, listener, listenerThread);
111 }
112
113 /**
John Reck95801462016-09-01 09:44:09 -0700114 * Requests for the display content of a {@link SurfaceView} to be copied
115 * into a provided {@link Bitmap}.
116 *
117 * The contents of the source will be scaled to fit exactly inside the bitmap.
118 * The pixel format of the source buffer will be converted, as part of the copy,
119 * to fit the the bitmap's {@link Bitmap.Config}. The most recently queued buffer
120 * in the SurfaceView's Surface will be used as the source of the copy.
121 *
122 * @param source The source from which to copy
123 * @param srcRect The area of the source to copy from. If this is null
124 * the copy area will be the entire surface. The rect will be clamped to
125 * the bounds of the Surface.
126 * @param dest The destination of the copy. The source will be scaled to
127 * match the width, height, and format of this bitmap.
128 * @param listener Callback for when the pixel copy request completes
129 * @param listenerThread The callback will be invoked on this Handler when
130 * the copy is finished.
131 */
132 public static void request(@NonNull SurfaceView source, @Nullable Rect srcRect,
133 @NonNull Bitmap dest, @NonNull OnPixelCopyFinishedListener listener,
134 @NonNull Handler listenerThread) {
135 request(source.getHolder().getSurface(), srcRect,
136 dest, listener, listenerThread);
137 }
138
139 /**
John Recke94cbc72016-04-25 13:03:44 -0700140 * Requests a copy of the pixels from a {@link Surface} to be copied into
141 * a provided {@link Bitmap}.
142 *
143 * The contents of the source will be scaled to fit exactly inside the bitmap.
144 * The pixel format of the source buffer will be converted, as part of the copy,
145 * to fit the the bitmap's {@link Bitmap.Config}. The most recently queued buffer
146 * in the Surface will be used as the source of the copy.
147 *
148 * @param source The source from which to copy
149 * @param dest The destination of the copy. The source will be scaled to
150 * match the width, height, and format of this bitmap.
151 * @param listener Callback for when the pixel copy request completes
152 * @param listenerThread The callback will be invoked on this Handler when
153 * the copy is finished.
154 */
155 public static void request(@NonNull Surface source, @NonNull Bitmap dest,
156 @NonNull OnPixelCopyFinishedListener listener, @NonNull Handler listenerThread) {
John Reck95801462016-09-01 09:44:09 -0700157 request(source, null, dest, listener, listenerThread);
158 }
159
160 /**
161 * Requests a copy of the pixels at the provided {@link Rect} from
162 * a {@link Surface} to be copied into a provided {@link Bitmap}.
163 *
164 * The contents of the source rect will be scaled to fit exactly inside the bitmap.
165 * The pixel format of the source buffer will be converted, as part of the copy,
166 * to fit the the bitmap's {@link Bitmap.Config}. The most recently queued buffer
167 * in the Surface will be used as the source of the copy.
168 *
169 * @param source The source from which to copy
170 * @param srcRect The area of the source to copy from. If this is null
171 * the copy area will be the entire surface. The rect will be clamped to
172 * the bounds of the Surface.
173 * @param dest The destination of the copy. The source will be scaled to
174 * match the width, height, and format of this bitmap.
175 * @param listener Callback for when the pixel copy request completes
176 * @param listenerThread The callback will be invoked on this Handler when
177 * the copy is finished.
178 */
179 public static void request(@NonNull Surface source, @Nullable Rect srcRect,
180 @NonNull Bitmap dest, @NonNull OnPixelCopyFinishedListener listener,
181 @NonNull Handler listenerThread) {
John Recke94cbc72016-04-25 13:03:44 -0700182 validateBitmapDest(dest);
John Reckf3a51d62016-04-27 15:23:51 -0700183 if (!source.isValid()) {
184 throw new IllegalArgumentException("Surface isn't valid, source.isValid() == false");
185 }
John Reck95801462016-09-01 09:44:09 -0700186 if (srcRect != null && srcRect.isEmpty()) {
187 throw new IllegalArgumentException("sourceRect is empty");
188 }
John Reck4d73cb12022-07-27 10:32:52 -0400189 HardwareRenderer.copySurfaceInto(source, new HardwareRenderer.CopyRequest(srcRect, dest) {
John Recke94cbc72016-04-25 13:03:44 -0700190 @Override
John Reck4d73cb12022-07-27 10:32:52 -0400191 public void onCopyFinished(int result) {
192 listenerThread.post(() -> listener.onPixelCopyFinished(result));
John Recke94cbc72016-04-25 13:03:44 -0700193 }
194 });
195 }
196
John Reck95801462016-09-01 09:44:09 -0700197 /**
198 * Requests a copy of the pixels from a {@link Window} to be copied into
199 * a provided {@link Bitmap}.
200 *
201 * The contents of the source will be scaled to fit exactly inside the bitmap.
202 * The pixel format of the source buffer will be converted, as part of the copy,
203 * to fit the the bitmap's {@link Bitmap.Config}. The most recently queued buffer
204 * in the Window's Surface will be used as the source of the copy.
205 *
206 * Note: This is limited to being able to copy from Window's with a non-null
207 * DecorView. If {@link Window#peekDecorView()} is null this throws an
208 * {@link IllegalArgumentException}. It will similarly throw an exception
209 * if the DecorView has not yet acquired a backing surface. It is recommended
210 * that {@link OnDrawListener} is used to ensure that at least one draw
211 * has happened before trying to copy from the window, otherwise either
212 * an {@link IllegalArgumentException} will be thrown or an error will
213 * be returned to the {@link OnPixelCopyFinishedListener}.
214 *
215 * @param source The source from which to copy
216 * @param dest The destination of the copy. The source will be scaled to
217 * match the width, height, and format of this bitmap.
218 * @param listener Callback for when the pixel copy request completes
219 * @param listenerThread The callback will be invoked on this Handler when
220 * the copy is finished.
221 */
222 public static void request(@NonNull Window source, @NonNull Bitmap dest,
223 @NonNull OnPixelCopyFinishedListener listener, @NonNull Handler listenerThread) {
224 request(source, null, dest, listener, listenerThread);
225 }
226
227 /**
228 * Requests a copy of the pixels at the provided {@link Rect} from
229 * a {@link Window} to be copied into a provided {@link Bitmap}.
230 *
231 * The contents of the source rect will be scaled to fit exactly inside the bitmap.
232 * The pixel format of the source buffer will be converted, as part of the copy,
233 * to fit the the bitmap's {@link Bitmap.Config}. The most recently queued buffer
234 * in the Window's Surface will be used as the source of the copy.
235 *
236 * Note: This is limited to being able to copy from Window's with a non-null
237 * DecorView. If {@link Window#peekDecorView()} is null this throws an
238 * {@link IllegalArgumentException}. It will similarly throw an exception
239 * if the DecorView has not yet acquired a backing surface. It is recommended
240 * that {@link OnDrawListener} is used to ensure that at least one draw
241 * has happened before trying to copy from the window, otherwise either
242 * an {@link IllegalArgumentException} will be thrown or an error will
243 * be returned to the {@link OnPixelCopyFinishedListener}.
244 *
245 * @param source The source from which to copy
246 * @param srcRect The area of the source to copy from. If this is null
247 * the copy area will be the entire surface. The rect will be clamped to
248 * the bounds of the Surface.
249 * @param dest The destination of the copy. The source will be scaled to
250 * match the width, height, and format of this bitmap.
251 * @param listener Callback for when the pixel copy request completes
252 * @param listenerThread The callback will be invoked on this Handler when
253 * the copy is finished.
254 */
255 public static void request(@NonNull Window source, @Nullable Rect srcRect,
256 @NonNull Bitmap dest, @NonNull OnPixelCopyFinishedListener listener,
257 @NonNull Handler listenerThread) {
258 validateBitmapDest(dest);
John Reck4d73cb12022-07-27 10:32:52 -0400259 final Rect insets = new Rect();
260 final Surface surface = sourceForWindow(source, insets);
261 request(surface, adjustSourceRectForInsets(insets, srcRect), dest, listener,
262 listenerThread);
John Reck95801462016-09-01 09:44:09 -0700263 }
264
John Recke94cbc72016-04-25 13:03:44 -0700265 private static void validateBitmapDest(Bitmap bitmap) {
266 // TODO: Pre-check max texture dimens if we can
267 if (bitmap == null) {
268 throw new IllegalArgumentException("Bitmap cannot be null");
269 }
270 if (bitmap.isRecycled()) {
271 throw new IllegalArgumentException("Bitmap is recycled");
272 }
273 if (!bitmap.isMutable()) {
274 throw new IllegalArgumentException("Bitmap is immutable");
275 }
276 }
277
John Reck4d73cb12022-07-27 10:32:52 -0400278 private static Surface sourceForWindow(Window source, Rect outInsets) {
279 if (source == null) {
280 throw new IllegalArgumentException("source is null");
281 }
282 if (source.peekDecorView() == null) {
283 throw new IllegalArgumentException(
284 "Only able to copy windows with decor views");
285 }
286 Surface surface = null;
287 final ViewRootImpl root = source.peekDecorView().getViewRootImpl();
288 if (root != null) {
289 surface = root.mSurface;
290 final Rect surfaceInsets = root.mWindowAttributes.surfaceInsets;
291 outInsets.set(surfaceInsets.left, surfaceInsets.top,
292 root.mWidth + surfaceInsets.left, root.mHeight + surfaceInsets.top);
293 }
294 if (surface == null || !surface.isValid()) {
295 throw new IllegalArgumentException(
296 "Window doesn't have a backing surface!");
297 }
298 return surface;
299 }
300
301 private static Rect adjustSourceRectForInsets(Rect insets, Rect srcRect) {
302 if (srcRect == null) {
303 return insets;
304 }
305 if (insets != null) {
306 srcRect.offset(insets.left, insets.top);
307 }
308 return srcRect;
309 }
310
311 /**
312 * Contains the result of a PixelCopy request
313 */
John Reck03256dd2022-10-27 15:32:51 -0400314 public static final class Result {
John Reck4d73cb12022-07-27 10:32:52 -0400315 private int mStatus;
316 private Bitmap mBitmap;
317
John Reck03256dd2022-10-27 15:32:51 -0400318 private Result(@CopyResultStatus int status, Bitmap bitmap) {
John Reck4d73cb12022-07-27 10:32:52 -0400319 mStatus = status;
320 mBitmap = bitmap;
321 }
322
323 /**
324 * Returns the {@link CopyResultStatus} of the copy request.
325 */
326 public @CopyResultStatus int getStatus() {
327 return mStatus;
328 }
329
330 private void validateStatus() {
331 if (mStatus != SUCCESS) {
332 throw new IllegalStateException("Copy request didn't succeed, status = " + mStatus);
333 }
334 }
335
336 /**
337 * If the PixelCopy {@link Request} was given a destination bitmap with
John Reck03256dd2022-10-27 15:32:51 -0400338 * {@link Request.Builder#setDestinationBitmap(Bitmap)} then the returned bitmap will be
339 * the same as the one given. If no destination bitmap was provided, then this
John Reck4d73cb12022-07-27 10:32:52 -0400340 * will contain the automatically allocated Bitmap to hold the result.
341 *
342 * @return the Bitmap the copy request was stored in.
343 * @throws IllegalStateException if {@link #getStatus()} is not SUCCESS
344 */
345 public @NonNull Bitmap getBitmap() {
346 validateStatus();
347 return mBitmap;
348 }
349 }
350
351 /**
John Reck03256dd2022-10-27 15:32:51 -0400352 * Represents a PixelCopy request.
353 *
354 * To create a copy request, use either of the PixelCopy.Request.ofWindow or
355 * PixelCopy.Request.ofSurface factories to create a {@link Request.Builder} for the
356 * given source content. After setting any optional parameters, such as
357 * {@link Builder#setSourceRect(Rect)}, build the request with {@link Builder#build()} and
358 * then execute it with {@link PixelCopy#request(Request)}
John Reck4d73cb12022-07-27 10:32:52 -0400359 */
360 public static final class Request {
John Reck03256dd2022-10-27 15:32:51 -0400361 private final Surface mSource;
362 private final Consumer<Result> mListener;
363 private final Executor mListenerThread;
364 private final Rect mSourceInsets;
365 private Rect mSrcRect;
366 private Bitmap mDest;
367
John Reck4d73cb12022-07-27 10:32:52 -0400368 private Request(Surface source, Rect sourceInsets, Executor listenerThread,
John Reck03256dd2022-10-27 15:32:51 -0400369 Consumer<Result> listener) {
John Reck4d73cb12022-07-27 10:32:52 -0400370 this.mSource = source;
371 this.mSourceInsets = sourceInsets;
372 this.mListenerThread = listenerThread;
373 this.mListener = listener;
374 }
375
John Reck4d73cb12022-07-27 10:32:52 -0400376 /**
John Reck03256dd2022-10-27 15:32:51 -0400377 * A builder to create the complete PixelCopy request, which is then executed by calling
378 * {@link #request(Request)} with the built request returned from {@link #build()}
John Reck4d73cb12022-07-27 10:32:52 -0400379 */
John Reck03256dd2022-10-27 15:32:51 -0400380 public static final class Builder {
381 private Request mRequest;
John Reck4d73cb12022-07-27 10:32:52 -0400382
John Reck03256dd2022-10-27 15:32:51 -0400383 private Builder(Request request) {
384 mRequest = request;
John Reck4d73cb12022-07-27 10:32:52 -0400385 }
John Reck03256dd2022-10-27 15:32:51 -0400386
387 private void requireNotBuilt() {
388 if (mRequest == null) {
389 throw new IllegalStateException("build() already called on this builder");
390 }
391 }
392
393 /**
394 * Sets the region of the source to copy from. By default, the entire source is copied
395 * to the output. If only a subset of the source is necessary to be copied, specifying
396 * a srcRect will improve performance by reducing
397 * the amount of data being copied.
398 *
399 * @param srcRect The area of the source to read from. Null or empty will be treated to
400 * mean the entire source
401 * @return this
402 */
403 public @NonNull Builder setSourceRect(@Nullable Rect srcRect) {
404 requireNotBuilt();
405 mRequest.mSrcRect = srcRect;
406 return this;
407 }
408
409 /**
410 * Specifies the output bitmap in which to store the result. By default, a Bitmap of
411 * format {@link android.graphics.Bitmap.Config#ARGB_8888} with a width & height
412 * matching that of the {@link #setSourceRect(Rect) source area} will be created to
413 * place the result.
414 *
415 * @param destination The bitmap to store the result, or null to have a bitmap
416 * automatically created of the appropriate size. If not null, must
417 * not be {@link Bitmap#isRecycled() recycled} and must be
418 * {@link Bitmap#isMutable() mutable}.
419 * @return this
420 */
421 public @NonNull Builder setDestinationBitmap(@Nullable Bitmap destination) {
422 requireNotBuilt();
423 if (destination != null) {
424 validateBitmapDest(destination);
425 }
426 mRequest.mDest = destination;
427 return this;
428 }
429
430 /**
431 * @return The built {@link PixelCopy.Request}
432 */
433 public @NonNull Request build() {
434 requireNotBuilt();
435 Request ret = mRequest;
436 mRequest = null;
437 return ret;
438 }
John Reck4d73cb12022-07-27 10:32:52 -0400439 }
440
441 /**
John Reck03256dd2022-10-27 15:32:51 -0400442 * Creates a PixelCopy request for the given {@link Window}
443 * @param source The Window to copy from
444 * @param callbackExecutor The executor to run the callback on
445 * @param listener The callback for when the copy request is completed
446 * @return A {@link Builder} builder to set the optional params & execute the request
447 */
448 public static @NonNull Builder ofWindow(@NonNull Window source,
449 @NonNull Executor callbackExecutor,
450 @NonNull Consumer<Result> listener) {
451 final Rect insets = new Rect();
452 final Surface surface = sourceForWindow(source, insets);
453 return new Builder(new Request(surface, insets, callbackExecutor, listener));
454 }
455
456 /**
457 * Creates a PixelCopy request for the {@link Window} that the given {@link View} is
458 * attached to.
459 *
460 * Note that this copy request is not cropped to the area the View occupies by default. If
461 * that behavior is desired, use {@link View#getLocationInWindow(int[])} combined with
462 * {@link Builder#setSourceRect(Rect)} to set a crop area to restrict the copy operation.
463 *
464 * @param source A View that {@link View#isAttachedToWindow() is attached} to a window that
465 * will be used to retrieve the window to copy from.
466 * @param callbackExecutor The executor to run the callback on
467 * @param listener The callback for when the copy request is completed
468 * @return A {@link Builder} builder to set the optional params & execute the request
469 */
470 public static @NonNull Builder ofWindow(@NonNull View source,
471 @NonNull Executor callbackExecutor,
472 @NonNull Consumer<Result> listener) {
473 if (source == null || !source.isAttachedToWindow()) {
474 throw new IllegalArgumentException(
475 "View must not be null & must be attached to window");
476 }
477 final Rect insets = new Rect();
478 Surface surface = null;
479 final ViewRootImpl root = source.getViewRootImpl();
480 if (root != null) {
481 surface = root.mSurface;
482 insets.set(root.mWindowAttributes.surfaceInsets);
483 }
484 if (surface == null || !surface.isValid()) {
485 throw new IllegalArgumentException(
486 "Window doesn't have a backing surface!");
487 }
488 return new Builder(new Request(surface, insets, callbackExecutor, listener));
489 }
490
491 /**
492 * Creates a PixelCopy request for the given {@link Surface}
493 *
494 * @param source The Surface to copy from. Must be {@link Surface#isValid() valid}.
495 * @param callbackExecutor The executor to run the callback on
496 * @param listener The callback for when the copy request is completed
497 * @return A {@link Builder} builder to set the optional params & execute the request
498 */
499 public static @NonNull Builder ofSurface(@NonNull Surface source,
500 @NonNull Executor callbackExecutor,
501 @NonNull Consumer<Result> listener) {
502 if (source == null || !source.isValid()) {
503 throw new IllegalArgumentException("Source must not be null & must be valid");
504 }
505 return new Builder(new Request(source, null, callbackExecutor, listener));
506 }
507
508 /**
509 * Creates a PixelCopy request for the {@link Surface} belonging to the
510 * given {@link SurfaceView}
511 *
512 * @param source The SurfaceView to copy from. The backing surface must be
513 * {@link Surface#isValid() valid}
514 * @param callbackExecutor The executor to run the callback on
515 * @param listener The callback for when the copy request is completed
516 * @return A {@link Builder} builder to set the optional params & execute the request
517 */
518 public static @NonNull Builder ofSurface(@NonNull SurfaceView source,
519 @NonNull Executor callbackExecutor,
520 @NonNull Consumer<Result> listener) {
521 return ofSurface(source.getHolder().getSurface(), callbackExecutor, listener);
522 }
523
524 /**
525 * @return The destination bitmap as set by {@link Builder#setDestinationBitmap(Bitmap)}
526 */
527 public @Nullable Bitmap getDestinationBitmap() {
528 return mDest;
529 }
530
531 /**
532 * @return The source rect to copy from as set by {@link Builder#setSourceRect(Rect)}
533 */
534 public @Nullable Rect getSourceRect() {
535 return mSrcRect;
536 }
537
538 /**
539 * @hide
John Reck4d73cb12022-07-27 10:32:52 -0400540 */
541 public void request() {
542 if (!mSource.isValid()) {
543 mListenerThread.execute(() -> mListener.accept(
John Reck03256dd2022-10-27 15:32:51 -0400544 new Result(ERROR_SOURCE_INVALID, null)));
John Reck4d73cb12022-07-27 10:32:52 -0400545 return;
546 }
547 HardwareRenderer.copySurfaceInto(mSource, new HardwareRenderer.CopyRequest(
548 adjustSourceRectForInsets(mSourceInsets, mSrcRect), mDest) {
549 @Override
550 public void onCopyFinished(int result) {
551 mListenerThread.execute(() -> mListener.accept(
John Reck03256dd2022-10-27 15:32:51 -0400552 new Result(result, mDestinationBitmap)));
John Reck4d73cb12022-07-27 10:32:52 -0400553 }
554 });
555 }
556 }
557
558 /**
John Reck03256dd2022-10-27 15:32:51 -0400559 * Executes the pixel copy request
560 * @param request The request to execute
John Reck4d73cb12022-07-27 10:32:52 -0400561 */
John Reck03256dd2022-10-27 15:32:51 -0400562 public static void request(@NonNull Request request) {
563 request.request();
John Reck4d73cb12022-07-27 10:32:52 -0400564 }
565
John Recke94cbc72016-04-25 13:03:44 -0700566 private PixelCopy() {}
567}