xref: /aosp_15_r20/external/robolectric/shadows/framework/src/main/java/org/robolectric/shadows/ShadowCamera.java (revision e6ba16074e6af37d123cb567d575f496bf0a58ee)
1 package org.robolectric.shadows;
2 
3 import static org.robolectric.shadow.api.Shadow.newInstanceOf;
4 
5 import android.hardware.Camera;
6 import android.view.SurfaceHolder;
7 import com.google.common.base.Joiner;
8 import com.google.common.collect.ImmutableList;
9 import com.google.common.collect.ImmutableMap;
10 import java.util.ArrayList;
11 import java.util.Collections;
12 import java.util.HashMap;
13 import java.util.List;
14 import java.util.Map;
15 import org.robolectric.annotation.Implementation;
16 import org.robolectric.annotation.Implements;
17 import org.robolectric.annotation.RealObject;
18 import org.robolectric.annotation.Resetter;
19 import org.robolectric.shadow.api.Shadow;
20 import org.robolectric.util.ReflectionHelpers;
21 import org.robolectric.util.ReflectionHelpers.ClassParameter;
22 
23 @Implements(Camera.class)
24 public class ShadowCamera {
25   // These are completely arbitrary and likely outdated default parameters that have been added long
26   // ago.
27   private static final ImmutableMap<String, String> DEFAULT_PARAMS =
28       ImmutableMap.<String, String>builder()
29           .put("picture-size", "1280x960")
30           .put("preview-size", "640x480")
31           .put("preview-fps-range", "10,30")
32           .put("preview-frame-rate", "30")
33           .put("preview-format", "yuv420sp")
34           .put("picture-format-values", "yuv420sp,jpeg")
35           .put("preview-format-values", "yuv420sp,jpeg")
36           .put("picture-size-values", "320x240,640x480,800x600")
37           .put("preview-size-values", "320x240,640x480")
38           .put("preview-fps-range-values", "(15000,15000),(10000,30000)")
39           .put("preview-frame-rate-values", "10,15,30")
40           .put("exposure-compensation", "0")
41           .put("exposure-compensation-step", "0.5")
42           .put("min-exposure-compensation", "-6")
43           .put("max-exposure-compensation", "6")
44           .put("focus-mode-values", Camera.Parameters.FOCUS_MODE_AUTO)
45           .put("focus-mode", Camera.Parameters.FOCUS_MODE_AUTO)
46           .put(
47               "flash-mode-values",
48               Camera.Parameters.FLASH_MODE_AUTO
49                   + ","
50                   + Camera.Parameters.FLASH_MODE_ON
51                   + ","
52                   + Camera.Parameters.FLASH_MODE_OFF)
53           .put("flash-mode", Camera.Parameters.FLASH_MODE_AUTO)
54           .put("max-num-focus-areas", "1")
55           .put("max-num-metering-areas", "1")
56           .build();
57 
58   private static int lastOpenedCameraId;
59 
60   private int id;
61   private boolean locked = true;
62   private boolean previewing;
63   private boolean released;
64   private Camera.Parameters parameters;
65   private Camera.PreviewCallback previewCallback;
66   private List<byte[]> callbackBuffers = new ArrayList<>();
67   private SurfaceHolder surfaceHolder;
68   private int displayOrientation;
69   private Camera.AutoFocusCallback autoFocusCallback;
70   private boolean autoFocusing;
71   private boolean shutterSoundEnabled = true;
72 
73   private static final Map<Integer, Camera.CameraInfo> cameras = new HashMap<>();
74   private static final Map<Integer, Camera.Parameters> cameraParameters = new HashMap<>();
75 
76   @RealObject private Camera realCamera;
77 
78   @Implementation
open()79   protected static Camera open() {
80     return open(0);
81   }
82 
83   @Implementation
open(int cameraId)84   protected static Camera open(int cameraId) {
85     lastOpenedCameraId = cameraId;
86     Camera camera = newInstanceOf(Camera.class);
87     ShadowCamera shadowCamera = Shadow.extract(camera);
88     shadowCamera.id = cameraId;
89     if (cameraParameters.containsKey(cameraId)) {
90       shadowCamera.parameters = cameraParameters.get(cameraId);
91     } else {
92       cameraParameters.put(cameraId, camera.getParameters());
93     }
94     return camera;
95   }
96 
getLastOpenedCameraId()97   public static int getLastOpenedCameraId() {
98     return lastOpenedCameraId;
99   }
100 
101   @Implementation
unlock()102   protected void unlock() {
103     locked = false;
104   }
105 
106   @Implementation
reconnect()107   protected void reconnect() {
108     locked = true;
109   }
110 
111   @Implementation
getParameters()112   protected Camera.Parameters getParameters() {
113     if (parameters == null) {
114       parameters =
115           ReflectionHelpers.callConstructor(
116               Camera.Parameters.class, ClassParameter.from(Camera.class, realCamera));
117       Joiner.MapJoiner mapJoiner = Joiner.on(";").withKeyValueSeparator("=");
118       parameters.unflatten(mapJoiner.join(DEFAULT_PARAMS));
119     }
120     return parameters;
121   }
122 
123   @Implementation
setParameters(Camera.Parameters params)124   protected void setParameters(Camera.Parameters params) {
125     parameters = params;
126   }
127 
128   @Implementation
setPreviewDisplay(SurfaceHolder holder)129   protected void setPreviewDisplay(SurfaceHolder holder) {
130     surfaceHolder = holder;
131   }
132 
133   @Implementation
startPreview()134   protected void startPreview() {
135     previewing = true;
136   }
137 
138   @Implementation
stopPreview()139   protected void stopPreview() {
140     previewing = false;
141   }
142 
143   @Implementation
release()144   protected void release() {
145     released = true;
146   }
147 
148   @Implementation
setPreviewCallback(Camera.PreviewCallback cb)149   protected void setPreviewCallback(Camera.PreviewCallback cb) {
150     previewCallback = cb;
151   }
152 
153   @Implementation
setOneShotPreviewCallback(Camera.PreviewCallback cb)154   protected void setOneShotPreviewCallback(Camera.PreviewCallback cb) {
155     previewCallback = cb;
156   }
157 
158   @Implementation
setPreviewCallbackWithBuffer(Camera.PreviewCallback cb)159   protected void setPreviewCallbackWithBuffer(Camera.PreviewCallback cb) {
160     previewCallback = cb;
161   }
162 
163   /**
164    * Allows test cases to invoke the preview callback, to simulate a frame of camera data.
165    *
166    * @param data byte buffer of simulated camera data
167    */
invokePreviewCallback(byte[] data)168   public void invokePreviewCallback(byte[] data) {
169     if (previewCallback != null) {
170       previewCallback.onPreviewFrame(data, realCamera);
171     }
172   }
173 
174   @Implementation
addCallbackBuffer(byte[] callbackBuffer)175   protected void addCallbackBuffer(byte[] callbackBuffer) {
176     callbackBuffers.add(callbackBuffer);
177   }
178 
getAddedCallbackBuffers()179   public List<byte[]> getAddedCallbackBuffers() {
180     return Collections.unmodifiableList(callbackBuffers);
181   }
182 
183   @Implementation
setDisplayOrientation(int degrees)184   protected void setDisplayOrientation(int degrees) {
185     displayOrientation = degrees;
186     if (cameras.containsKey(id)) {
187       cameras.get(id).orientation = degrees;
188     }
189   }
190 
getDisplayOrientation()191   public int getDisplayOrientation() {
192     return displayOrientation;
193   }
194 
195   @Implementation
autoFocus(Camera.AutoFocusCallback callback)196   protected void autoFocus(Camera.AutoFocusCallback callback) {
197     autoFocusCallback = callback;
198     autoFocusing = true;
199   }
200 
201   @Implementation
cancelAutoFocus()202   protected void cancelAutoFocus() {
203     autoFocusCallback = null;
204     autoFocusing = false;
205   }
206 
hasRequestedAutoFocus()207   public boolean hasRequestedAutoFocus() {
208     return autoFocusing;
209   }
210 
invokeAutoFocusCallback(boolean success, Camera camera)211   public void invokeAutoFocusCallback(boolean success, Camera camera) {
212     if (autoFocusCallback == null) {
213       throw new IllegalStateException(
214           "cannot invoke AutoFocusCallback before autoFocus() has been called "
215               + "or after cancelAutoFocus() has been called "
216               + "or after the callback has been invoked.");
217     }
218     autoFocusCallback.onAutoFocus(success, camera);
219     autoFocusCallback = null;
220     autoFocusing = false;
221   }
222 
223   @Implementation
getCameraInfo(int cameraId, Camera.CameraInfo cameraInfo)224   protected static void getCameraInfo(int cameraId, Camera.CameraInfo cameraInfo) {
225     Camera.CameraInfo foundCam = cameras.get(cameraId);
226     cameraInfo.facing = foundCam.facing;
227     cameraInfo.orientation = foundCam.orientation;
228     cameraInfo.canDisableShutterSound = foundCam.canDisableShutterSound;
229   }
230 
231   @Implementation
getNumberOfCameras()232   protected static int getNumberOfCameras() {
233     return cameras.size();
234   }
235 
236   @Implementation
takePicture( Camera.ShutterCallback shutter, Camera.PictureCallback raw, Camera.PictureCallback jpeg)237   protected void takePicture(
238       Camera.ShutterCallback shutter, Camera.PictureCallback raw, Camera.PictureCallback jpeg) {
239     if (shutter != null) {
240       shutter.onShutter();
241     }
242 
243     if (raw != null) {
244       raw.onPictureTaken(new byte[0], realCamera);
245     }
246 
247     if (jpeg != null) {
248       jpeg.onPictureTaken(new byte[0], realCamera);
249     }
250   }
251 
252   @Implementation
enableShutterSound(boolean enabled)253   protected boolean enableShutterSound(boolean enabled) {
254     if (!enabled && cameras.containsKey(id) && !cameras.get(id).canDisableShutterSound) {
255       return false;
256     }
257     shutterSoundEnabled = enabled;
258     return true;
259   }
260 
261   /** Returns {@code true} if the default shutter sound is played when taking a picture. */
isShutterSoundEnabled()262   public boolean isShutterSoundEnabled() {
263     return shutterSoundEnabled;
264   }
265 
isLocked()266   public boolean isLocked() {
267     return locked;
268   }
269 
isPreviewing()270   public boolean isPreviewing() {
271     return previewing;
272   }
273 
isReleased()274   public boolean isReleased() {
275     return released;
276   }
277 
getPreviewDisplay()278   public SurfaceHolder getPreviewDisplay() {
279     return surfaceHolder;
280   }
281 
282   /**
283    * Add a mock {@code Camera.CameraInfo} object to simulate the existence of one or more cameras.
284    * By default, no cameras are defined.
285    *
286    * @param id The camera id
287    * @param camInfo The CameraInfo
288    */
addCameraInfo(int id, Camera.CameraInfo camInfo)289   public static void addCameraInfo(int id, Camera.CameraInfo camInfo) {
290     cameras.put(id, camInfo);
291   }
292 
293   @Resetter
clearCameraInfo()294   public static void clearCameraInfo() {
295     cameras.clear();
296     cameraParameters.clear();
297   }
298 
299   /** Shadows the Android {@code Camera.Parameters} class. */
300   @Implements(Camera.Parameters.class)
301   public static class ShadowParameters {
302 
303     @SuppressWarnings("nullness:initialization.field.uninitialized") // Managed by Robolectric
304     @RealObject
305     private Camera.Parameters realParameters;
306 
initSupportedPreviewSizes()307     public void initSupportedPreviewSizes() {
308       realParameters.remove("preview-size-values");
309     }
310 
setSupportedFocusModes(String... focusModes)311     public void setSupportedFocusModes(String... focusModes) {
312       realParameters.set("focus-mode-values", Joiner.on(",").join(focusModes));
313       if (focusModes.length == 0) {
314         realParameters.remove("focus-mode");
315       }
316     }
317 
setSupportedFlashModes(String... flashModes)318     public void setSupportedFlashModes(String... flashModes) {
319       realParameters.set("flash-mode-values", Joiner.on(",").join(flashModes));
320       if (flashModes.length == 0) {
321         realParameters.remove("flash-mode");
322       }
323     }
324 
325     /**
326      * Allows test cases to set the maximum number of focus areas. See {@link
327      * Camera.Parameters#getMaxNumFocusAreas}.
328      */
setMaxNumFocusAreas(int maxNumFocusAreas)329     public void setMaxNumFocusAreas(int maxNumFocusAreas) {
330       realParameters.set("max-num-focus-areas", maxNumFocusAreas);
331     }
332 
addSupportedPreviewSize(int width, int height)333     public void addSupportedPreviewSize(int width, int height) {
334       List<String> sizesStrings = new ArrayList<>();
335       List<Camera.Size> sizes = realParameters.getSupportedPreviewSizes();
336       if (sizes == null) {
337         sizes = ImmutableList.of();
338       }
339       for (Camera.Size size : sizes) {
340         sizesStrings.add(size.width + "x" + size.height);
341       }
342       sizesStrings.add(width + "x" + height);
343       realParameters.set("preview-size-values", Joiner.on(",").join(sizesStrings));
344     }
345 
346     /**
347      * Allows test cases to set the maximum number of metering areas. See {@link
348      * Camera.Parameters#getMaxNumMeteringAreas}.
349      */
setMaxNumMeteringAreas(int maxNumMeteringAreas)350     public void setMaxNumMeteringAreas(int maxNumMeteringAreas) {
351       realParameters.set("max-num-metering-areas", maxNumMeteringAreas);
352     }
353 
getPreviewWidth()354     public int getPreviewWidth() {
355       return realParameters.getPreviewSize().width;
356     }
357 
getPreviewHeight()358     public int getPreviewHeight() {
359       return realParameters.getPreviewSize().height;
360     }
361 
getPictureWidth()362     public int getPictureWidth() {
363       return realParameters.getPictureSize().width;
364     }
365 
getPictureHeight()366     public int getPictureHeight() {
367       return realParameters.getPictureSize().height;
368     }
369 
getRotation()370     public int getRotation() {
371       return realParameters.getInt("rotation");
372     }
373   }
374 }
375