3eaaded29685358fd2ac872bbbf7323edf87339c
[zxing.git] / android / src / com / google / zxing / client / android / CameraManager.java
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;
18
19 import com.google.zxing.ResultPoint;
20
21 import android.content.Context;
22 import android.graphics.PixelFormat;
23 import android.graphics.Point;
24 import android.graphics.Rect;
25 import android.hardware.Camera;
26 import android.os.Handler;
27 import android.os.Message;
28 import android.util.Log;
29 import android.view.Display;
30 import android.view.SurfaceHolder;
31 import android.view.WindowManager;
32
33 import java.io.IOException;
34
35 /**
36  * This object wraps the Camera service object and expects to be the only one talking to it. The
37  * implementation encapsulates the steps needed to take preview-sized images, which are used for
38  * both preview and decoding.
39  *
40  * @author dswitkin@google.com (Daniel Switkin)
41  */
42 final class CameraManager {
43   private static final String TAG = "CameraManager";
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 = 480;
47   private static final int MAX_FRAME_HEIGHT = 360;
48
49   private static CameraManager cameraManager;
50   private Camera camera;
51   private final Context context;
52   private Point screenResolution;
53   private Point cameraResolution;
54   private Rect framingRect;
55   private Handler previewHandler;
56   private int previewMessage;
57   private Handler autoFocusHandler;
58   private int autoFocusMessage;
59   private boolean initialized;
60   private boolean previewing;
61   private int previewFormat;
62   private String previewFormatString;
63
64   /**
65    * Preview frames are delivered here, which we pass on to the registered handler. Make sure to
66    * clear the handler so it will only receive one message.
67    */
68   private final Camera.PreviewCallback previewCallback = new Camera.PreviewCallback() {
69     public void onPreviewFrame(byte[] data, Camera camera) {
70       camera.setPreviewCallback(null);
71       if (previewHandler != null) {
72         Message message = previewHandler.obtainMessage(previewMessage, cameraResolution.x,
73             cameraResolution.y, data);
74         message.sendToTarget();
75         previewHandler = null;
76       }
77     }
78   };
79
80   /**
81    * Autofocus callbacks arrive here, and are dispatched to the Handler which requested them.
82    */
83   private final Camera.AutoFocusCallback autoFocusCallback = new Camera.AutoFocusCallback() {
84     public void onAutoFocus(boolean success, Camera camera) {
85       if (autoFocusHandler != null) {
86         Message message = autoFocusHandler.obtainMessage(autoFocusMessage, success);
87         // Simulate continuous autofocus by sending a focus request every 1.5 seconds.
88         autoFocusHandler.sendMessageDelayed(message, 1500L);
89         autoFocusHandler = null;
90       }
91     }
92   };
93
94   /**
95    * Initializes this static object with the Context of the calling Activity.
96    *
97    * @param context The Activity which wants to use the camera.
98    */
99   public static void init(Context context) {
100     if (cameraManager == null) {
101       cameraManager = new CameraManager(context);
102     }
103   }
104
105   /**
106    * Gets the CameraManager singleton instance.
107    *
108    * @return A reference to the CameraManager singleton.
109    */
110   public static CameraManager get() {
111     return cameraManager;
112   }
113
114   private CameraManager(Context context) {
115     this.context = context;
116     camera = null;
117     initialized = false;
118     previewing = false;
119   }
120
121   /**
122    * Opens the camera driver and initializes the hardware parameters.
123    *
124    * @param holder The surface object which the camera will draw preview frames into.
125    * @throws IOException Indicates the camera driver failed to open.
126    */
127   public void openDriver(SurfaceHolder holder) throws IOException {
128     if (camera == null) {
129       camera = Camera.open();
130       camera.setPreviewDisplay(holder);
131
132       if (!initialized) {
133         initialized = true;
134         getScreenResolution();
135       }
136
137       setCameraParameters();
138     }
139   }
140
141   /**
142    * Closes the camera driver if still in use.
143    */
144   public void closeDriver() {
145     if (camera != null) {
146       camera.release();
147       camera = null;
148     }
149   }
150
151   /**
152    * Asks the camera hardware to begin drawing preview frames to the screen.
153    */
154   public void startPreview() {
155     if (camera != null && !previewing) {
156       camera.startPreview();
157       previewing = true;
158     }
159   }
160
161   /**
162    * Tells the camera to stop drawing preview frames.
163    */
164   public void stopPreview() {
165     if (camera != null && previewing) {
166       camera.setPreviewCallback(null);
167       camera.stopPreview();
168       previewHandler = null;
169       autoFocusHandler = null;
170       previewing = false;
171     }
172   }
173
174   /**
175    * A single preview frame will be returned to the handler supplied. The data will arrive as byte[]
176    * in the message.obj field, with width and height encoded as message.arg1 and message.arg2,
177    * respectively.
178    *
179    * @param handler The handler to send the message to.
180    * @param message The what field of the message to be sent.
181    */
182   public void requestPreviewFrame(Handler handler, int message) {
183     if (camera != null && previewing) {
184       previewHandler = handler;
185       previewMessage = message;
186       camera.setPreviewCallback(previewCallback);
187     }
188   }
189
190   /**
191    * Asks the camera hardware to perform an autofocus.
192    *
193    * @param handler The Handler to notify when the autofocus completes.
194    * @param message The message to deliver.
195    */
196   public void requestAutoFocus(Handler handler, int message) {
197     if (camera != null && previewing) {
198       autoFocusHandler = handler;
199       autoFocusMessage = message;
200       camera.autoFocus(autoFocusCallback);
201     }
202   }
203
204   /**
205    * Calculates the framing rect which the UI should draw to show the user where to place the
206    * barcode. This target helps with alignment as well as forces the user to hold the device
207    * far enough away to ensure the image will be in focus.
208    *
209    * @return The rectangle to draw on screen in window coordinates.
210    */
211   public Rect getFramingRect() {
212     if (framingRect == null) {
213       int width = cameraResolution.x * 3 / 4;
214       if (width < MIN_FRAME_WIDTH) {
215         width = MIN_FRAME_WIDTH;
216       } else if (width > MAX_FRAME_WIDTH) {
217         width = MAX_FRAME_WIDTH;
218       }
219       int height = cameraResolution.y * 3 / 4;
220       if (height < MIN_FRAME_HEIGHT) {
221         height = MIN_FRAME_HEIGHT;
222       } else if (height > MAX_FRAME_HEIGHT) {
223         height = MAX_FRAME_HEIGHT;
224       }
225       int leftOffset = (cameraResolution.x - width) / 2;
226       int topOffset = (cameraResolution.y - height) / 2;
227       framingRect = new Rect(leftOffset, topOffset, leftOffset + width, topOffset + height);
228       Log.v(TAG, "Calculated framing rect: " + framingRect);
229     }
230     return framingRect;
231   }
232
233   /**
234    * Converts the result points from still resolution coordinates to screen coordinates.
235    *
236    * @param points The points returned by the Reader subclass through Result.getResultPoints().
237    * @return An array of Points scaled to the size of the framing rect and offset appropriately
238    *         so they can be drawn in screen coordinates.
239    */
240   public Point[] convertResultPoints(ResultPoint[] points) {
241     Rect frame = getFramingRect();
242     int count = points.length;
243     Point[] output = new Point[count];
244     for (int x = 0; x < count; x++) {
245       output[x] = new Point();
246       output[x].x = frame.left + (int) (points[x].getX() + 0.5f);
247       output[x].y = frame.top + (int) (points[x].getY() + 0.5f);
248     }
249     return output;
250   }
251
252   /**
253    * A factory method to build the appropriate LuminanceSource object based on the format
254    * of the preview buffers, as described by Camera.Parameters.
255    *
256    * @param data A preview frame.
257    * @param width The width of the image.
258    * @param height The height of the image.
259    * @return A BaseLuminanceSource subclass.
260    */
261   public BaseLuminanceSource buildLuminanceSource(byte[] data, int width, int height) {
262     Rect rect = getFramingRect();
263     switch (previewFormat) {
264       case PixelFormat.YCbCr_420_SP:
265       case PixelFormat.YCbCr_422_SP:
266         return new PlanarYUVLuminanceSource(data, width, height, rect.left, rect.top,
267             rect.width(), rect.height());
268       default:
269         // There's no PixelFormat constant for this buffer format yet.
270         if (previewFormatString.equals("yuv422i-yuyv")) {
271           return new InterleavedYUV422LuminanceSource(data, width, height, rect.left, rect.top,
272               rect.width(), rect.height());
273         }
274         break;
275     }
276     return null;
277   }
278
279   /**
280    * Sets the camera up to take preview images which are used for both preview and decoding.
281    * We detect the preview format here so that buildLuminanceSource() can build an appropriate
282    * LuminanceSource subclass. In the future we may want to force YUV420SP as it's the smallest,
283    * and the planar Y can be used for barcode scanning without a copy in some cases.
284    */
285   private void setCameraParameters() {
286     Camera.Parameters parameters = camera.getParameters();
287     Camera.Size size = parameters.getPreviewSize();
288     Log.v(TAG, "Default preview size: " + size.width + ", " + size.height);
289     previewFormat = parameters.getPreviewFormat();
290     previewFormatString = parameters.get("preview-format");
291     Log.v(TAG, "Default preview format: " + previewFormat);
292
293     // Ensure that the camera resolution is a multiple of 8, as the screen may not be.
294     // TODO: A better solution would be to request the supported preview resolutions
295     // and pick the best match, but this parameter is not standardized in Cupcake.
296     cameraResolution = new Point();
297     cameraResolution.x = (screenResolution.x >> 3) << 3;
298     cameraResolution.y = (screenResolution.y >> 3) << 3;
299     Log.v(TAG, "Setting preview size: " + cameraResolution.x + ", " + cameraResolution.y);
300     parameters.setPreviewSize(cameraResolution.x, cameraResolution.y);
301
302     // FIXME: This is a hack to turn the flash off on the Samsung Galaxy.
303     parameters.set("flash-value", 2);
304
305     // This is the standard setting to turn the flash off that all devices should honor.
306     parameters.set("flash-mode", "off");
307
308     camera.setParameters(parameters);
309   }
310
311   private Point getScreenResolution() {
312     if (screenResolution == null) {
313       WindowManager manager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
314       Display display = manager.getDefaultDisplay();
315       screenResolution = new Point(display.getWidth(), display.getHeight());
316     }
317     return screenResolution;
318   }
319
320 }