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