xref: /aosp_15_r20/external/zxing/android/src/com/google/zxing/client/android/camera/CameraManager.java (revision 513427e33d61bc67fc40bc261642ac0b2a686b45)
1 /*
2  * Copyright (C) 2008 ZXing authors
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 com.google.zxing.client.android.camera;
18 
19 import android.content.Context;
20 import android.graphics.Point;
21 import android.graphics.Rect;
22 import android.hardware.Camera;
23 import android.os.Handler;
24 import android.util.Log;
25 import android.view.SurfaceHolder;
26 import com.google.zxing.PlanarYUVLuminanceSource;
27 import com.google.zxing.client.android.camera.open.OpenCamera;
28 import com.google.zxing.client.android.camera.open.OpenCameraInterface;
29 
30 import java.io.IOException;
31 
32 /**
33  * This object wraps the Camera service object and expects to be the only one talking to it. The
34  * implementation encapsulates the steps needed to take preview-sized images, which are used for
35  * both preview and decoding.
36  *
37  * @author [email protected] (Daniel Switkin)
38  */
39 @SuppressWarnings("deprecation") // camera APIs
40 public final class CameraManager {
41 
42   private static final String TAG = CameraManager.class.getSimpleName();
43 
44   private static final int MIN_FRAME_WIDTH = 240;
45   private static final int MIN_FRAME_HEIGHT = 240;
46   private static final int MAX_FRAME_WIDTH = 1200; // = 5/8 * 1920
47   private static final int MAX_FRAME_HEIGHT = 675; // = 5/8 * 1080
48 
49   private final Context context;
50   private final CameraConfigurationManager configManager;
51   private OpenCamera camera;
52   private AutoFocusManager autoFocusManager;
53   private Rect framingRect;
54   private Rect framingRectInPreview;
55   private boolean initialized;
56   private boolean previewing;
57   private int requestedCameraId = OpenCameraInterface.NO_REQUESTED_CAMERA;
58   private int requestedFramingRectWidth;
59   private int requestedFramingRectHeight;
60   /**
61    * Preview frames are delivered here, which we pass on to the registered handler. Make sure to
62    * clear the handler so it will only receive one message.
63    */
64   private final PreviewCallback previewCallback;
65 
CameraManager(Context context)66   public CameraManager(Context context) {
67     this.context = context;
68     this.configManager = new CameraConfigurationManager(context);
69     previewCallback = new PreviewCallback(configManager);
70   }
71 
72   /**
73    * Opens the camera driver and initializes the hardware parameters.
74    *
75    * @param holder The surface object which the camera will draw preview frames into.
76    * @throws IOException Indicates the camera driver failed to open.
77    */
openDriver(SurfaceHolder holder)78   public synchronized void openDriver(SurfaceHolder holder) throws IOException {
79     OpenCamera theCamera = camera;
80     if (theCamera == null) {
81       theCamera = OpenCameraInterface.open(requestedCameraId);
82       if (theCamera == null) {
83         throw new IOException("Camera.open() failed to return object from driver");
84       }
85       camera = theCamera;
86     }
87 
88     if (!initialized) {
89       initialized = true;
90       configManager.initFromCameraParameters(theCamera);
91       if (requestedFramingRectWidth > 0 && requestedFramingRectHeight > 0) {
92         setManualFramingRect(requestedFramingRectWidth, requestedFramingRectHeight);
93         requestedFramingRectWidth = 0;
94         requestedFramingRectHeight = 0;
95       }
96     }
97 
98     Camera cameraObject = theCamera.getCamera();
99     Camera.Parameters parameters = cameraObject.getParameters();
100     String parametersFlattened = parameters == null ? null : parameters.flatten(); // Save these, temporarily
101     try {
102       configManager.setDesiredCameraParameters(theCamera, false);
103     } catch (RuntimeException re) {
104       // Driver failed
105       Log.w(TAG, "Camera rejected parameters. Setting only minimal safe-mode parameters");
106       Log.i(TAG, "Resetting to saved camera params: " + parametersFlattened);
107       // Reset:
108       if (parametersFlattened != null) {
109         parameters = cameraObject.getParameters();
110         parameters.unflatten(parametersFlattened);
111         try {
112           cameraObject.setParameters(parameters);
113           configManager.setDesiredCameraParameters(theCamera, true);
114         } catch (RuntimeException re2) {
115           // Well, darn. Give up
116           Log.w(TAG, "Camera rejected even safe-mode parameters! No configuration");
117         }
118       }
119     }
120     cameraObject.setPreviewDisplay(holder);
121 
122   }
123 
isOpen()124   public synchronized boolean isOpen() {
125     return camera != null;
126   }
127 
128   /**
129    * Closes the camera driver if still in use.
130    */
closeDriver()131   public synchronized void closeDriver() {
132     if (camera != null) {
133       camera.getCamera().release();
134       camera = null;
135       // Make sure to clear these each time we close the camera, so that any scanning rect
136       // requested by intent is forgotten.
137       framingRect = null;
138       framingRectInPreview = null;
139     }
140   }
141 
142   /**
143    * Asks the camera hardware to begin drawing preview frames to the screen.
144    */
startPreview()145   public synchronized void startPreview() {
146     OpenCamera theCamera = camera;
147     if (theCamera != null && !previewing) {
148       theCamera.getCamera().startPreview();
149       previewing = true;
150       autoFocusManager = new AutoFocusManager(context, theCamera.getCamera());
151     }
152   }
153 
154   /**
155    * Tells the camera to stop drawing preview frames.
156    */
stopPreview()157   public synchronized void stopPreview() {
158     if (autoFocusManager != null) {
159       autoFocusManager.stop();
160       autoFocusManager = null;
161     }
162     if (camera != null && previewing) {
163       camera.getCamera().stopPreview();
164       previewCallback.setHandler(null, 0);
165       previewing = false;
166     }
167   }
168 
169   /**
170    * Convenience method for {@link com.google.zxing.client.android.CaptureActivity}
171    *
172    * @param newSetting if {@code true}, light should be turned on if currently off. And vice versa.
173    */
setTorch(boolean newSetting)174   public synchronized void setTorch(boolean newSetting) {
175     OpenCamera theCamera = camera;
176     if (theCamera != null && newSetting != configManager.getTorchState(theCamera.getCamera())) {
177       boolean wasAutoFocusManager = autoFocusManager != null;
178       if (wasAutoFocusManager) {
179         autoFocusManager.stop();
180         autoFocusManager = null;
181       }
182       configManager.setTorch(theCamera.getCamera(), newSetting);
183       if (wasAutoFocusManager) {
184         autoFocusManager = new AutoFocusManager(context, theCamera.getCamera());
185         autoFocusManager.start();
186       }
187     }
188   }
189 
190   /**
191    * A single preview frame will be returned to the handler supplied. The data will arrive as byte[]
192    * in the message.obj field, with width and height encoded as message.arg1 and message.arg2,
193    * respectively.
194    *
195    * @param handler The handler to send the message to.
196    * @param message The what field of the message to be sent.
197    */
requestPreviewFrame(Handler handler, int message)198   public synchronized void requestPreviewFrame(Handler handler, int message) {
199     OpenCamera theCamera = camera;
200     if (theCamera != null && previewing) {
201       previewCallback.setHandler(handler, message);
202       theCamera.getCamera().setOneShotPreviewCallback(previewCallback);
203     }
204   }
205 
206   /**
207    * Calculates the framing rect which the UI should draw to show the user where to place the
208    * barcode. This target helps with alignment as well as forces the user to hold the device
209    * far enough away to ensure the image will be in focus.
210    *
211    * @return The rectangle to draw on screen in window coordinates.
212    */
getFramingRect()213   public synchronized Rect getFramingRect() {
214     if (framingRect == null) {
215       if (camera == null) {
216         return null;
217       }
218       Point screenResolution = configManager.getScreenResolution();
219       if (screenResolution == null) {
220         // Called early, before init even finished
221         return null;
222       }
223 
224       int width = findDesiredDimensionInRange(screenResolution.x, MIN_FRAME_WIDTH, MAX_FRAME_WIDTH);
225       int height = findDesiredDimensionInRange(screenResolution.y, MIN_FRAME_HEIGHT, MAX_FRAME_HEIGHT);
226 
227       int leftOffset = (screenResolution.x - width) / 2;
228       int topOffset = (screenResolution.y - height) / 2;
229       framingRect = new Rect(leftOffset, topOffset, leftOffset + width, topOffset + height);
230     }
231     return framingRect;
232   }
233 
findDesiredDimensionInRange(int resolution, int hardMin, int hardMax)234   private static int findDesiredDimensionInRange(int resolution, int hardMin, int hardMax) {
235     int dim = 5 * resolution / 8; // Target 5/8 of each dimension
236     if (dim < hardMin) {
237       return hardMin;
238     }
239     return Math.min(dim, hardMax);
240   }
241 
242   /**
243    * Like {@link #getFramingRect} but coordinates are in terms of the preview frame,
244    * not UI / screen.
245    *
246    * @return {@link Rect} expressing barcode scan area in terms of the preview size
247    */
getFramingRectInPreview()248   public synchronized Rect getFramingRectInPreview() {
249     if (framingRectInPreview == null) {
250       Rect framingRect = getFramingRect();
251       if (framingRect == null) {
252         return null;
253       }
254       Rect rect = new Rect(framingRect);
255       Point cameraResolution = configManager.getCameraResolution();
256       Point screenResolution = configManager.getScreenResolution();
257       if (cameraResolution == null || screenResolution == null) {
258         // Called early, before init even finished
259         return null;
260       }
261       rect.left = rect.left * cameraResolution.x / screenResolution.x;
262       rect.right = rect.right * cameraResolution.x / screenResolution.x;
263       rect.top = rect.top * cameraResolution.y / screenResolution.y;
264       rect.bottom = rect.bottom * cameraResolution.y / screenResolution.y;
265       framingRectInPreview = rect;
266     }
267     return framingRectInPreview;
268   }
269 
270 
271   /**
272    * Allows third party apps to specify the camera ID, rather than determine
273    * it automatically based on available cameras and their orientation.
274    *
275    * @param cameraId camera ID of the camera to use. A negative value means "no preference".
276    */
setManualCameraId(int cameraId)277   public synchronized void setManualCameraId(int cameraId) {
278     requestedCameraId = cameraId;
279   }
280 
281   /**
282    * Allows third party apps to specify the scanning rectangle dimensions, rather than determine
283    * them automatically based on screen resolution.
284    *
285    * @param width The width in pixels to scan.
286    * @param height The height in pixels to scan.
287    */
setManualFramingRect(int width, int height)288   public synchronized void setManualFramingRect(int width, int height) {
289     if (initialized) {
290       Point screenResolution = configManager.getScreenResolution();
291       if (width > screenResolution.x) {
292         width = screenResolution.x;
293       }
294       if (height > screenResolution.y) {
295         height = screenResolution.y;
296       }
297       int leftOffset = (screenResolution.x - width) / 2;
298       int topOffset = (screenResolution.y - height) / 2;
299       framingRect = new Rect(leftOffset, topOffset, leftOffset + width, topOffset + height);
300       Log.d(TAG, "Calculated manual framing rect: " + framingRect);
301       framingRectInPreview = null;
302     } else {
303       requestedFramingRectWidth = width;
304       requestedFramingRectHeight = height;
305     }
306   }
307 
308   /**
309    * A factory method to build the appropriate LuminanceSource object based on the format
310    * of the preview buffers, as described by Camera.Parameters.
311    *
312    * @param data A preview frame.
313    * @param width The width of the image.
314    * @param height The height of the image.
315    * @return A PlanarYUVLuminanceSource instance.
316    */
buildLuminanceSource(byte[] data, int width, int height)317   public PlanarYUVLuminanceSource buildLuminanceSource(byte[] data, int width, int height) {
318     Rect rect = getFramingRectInPreview();
319     if (rect == null) {
320       return null;
321     }
322     // Go ahead and assume it's YUV rather than die.
323     return new PlanarYUVLuminanceSource(data, width, height, rect.left, rect.top,
324                                         rect.width(), rect.height(), false);
325   }
326 
327 }
328