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
17package android.view;
18
19import android.annotation.IntDef;
20import android.annotation.NonNull;
21import android.annotation.Nullable;
22import android.graphics.Bitmap;
23import android.graphics.Rect;
24import android.os.Handler;
25import android.view.ViewTreeObserver.OnDrawListener;
26
27import java.lang.annotation.Retention;
28import 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 */
34public 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         */
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     */
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     */
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     */
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     */
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     */
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     */
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        if (source.peekDecorView().getViewRootImpl() != null) {
267            surface = source.peekDecorView().getViewRootImpl().mSurface;
268        }
269        if (surface == null || !surface.isValid()) {
270            throw new IllegalArgumentException(
271                    "Window doesn't have a backing surface!");
272        }
273        request(surface, srcRect, dest, listener, listenerThread);
274    }
275
276    private static void validateBitmapDest(Bitmap bitmap) {
277        // TODO: Pre-check max texture dimens if we can
278        if (bitmap == null) {
279            throw new IllegalArgumentException("Bitmap cannot be null");
280        }
281        if (bitmap.isRecycled()) {
282            throw new IllegalArgumentException("Bitmap is recycled");
283        }
284        if (!bitmap.isMutable()) {
285            throw new IllegalArgumentException("Bitmap is immutable");
286        }
287    }
288
289    private PixelCopy() {}
290}
291