1 /*
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 
17 package android.view;
18 
19 import android.annotation.IntDef;
20 import android.annotation.NonNull;
21 import android.annotation.Nullable;
22 import android.graphics.Bitmap;
23 import android.graphics.Rect;
24 import android.os.Handler;
25 import android.view.ViewTreeObserver.OnDrawListener;
26 
27 import java.lang.annotation.Retention;
28 import java.lang.annotation.RetentionPolicy;
29 
30 /**
31  * Provides a mechanisms to issue pixel copy requests to allow for copy
32  * operations from {@link Surface} to {@link Bitmap}
33  */
34 public final class PixelCopy {
35 
36     /** @hide */
37     @Retention(RetentionPolicy.SOURCE)
38     @IntDef({SUCCESS, ERROR_UNKNOWN, ERROR_TIMEOUT, ERROR_SOURCE_NO_DATA,
39         ERROR_SOURCE_INVALID, ERROR_DESTINATION_INVALID})
40     public @interface CopyResultStatus {}
41 
42     /** The pixel copy request succeeded */
43     public static final int SUCCESS = 0;
44 
45     /** The pixel copy request failed with an unknown error. */
46     public static final int ERROR_UNKNOWN = 1;
47 
48     /**
49      * A timeout occurred while trying to acquire a buffer from the source to
50      * copy from.
51      */
52     public static final int ERROR_TIMEOUT = 2;
53 
54     /**
55      * The source has nothing to copy from. When the source is a {@link Surface}
56      * this means that no buffers have been queued yet. Wait for the source
57      * to produce a frame and try again.
58      */
59     public static final int ERROR_SOURCE_NO_DATA = 3;
60 
61     /**
62      * It is not possible to copy from the source. This can happen if the source
63      * is hardware-protected or destroyed.
64      */
65     public static final int ERROR_SOURCE_INVALID = 4;
66 
67     /**
68      * The destination isn't a valid copy target. If the destination is a bitmap
69      * this can occur if the bitmap is too large for the hardware to copy to.
70      * It can also occur if the destination has been destroyed.
71      */
72     public static final int ERROR_DESTINATION_INVALID = 5;
73 
74     /**
75      * Listener for observing the completion of a PixelCopy request.
76      */
77     public interface OnPixelCopyFinishedListener {
78         /**
79          * Callback for when a pixel copy request has completed. This will be called
80          * regardless of whether the copy succeeded or failed.
81          *
82          * @param copyResult Contains the resulting status of the copy request.
83          * This will either be {@link PixelCopy#SUCCESS} or one of the
84          * <code>PixelCopy.ERROR_*</code> values.
85          */
onPixelCopyFinished(@opyResultStatus int copyResult)86         void onPixelCopyFinished(@CopyResultStatus int copyResult);
87     }
88 
89     /**
90      * Requests for the display content of a {@link SurfaceView} to be copied
91      * into a provided {@link Bitmap}.
92      *
93      * The contents of the source will be scaled to fit exactly inside the bitmap.
94      * The pixel format of the source buffer will be converted, as part of the copy,
95      * to fit the the bitmap's {@link Bitmap.Config}. The most recently queued buffer
96      * in the SurfaceView's Surface will be used as the source of the copy.
97      *
98      * @param source The source from which to copy
99      * @param dest The destination of the copy. The source will be scaled to
100      * match the width, height, and format of this bitmap.
101      * @param listener Callback for when the pixel copy request completes
102      * @param listenerThread The callback will be invoked on this Handler when
103      * the copy is finished.
104      */
request(@onNull SurfaceView source, @NonNull Bitmap dest, @NonNull OnPixelCopyFinishedListener listener, @NonNull Handler listenerThread)105     public static void request(@NonNull SurfaceView source, @NonNull Bitmap dest,
106             @NonNull OnPixelCopyFinishedListener listener, @NonNull Handler listenerThread) {
107         request(source.getHolder().getSurface(), dest, listener, listenerThread);
108     }
109 
110     /**
111      * Requests for the display content of a {@link SurfaceView} to be copied
112      * into a provided {@link Bitmap}.
113      *
114      * The contents of the source will be scaled to fit exactly inside the bitmap.
115      * The pixel format of the source buffer will be converted, as part of the copy,
116      * to fit the the bitmap's {@link Bitmap.Config}. The most recently queued buffer
117      * in the SurfaceView's Surface will be used as the source of the copy.
118      *
119      * @param source The source from which to copy
120      * @param srcRect The area of the source to copy from. If this is null
121      * the copy area will be the entire surface. The rect will be clamped to
122      * the bounds of the Surface.
123      * @param dest The destination of the copy. The source will be scaled to
124      * match the width, height, and format of this bitmap.
125      * @param listener Callback for when the pixel copy request completes
126      * @param listenerThread The callback will be invoked on this Handler when
127      * the copy is finished.
128      */
request(@onNull SurfaceView source, @Nullable Rect srcRect, @NonNull Bitmap dest, @NonNull OnPixelCopyFinishedListener listener, @NonNull Handler listenerThread)129     public static void request(@NonNull SurfaceView source, @Nullable Rect srcRect,
130             @NonNull Bitmap dest, @NonNull OnPixelCopyFinishedListener listener,
131             @NonNull Handler listenerThread) {
132         request(source.getHolder().getSurface(), srcRect,
133                 dest, listener, listenerThread);
134     }
135 
136     /**
137      * Requests a copy of the pixels from a {@link Surface} to be copied into
138      * a provided {@link Bitmap}.
139      *
140      * The contents of the source will be scaled to fit exactly inside the bitmap.
141      * The pixel format of the source buffer will be converted, as part of the copy,
142      * to fit the the bitmap's {@link Bitmap.Config}. The most recently queued buffer
143      * in the Surface will be used as the source of the copy.
144      *
145      * @param source The source from which to copy
146      * @param dest The destination of the copy. The source will be scaled to
147      * match the width, height, and format of this bitmap.
148      * @param listener Callback for when the pixel copy request completes
149      * @param listenerThread The callback will be invoked on this Handler when
150      * the copy is finished.
151      */
request(@onNull Surface source, @NonNull Bitmap dest, @NonNull OnPixelCopyFinishedListener listener, @NonNull Handler listenerThread)152     public static void request(@NonNull Surface source, @NonNull Bitmap dest,
153             @NonNull OnPixelCopyFinishedListener listener, @NonNull Handler listenerThread) {
154         request(source, null, dest, listener, listenerThread);
155     }
156 
157     /**
158      * Requests a copy of the pixels at the provided {@link Rect} from
159      * a {@link Surface} to be copied into a provided {@link Bitmap}.
160      *
161      * The contents of the source rect will be scaled to fit exactly inside the bitmap.
162      * The pixel format of the source buffer will be converted, as part of the copy,
163      * to fit the the bitmap's {@link Bitmap.Config}. The most recently queued buffer
164      * in the Surface will be used as the source of the copy.
165      *
166      * @param source The source from which to copy
167      * @param srcRect The area of the source to copy from. If this is null
168      * the copy area will be the entire surface. The rect will be clamped to
169      * the bounds of the Surface.
170      * @param dest The destination of the copy. The source will be scaled to
171      * match the width, height, and format of this bitmap.
172      * @param listener Callback for when the pixel copy request completes
173      * @param listenerThread The callback will be invoked on this Handler when
174      * the copy is finished.
175      */
request(@onNull Surface source, @Nullable Rect srcRect, @NonNull Bitmap dest, @NonNull OnPixelCopyFinishedListener listener, @NonNull Handler listenerThread)176     public static void request(@NonNull Surface source, @Nullable Rect srcRect,
177             @NonNull Bitmap dest, @NonNull OnPixelCopyFinishedListener listener,
178             @NonNull Handler listenerThread) {
179         validateBitmapDest(dest);
180         if (!source.isValid()) {
181             throw new IllegalArgumentException("Surface isn't valid, source.isValid() == false");
182         }
183         if (srcRect != null && srcRect.isEmpty()) {
184             throw new IllegalArgumentException("sourceRect is empty");
185         }
186         // TODO: Make this actually async and fast and cool and stuff
187         int result = ThreadedRenderer.copySurfaceInto(source, srcRect, dest);
188         listenerThread.post(new Runnable() {
189             @Override
190             public void run() {
191                 listener.onPixelCopyFinished(result);
192             }
193         });
194     }
195 
196     /**
197      * Requests a copy of the pixels from a {@link Window} to be copied into
198      * a provided {@link Bitmap}.
199      *
200      * The contents of the source will be scaled to fit exactly inside the bitmap.
201      * The pixel format of the source buffer will be converted, as part of the copy,
202      * to fit the the bitmap's {@link Bitmap.Config}. The most recently queued buffer
203      * in the Window's Surface will be used as the source of the copy.
204      *
205      * Note: This is limited to being able to copy from Window's with a non-null
206      * DecorView. If {@link Window#peekDecorView()} is null this throws an
207      * {@link IllegalArgumentException}. It will similarly throw an exception
208      * if the DecorView has not yet acquired a backing surface. It is recommended
209      * that {@link OnDrawListener} is used to ensure that at least one draw
210      * has happened before trying to copy from the window, otherwise either
211      * an {@link IllegalArgumentException} will be thrown or an error will
212      * be returned to the {@link OnPixelCopyFinishedListener}.
213      *
214      * @param source The source from which to copy
215      * @param dest The destination of the copy. The source will be scaled to
216      * match the width, height, and format of this bitmap.
217      * @param listener Callback for when the pixel copy request completes
218      * @param listenerThread The callback will be invoked on this Handler when
219      * the copy is finished.
220      */
request(@onNull Window source, @NonNull Bitmap dest, @NonNull OnPixelCopyFinishedListener listener, @NonNull Handler listenerThread)221     public static void request(@NonNull Window source, @NonNull Bitmap dest,
222             @NonNull OnPixelCopyFinishedListener listener, @NonNull Handler listenerThread) {
223         request(source, null, dest, listener, listenerThread);
224     }
225 
226     /**
227      * Requests a copy of the pixels at the provided {@link Rect} from
228      * a {@link Window} to be copied into a provided {@link Bitmap}.
229      *
230      * The contents of the source rect will be scaled to fit exactly inside the bitmap.
231      * The pixel format of the source buffer will be converted, as part of the copy,
232      * to fit the the bitmap's {@link Bitmap.Config}. The most recently queued buffer
233      * in the Window's Surface will be used as the source of the copy.
234      *
235      * Note: This is limited to being able to copy from Window's with a non-null
236      * DecorView. If {@link Window#peekDecorView()} is null this throws an
237      * {@link IllegalArgumentException}. It will similarly throw an exception
238      * if the DecorView has not yet acquired a backing surface. It is recommended
239      * that {@link OnDrawListener} is used to ensure that at least one draw
240      * has happened before trying to copy from the window, otherwise either
241      * an {@link IllegalArgumentException} will be thrown or an error will
242      * be returned to the {@link OnPixelCopyFinishedListener}.
243      *
244      * @param source The source from which to copy
245      * @param srcRect The area of the source to copy from. If this is null
246      * the copy area will be the entire surface. The rect will be clamped to
247      * the bounds of the Surface.
248      * @param dest The destination of the copy. The source will be scaled to
249      * match the width, height, and format of this bitmap.
250      * @param listener Callback for when the pixel copy request completes
251      * @param listenerThread The callback will be invoked on this Handler when
252      * the copy is finished.
253      */
request(@onNull Window source, @Nullable Rect srcRect, @NonNull Bitmap dest, @NonNull OnPixelCopyFinishedListener listener, @NonNull Handler listenerThread)254     public static void request(@NonNull Window source, @Nullable Rect srcRect,
255             @NonNull Bitmap dest, @NonNull OnPixelCopyFinishedListener listener,
256             @NonNull Handler listenerThread) {
257         validateBitmapDest(dest);
258         if (source == null) {
259             throw new IllegalArgumentException("source is null");
260         }
261         if (source.peekDecorView() == null) {
262             throw new IllegalArgumentException(
263                     "Only able to copy windows with decor views");
264         }
265         Surface surface = null;
266         final ViewRootImpl root = source.peekDecorView().getViewRootImpl();
267         if (root != null) {
268             surface = root.mSurface;
269             final Rect surfaceInsets = root.mWindowAttributes.surfaceInsets;
270             if (srcRect == null) {
271                 srcRect = new Rect(surfaceInsets.left, surfaceInsets.top,
272                         root.mWidth + surfaceInsets.left, root.mHeight + surfaceInsets.top);
273             } else {
274                 srcRect.offset(surfaceInsets.left, surfaceInsets.top);
275             }
276         }
277         if (surface == null || !surface.isValid()) {
278             throw new IllegalArgumentException(
279                     "Window doesn't have a backing surface!");
280         }
281         request(surface, srcRect, dest, listener, listenerThread);
282     }
283 
validateBitmapDest(Bitmap bitmap)284     private static void validateBitmapDest(Bitmap bitmap) {
285         // TODO: Pre-check max texture dimens if we can
286         if (bitmap == null) {
287             throw new IllegalArgumentException("Bitmap cannot be null");
288         }
289         if (bitmap.isRecycled()) {
290             throw new IllegalArgumentException("Bitmap is recycled");
291         }
292         if (!bitmap.isMutable()) {
293             throw new IllegalArgumentException("Bitmap is immutable");
294         }
295     }
296 
PixelCopy()297     private PixelCopy() {}
298 }
299