53c11ba8841cb187bfeae49583b4ffc5acf5473d
[zxing.git] / android / src / com / google / zxing / client / android / camera / 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.camera;
18
19 import android.content.Context;
20 import android.content.SharedPreferences;
21 import android.graphics.PixelFormat;
22 import android.graphics.Point;
23 import android.graphics.Rect;
24 import android.hardware.Camera;
25 import android.os.Build;
26 import android.os.Handler;
27 import android.preference.PreferenceManager;
28 import android.util.Log;
29 import android.view.SurfaceHolder;
30 import com.google.zxing.client.android.PlanarYUVLuminanceSource;
31 import com.google.zxing.client.android.PreferencesActivity;
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 public final class CameraManager {
43
44   private static final String TAG = CameraManager.class.getSimpleName();
45
46   private static final int MIN_FRAME_WIDTH = 240;
47   private static final int MIN_FRAME_HEIGHT = 240;
48   private static final int MAX_FRAME_WIDTH = 480;
49   private static final int MAX_FRAME_HEIGHT = 360;
50
51   private static CameraManager cameraManager;
52
53   static final int SDK_INT; // Later we can use Build.VERSION.SDK_INT
54   static {
55     int sdkInt;
56     try {
57       sdkInt = Integer.parseInt(Build.VERSION.SDK);
58     } catch (NumberFormatException nfe) {
59       // Just to be safe
60       sdkInt = 10000;
61     }
62     SDK_INT = sdkInt;
63   }
64
65   private final Context context;
66   private final CameraConfigurationManager configManager;
67   private Camera camera;
68   private Rect framingRect;
69   private Rect framingRectInPreview;
70   private boolean initialized;
71   private boolean previewing;
72   private final boolean useOneShotPreviewCallback;
73   /**
74    * Preview frames are delivered here, which we pass on to the registered handler. Make sure to
75    * clear the handler so it will only receive one message.
76    */
77   private final PreviewCallback previewCallback;
78   /** Autofocus callbacks arrive here, and are dispatched to the Handler which requested them. */
79   private final AutoFocusCallback autoFocusCallback;
80
81   /**
82    * Initializes this static object with the Context of the calling Activity.
83    *
84    * @param context The Activity which wants to use the camera.
85    */
86   public static void init(Context context) {
87     if (cameraManager == null) {
88       cameraManager = new CameraManager(context);
89     }
90   }
91
92   /**
93    * Gets the CameraManager singleton instance.
94    *
95    * @return A reference to the CameraManager singleton.
96    */
97   public static CameraManager get() {
98     return cameraManager;
99   }
100
101   private CameraManager(Context context) {
102
103     this.context = context;
104     this.configManager = new CameraConfigurationManager(context);
105
106     // Camera.setOneShotPreviewCallback() has a race condition in Cupcake, so we use the older
107     // Camera.setPreviewCallback() on 1.5 and earlier. For Donut and later, we need to use
108     // the more efficient one shot callback, as the older one can swamp the system and cause it
109     // to run out of memory. We can't use SDK_INT because it was introduced in the Donut SDK.
110     useOneShotPreviewCallback = SDK_INT > Build.VERSION_CODES.CUPCAKE;
111
112     previewCallback = new PreviewCallback(configManager, useOneShotPreviewCallback);
113     autoFocusCallback = new AutoFocusCallback();
114   }
115
116   /**
117    * Opens the camera driver and initializes the hardware parameters.
118    *
119    * @param holder The surface object which the camera will draw preview frames into.
120    * @throws IOException Indicates the camera driver failed to open.
121    */
122   public void openDriver(SurfaceHolder holder) throws IOException {
123     if (camera == null) {
124       camera = Camera.open();
125       if (camera == null) {
126         throw new IOException();
127       }
128       camera.setPreviewDisplay(holder);
129
130       if (!initialized) {
131         initialized = true;
132         configManager.initFromCameraParameters(camera);
133       }
134       configManager.setDesiredCameraParameters(camera);
135
136       SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
137       if (prefs.getBoolean(PreferencesActivity.KEY_FRONT_LIGHT, false)) {
138         FlashlightManager.enableFlashlight();
139       }
140     }
141   }
142
143   /**
144    * Closes the camera driver if still in use.
145    */
146   public void closeDriver() {
147     if (camera != null) {
148       FlashlightManager.disableFlashlight();
149       camera.release();
150       camera = null;
151     }
152   }
153
154   /**
155    * Asks the camera hardware to begin drawing preview frames to the screen.
156    */
157   public void startPreview() {
158     if (camera != null && !previewing) {
159       camera.startPreview();
160       previewing = true;
161     }
162   }
163
164   /**
165    * Tells the camera to stop drawing preview frames.
166    */
167   public void stopPreview() {
168     if (camera != null && previewing) {
169       if (!useOneShotPreviewCallback) {
170         camera.setPreviewCallback(null);
171       }
172       camera.stopPreview();
173       previewCallback.setHandler(null, 0);
174       autoFocusCallback.setHandler(null, 0);
175       previewing = false;
176     }
177   }
178
179   /**
180    * A single preview frame will be returned to the handler supplied. The data will arrive as byte[]
181    * in the message.obj field, with width and height encoded as message.arg1 and message.arg2,
182    * respectively.
183    *
184    * @param handler The handler to send the message to.
185    * @param message The what field of the message to be sent.
186    */
187   public void requestPreviewFrame(Handler handler, int message) {
188     if (camera != null && previewing) {
189       previewCallback.setHandler(handler, message);
190       if (useOneShotPreviewCallback) {
191         camera.setOneShotPreviewCallback(previewCallback);
192       } else {
193         camera.setPreviewCallback(previewCallback);
194       }
195     }
196   }
197
198   /**
199    * Asks the camera hardware to perform an autofocus.
200    *
201    * @param handler The Handler to notify when the autofocus completes.
202    * @param message The message to deliver.
203    */
204   public void requestAutoFocus(Handler handler, int message) {
205     if (camera != null && previewing) {
206       autoFocusCallback.setHandler(handler, message);
207       //Log.d(TAG, "Requesting auto-focus callback");
208       camera.autoFocus(autoFocusCallback);
209     }
210   }
211
212   /**
213    * Calculates the framing rect which the UI should draw to show the user where to place the
214    * barcode. This target helps with alignment as well as forces the user to hold the device
215    * far enough away to ensure the image will be in focus.
216    *
217    * @return The rectangle to draw on screen in window coordinates.
218    */
219   public Rect getFramingRect() {
220     Point screenResolution = configManager.getScreenResolution();
221     if (framingRect == null) {
222       if (camera == null) {
223         return null;
224       }
225       int width = screenResolution.x * 3 / 4;
226       if (width < MIN_FRAME_WIDTH) {
227         width = MIN_FRAME_WIDTH;
228       } else if (width > MAX_FRAME_WIDTH) {
229         width = MAX_FRAME_WIDTH;
230       }
231       int height = screenResolution.y * 3 / 4;
232       if (height < MIN_FRAME_HEIGHT) {
233         height = MIN_FRAME_HEIGHT;
234       } else if (height > MAX_FRAME_HEIGHT) {
235         height = MAX_FRAME_HEIGHT;
236       }
237       int leftOffset = (screenResolution.x - width) / 2;
238       int topOffset = (screenResolution.y - height) / 2;
239       framingRect = new Rect(leftOffset, topOffset, leftOffset + width, topOffset + height);
240       Log.d(TAG, "Calculated framing rect: " + framingRect);
241     }
242     return framingRect;
243   }
244
245   /**
246    * Like {@link #getFramingRect} but coordinates are in terms of the preview frame,
247    * not UI / screen.
248    */
249   public Rect getFramingRectInPreview() {
250     if (framingRectInPreview == null) {
251       Rect rect = new Rect(getFramingRect());
252       Point cameraResolution = configManager.getCameraResolution();
253       Point screenResolution = configManager.getScreenResolution();
254       rect.left = rect.left * cameraResolution.x / screenResolution.x;
255       rect.right = rect.right * cameraResolution.x / screenResolution.x;
256       rect.top = rect.top * cameraResolution.y / screenResolution.y;
257       rect.bottom = rect.bottom * cameraResolution.y / screenResolution.y;
258       framingRectInPreview = rect;
259     }
260     return framingRectInPreview;
261   }
262
263   /**
264    * Converts the result points from still resolution coordinates to screen coordinates.
265    *
266    * @param points The points returned by the Reader subclass through Result.getResultPoints().
267    * @return An array of Points scaled to the size of the framing rect and offset appropriately
268    *         so they can be drawn in screen coordinates.
269    */
270   /*
271   public Point[] convertResultPoints(ResultPoint[] points) {
272     Rect frame = getFramingRectInPreview();
273     int count = points.length;
274     Point[] output = new Point[count];
275     for (int x = 0; x < count; x++) {
276       output[x] = new Point();
277       output[x].x = frame.left + (int) (points[x].getX() + 0.5f);
278       output[x].y = frame.top + (int) (points[x].getY() + 0.5f);
279     }
280     return output;
281   }
282    */
283
284   /**
285    * A factory method to build the appropriate LuminanceSource object based on the format
286    * of the preview buffers, as described by Camera.Parameters.
287    *
288    * @param data A preview frame.
289    * @param width The width of the image.
290    * @param height The height of the image.
291    * @return A PlanarYUVLuminanceSource instance.
292    */
293   public PlanarYUVLuminanceSource buildLuminanceSource(byte[] data, int width, int height) {
294     Rect rect = getFramingRectInPreview();
295     int previewFormat = configManager.getPreviewFormat();
296     String previewFormatString = configManager.getPreviewFormatString();
297     switch (previewFormat) {
298       // This is the standard Android format which all devices are REQUIRED to support.
299       // In theory, it's the only one we should ever care about.
300       case PixelFormat.YCbCr_420_SP:
301       // This format has never been seen in the wild, but is compatible as we only care
302       // about the Y channel, so allow it.
303       case PixelFormat.YCbCr_422_SP:
304         return new PlanarYUVLuminanceSource(data, width, height, rect.left, rect.top,
305             rect.width(), rect.height());
306       default:
307         // The Samsung Moment incorrectly uses this variant instead of the 'sp' version.
308         // Fortunately, it too has all the Y data up front, so we can read it.
309         if ("yuv420p".equals(previewFormatString)) {
310           return new PlanarYUVLuminanceSource(data, width, height, rect.left, rect.top,
311             rect.width(), rect.height());
312         }
313     }
314     throw new IllegalArgumentException("Unsupported picture format: " +
315         previewFormat + '/' + previewFormatString);
316   }
317
318 }