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.graphics.Bitmap;
22import android.os.Handler;
23
24import java.lang.annotation.Retention;
25import java.lang.annotation.RetentionPolicy;
26
27/**
28 * Provides a mechanisms to issue pixel copy requests to allow for copy
29 * operations from {@link Surface} to {@link Bitmap}
30 */
31public final class PixelCopy {
32
33    /** @hide */
34    @Retention(RetentionPolicy.SOURCE)
35    @IntDef({SUCCESS, ERROR_UNKNOWN, ERROR_TIMEOUT, ERROR_SOURCE_NO_DATA,
36        ERROR_SOURCE_INVALID, ERROR_DESTINATION_INVALID})
37    public @interface CopyResultStatus {}
38
39    /** The pixel copy request succeeded */
40    public static final int SUCCESS = 0;
41
42    /** The pixel copy request failed with an unknown error. */
43    public static final int ERROR_UNKNOWN = 1;
44
45    /**
46     * A timeout occurred while trying to acquire a buffer from the source to
47     * copy from.
48     */
49    public static final int ERROR_TIMEOUT = 2;
50
51    /**
52     * The source has nothing to copy from. When the source is a {@link Surface}
53     * this means that no buffers have been queued yet. Wait for the source
54     * to produce a frame and try again.
55     */
56    public static final int ERROR_SOURCE_NO_DATA = 3;
57
58    /**
59     * It is not possible to copy from the source. This can happen if the source
60     * is hardware-protected or destroyed.
61     */
62    public static final int ERROR_SOURCE_INVALID = 4;
63
64    /**
65     * The destination isn't a valid copy target. If the destination is a bitmap
66     * this can occur if the bitmap is too large for the hardware to copy to.
67     * It can also occur if the destination has been destroyed.
68     */
69    public static final int ERROR_DESTINATION_INVALID = 5;
70
71    /**
72     * Listener for observing the completion of a PixelCopy request.
73     */
74    public interface OnPixelCopyFinishedListener {
75        /**
76         * Callback for when a pixel copy request has completed. This will be called
77         * regardless of whether the copy succeeded or failed.
78         *
79         * @param copyResult Contains the resulting status of the copy request.
80         * This will either be {@link PixelCopy#SUCCESS} or one of the
81         * <code>PixelCopy.ERROR_*</code> values.
82         */
83        void onPixelCopyFinished(@CopyResultStatus int copyResult);
84    }
85
86    /**
87     * Requests for the display content of a {@link SurfaceView} to be copied
88     * into a provided {@link Bitmap}.
89     *
90     * The contents of the source will be scaled to fit exactly inside the bitmap.
91     * The pixel format of the source buffer will be converted, as part of the copy,
92     * to fit the the bitmap's {@link Bitmap.Config}. The most recently queued buffer
93     * in the SurfaceView's Surface will be used as the source of the copy.
94     *
95     * @param source The source from which to copy
96     * @param dest The destination of the copy. The source will be scaled to
97     * match the width, height, and format of this bitmap.
98     * @param listener Callback for when the pixel copy request completes
99     * @param listenerThread The callback will be invoked on this Handler when
100     * the copy is finished.
101     */
102    public static void request(@NonNull SurfaceView source, @NonNull Bitmap dest,
103            @NonNull OnPixelCopyFinishedListener listener, @NonNull Handler listenerThread) {
104        request(source.getHolder().getSurface(), dest, listener, listenerThread);
105    }
106
107    /**
108     * Requests a copy of the pixels from a {@link Surface} to be copied into
109     * a provided {@link Bitmap}.
110     *
111     * The contents of the source will be scaled to fit exactly inside the bitmap.
112     * The pixel format of the source buffer will be converted, as part of the copy,
113     * to fit the the bitmap's {@link Bitmap.Config}. The most recently queued buffer
114     * in the Surface will be used as the source of the copy.
115     *
116     * @param source The source from which to copy
117     * @param dest The destination of the copy. The source will be scaled to
118     * match the width, height, and format of this bitmap.
119     * @param listener Callback for when the pixel copy request completes
120     * @param listenerThread The callback will be invoked on this Handler when
121     * the copy is finished.
122     */
123    public static void request(@NonNull Surface source, @NonNull Bitmap dest,
124            @NonNull OnPixelCopyFinishedListener listener, @NonNull Handler listenerThread) {
125        validateBitmapDest(dest);
126        if (!source.isValid()) {
127            throw new IllegalArgumentException("Surface isn't valid, source.isValid() == false");
128        }
129        // TODO: Make this actually async and fast and cool and stuff
130        int result = ThreadedRenderer.copySurfaceInto(source, dest);
131        listenerThread.post(new Runnable() {
132            @Override
133            public void run() {
134                listener.onPixelCopyFinished(result);
135            }
136        });
137    }
138
139    private static void validateBitmapDest(Bitmap bitmap) {
140        // TODO: Pre-check max texture dimens if we can
141        if (bitmap == null) {
142            throw new IllegalArgumentException("Bitmap cannot be null");
143        }
144        if (bitmap.isRecycled()) {
145            throw new IllegalArgumentException("Bitmap is recycled");
146        }
147        if (!bitmap.isMutable()) {
148            throw new IllegalArgumentException("Bitmap is immutable");
149        }
150    }
151
152    private PixelCopy() {}
153}
154