1 /*
2  * Copyright 2023 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 androidx.camera.testing.impl.fakes;
18 
19 import android.graphics.Bitmap;
20 import android.graphics.Rect;
21 import android.media.Image;
22 
23 import androidx.annotation.GuardedBy;
24 import androidx.camera.core.ExperimentalGetImage;
25 import androidx.camera.core.ImageInfo;
26 import androidx.camera.core.ImageProxy;
27 import androidx.concurrent.futures.CallbackToFutureAdapter;
28 import androidx.core.util.Preconditions;
29 
30 import com.google.common.util.concurrent.ListenableFuture;
31 
32 import org.jspecify.annotations.NonNull;
33 import org.jspecify.annotations.Nullable;
34 
35 /**
36  * A fake implementation of {@link ImageProxy} where the values are settable.
37  */
38 public final class FakeImageProxy implements ImageProxy {
39     private Rect mCropRect = new Rect();
40     private int mFormat = 0;
41     private int mHeight = 0;
42     private int mWidth = 0;
43 
44     private PlaneProxy @NonNull [] mPlaneProxy = new PlaneProxy[0];
45 
46     private boolean mClosed = false;
47 
48     private @NonNull ImageInfo mImageInfo;
49     private Image mImage;
50     private @Nullable Bitmap mBitmap;
51     @SuppressWarnings("WeakerAccess") /* synthetic accessor */
52     final Object mReleaseLock = new Object();
53     @SuppressWarnings("WeakerAccess") /* synthetic accessor */
54     @GuardedBy("mReleaseLock")
55     ListenableFuture<Void> mReleaseFuture;
56     @SuppressWarnings("WeakerAccess") /* synthetic accessor */
57     @GuardedBy("mReleaseLock")
58     CallbackToFutureAdapter.Completer<Void> mReleaseCompleter;
59 
FakeImageProxy(@onNull ImageInfo imageInfo)60     public FakeImageProxy(@NonNull ImageInfo imageInfo) {
61         mImageInfo = imageInfo;
62     }
63 
FakeImageProxy(@onNull ImageInfo imageInfo, @NonNull Bitmap bitmap)64     public FakeImageProxy(@NonNull ImageInfo imageInfo, @NonNull Bitmap bitmap) {
65         mImageInfo = imageInfo;
66         mBitmap = bitmap;
67     }
68 
69     @Override
close()70     public void close() {
71         synchronized (mReleaseLock) {
72             mClosed = true;
73             if (mReleaseCompleter != null) {
74                 mReleaseCompleter.set(null);
75                 mReleaseCompleter = null;
76             }
77         }
78     }
79 
80     @Override
getCropRect()81     public @NonNull Rect getCropRect() {
82         synchronized (mReleaseLock) {
83             if (mClosed) {
84                 throw new IllegalStateException("FakeImageProxy already closed");
85             }
86             return mCropRect;
87         }
88     }
89 
90     @Override
setCropRect(@ullable Rect rect)91     public void setCropRect(@Nullable Rect rect) {
92         mCropRect = rect != null ? rect : new Rect();
93     }
94 
95     @Override
getFormat()96     public int getFormat() {
97         synchronized (mReleaseLock) {
98             if (mClosed) {
99                 throw new IllegalStateException("FakeImageProxy already closed");
100             }
101             return mFormat;
102         }
103     }
104 
105     @Override
getHeight()106     public int getHeight() {
107         synchronized (mReleaseLock) {
108             if (mClosed) {
109                 throw new IllegalStateException("FakeImageProxy already closed");
110             }
111             return mHeight;
112         }
113     }
114 
115     @Override
getWidth()116     public int getWidth() {
117         synchronized (mReleaseLock) {
118             if (mClosed) {
119                 throw new IllegalStateException("FakeImageProxy already closed");
120             }
121             return mWidth;
122         }
123     }
124 
125     @Override
getPlanes()126     public PlaneProxy @NonNull [] getPlanes() {
127         synchronized (mReleaseLock) {
128             if (mClosed) {
129                 throw new IllegalStateException("FakeImageProxy already closed");
130             }
131             return mPlaneProxy;
132         }
133     }
134 
135     @Override
getImageInfo()136     public @NonNull ImageInfo getImageInfo() {
137         return mImageInfo;
138     }
139 
140     @Override
141     @ExperimentalGetImage
getImage()142     public @Nullable Image getImage() {
143         return mImage;
144     }
145 
146     /**
147      * Checks the image close status.
148      * @return true if image closed, false otherwise.
149      */
isClosed()150     public boolean isClosed() {
151         synchronized (mReleaseLock) {
152             return mClosed;
153         }
154     }
155 
setFormat(int format)156     public void setFormat(int format) {
157         mFormat = format;
158     }
159 
setHeight(int height)160     public void setHeight(int height) {
161         mHeight = height;
162     }
163 
setWidth(int width)164     public void setWidth(int width) {
165         mWidth = width;
166     }
167 
setPlanes(PlaneProxy @onNull [] planeProxy)168     public void setPlanes(PlaneProxy @NonNull [] planeProxy) {
169         mPlaneProxy = planeProxy;
170     }
171 
setImageInfo(@onNull ImageInfo imageInfo)172     public void setImageInfo(@NonNull ImageInfo imageInfo) {
173         mImageInfo = imageInfo;
174     }
175 
setImage(@ullable Image image)176     public void setImage(@Nullable Image image) {
177         mImage = image;
178     }
179 
180     /**
181      * Returns ListenableFuture that completes when the {@link FakeImageProxy} has closed.
182      */
183     @SuppressWarnings("ObjectToString")
getCloseFuture()184     public @NonNull ListenableFuture<Void> getCloseFuture() {
185         synchronized (mReleaseLock) {
186             if (mReleaseFuture == null) {
187                 mReleaseFuture = CallbackToFutureAdapter.getFuture(
188                         completer -> {
189                             synchronized (mReleaseLock) {
190                                 Preconditions.checkState(mReleaseCompleter == null,
191                                         "Release completer expected to be null");
192                                 mReleaseCompleter = completer;
193                                 return "Release[imageProxy=" + FakeImageProxy.this + "]";
194                             }
195                         });
196             }
197             return mReleaseFuture;
198         }
199     }
200 
201     @Override
toBitmap()202     public @NonNull Bitmap toBitmap() {
203         if (mBitmap != null) {
204             return mBitmap;
205         }
206         return ImageProxy.super.toBitmap();
207     }
208 }
209