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
17package com.google.zxing.client.android.camera;
18
19import com.google.zxing.client.android.PlanarYUVLuminanceSource;
20
21import android.content.Context;
22import android.content.SharedPreferences;
23import android.graphics.Point;
24import android.graphics.Rect;
25import android.hardware.Camera;
26import android.os.Handler;
27import android.preference.PreferenceManager;
28import android.util.Log;
29import android.view.SurfaceHolder;
30
31import java.io.IOException;
32
33/**
34 * This object wraps the Camera service object and expects to be the only one talking to it. The
35 * implementation encapsulates the steps needed to take preview-sized images, which are used for
36 * both preview and decoding.
37 *
38 * @author dswitkin@google.com (Daniel Switkin)
39 */
40public 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 = 600;
47  private static final int MAX_FRAME_HEIGHT = 400;
48
49  private final Context context;
50  private final CameraConfigurationManager configManager;
51  private Camera camera;
52  private Rect framingRect;
53  private Rect framingRectInPreview;
54  private boolean initialized;
55  private boolean previewing;
56  private boolean reverseImage;
57  private int requestedFramingRectWidth;
58  private int requestedFramingRectHeight;
59  /**
60   * Preview frames are delivered here, which we pass on to the registered handler. Make sure to
61   * clear the handler so it will only receive one message.
62   */
63  private final PreviewCallback previewCallback;
64  /** Autofocus callbacks arrive here, and are dispatched to the Handler which requested them. */
65  private final AutoFocusCallback autoFocusCallback;
66
67  public CameraManager(Context context) {
68    this.context = context;
69    this.configManager = new CameraConfigurationManager(context);
70    previewCallback = new PreviewCallback(configManager);
71    autoFocusCallback = new AutoFocusCallback();
72  }
73
74  /**
75   * Opens the camera driver and initializes the hardware parameters.
76   *
77   * @param holder The surface object which the camera will draw preview frames into.
78   * @throws IOException Indicates the camera driver failed to open.
79   */
80  public void openDriver(SurfaceHolder holder) throws IOException {
81    Camera theCamera = camera;
82    if (theCamera == null) {
83      theCamera = Camera.open();
84      if (theCamera == null) {
85        throw new IOException();
86      }
87      camera = theCamera;
88    }
89    theCamera.setPreviewDisplay(holder);
90
91    if (!initialized) {
92      initialized = true;
93      configManager.initFromCameraParameters(theCamera);
94      if (requestedFramingRectWidth > 0 && requestedFramingRectHeight > 0) {
95        setManualFramingRect(requestedFramingRectWidth, requestedFramingRectHeight);
96        requestedFramingRectWidth = 0;
97        requestedFramingRectHeight = 0;
98      }
99    }
100    configManager.setDesiredCameraParameters(theCamera);
101
102    SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
103    reverseImage = false;
104  }
105
106  /**
107   * Closes the camera driver if still in use.
108   */
109  public void closeDriver() {
110    if (camera != null) {
111      camera.release();
112      camera = null;
113      // Make sure to clear these each time we close the camera, so that any scanning rect
114      // requested by intent is forgotten.
115      framingRect = null;
116      framingRectInPreview = null;
117    }
118  }
119
120  /**
121   * Asks the camera hardware to begin drawing preview frames to the screen.
122   */
123  public void startPreview() {
124    Camera theCamera = camera;
125    if (theCamera != null && !previewing) {
126      theCamera.startPreview();
127      previewing = true;
128    }
129  }
130
131  /**
132   * Tells the camera to stop drawing preview frames.
133   */
134  public void stopPreview() {
135    if (camera != null && previewing) {
136      camera.stopPreview();
137      previewCallback.setHandler(null, 0);
138      autoFocusCallback.setHandler(null, 0);
139      previewing = false;
140    }
141  }
142
143  /**
144   * A single preview frame will be returned to the handler supplied. The data will arrive as byte[]
145   * in the message.obj field, with width and height encoded as message.arg1 and message.arg2,
146   * respectively.
147   *
148   * @param handler The handler to send the message to.
149   * @param message The what field of the message to be sent.
150   */
151  public void requestPreviewFrame(Handler handler, int message) {
152    Camera theCamera = camera;
153    if (theCamera != null && previewing) {
154      previewCallback.setHandler(handler, message);
155      theCamera.setOneShotPreviewCallback(previewCallback);
156    }
157  }
158
159  /**
160   * Asks the camera hardware to perform an autofocus.
161   *
162   * @param handler The Handler to notify when the autofocus completes.
163   * @param message The message to deliver.
164   */
165  public void requestAutoFocus(Handler handler, int message) {
166    if (camera != null && previewing) {
167      autoFocusCallback.setHandler(handler, message);
168      camera.autoFocus(autoFocusCallback);
169    }
170  }
171
172  /**
173   * Calculates the framing rect which the UI should draw to show the user where to place the
174   * barcode. This target helps with alignment as well as forces the user to hold the device
175   * far enough away to ensure the image will be in focus.
176   *
177   * @return The rectangle to draw on screen in window coordinates.
178   */
179  public Rect getFramingRect() {
180    if (framingRect == null) {
181      if (camera == null) {
182        return null;
183      }
184      Point screenResolution = configManager.getScreenResolution();
185      int width = screenResolution.x * 3 / 4;
186      if (width < MIN_FRAME_WIDTH) {
187        width = MIN_FRAME_WIDTH;
188      } else if (width > MAX_FRAME_WIDTH) {
189        width = MAX_FRAME_WIDTH;
190      }
191      int height = screenResolution.y * 3 / 4;
192      if (height < MIN_FRAME_HEIGHT) {
193        height = MIN_FRAME_HEIGHT;
194      } else if (height > MAX_FRAME_HEIGHT) {
195        height = MAX_FRAME_HEIGHT;
196      }
197      int leftOffset = (screenResolution.x - width) / 2;
198      int topOffset = (screenResolution.y - height) / 2;
199      framingRect = new Rect(leftOffset, topOffset, leftOffset + width, topOffset + height);
200      Log.d(TAG, "Calculated framing rect: " + framingRect);
201    }
202    return framingRect;
203  }
204
205  /**
206   * Like {@link #getFramingRect} but coordinates are in terms of the preview frame,
207   * not UI / screen.
208   */
209  public Rect getFramingRectInPreview() {
210    if (framingRectInPreview == null) {
211      Rect framingRect = getFramingRect();
212      if (framingRect == null) {
213        return null;
214      }
215      Rect rect = new Rect(framingRect);
216      Point cameraResolution = configManager.getCameraResolution();
217      if (cameraResolution == null) {
218        return framingRect;
219      }
220      Point screenResolution = configManager.getScreenResolution();
221      rect.left = rect.left * cameraResolution.x / screenResolution.x;
222      rect.right = rect.right * cameraResolution.x / screenResolution.x;
223      rect.top = rect.top * cameraResolution.y / screenResolution.y;
224      rect.bottom = rect.bottom * cameraResolution.y / screenResolution.y;
225      framingRectInPreview = rect;
226    }
227    return framingRectInPreview;
228  }
229
230  /**
231   * Allows third party apps to specify the scanning rectangle dimensions, rather than determine
232   * them automatically based on screen resolution.
233   *
234   * @param width The width in pixels to scan.
235   * @param height The height in pixels to scan.
236   */
237  public void setManualFramingRect(int width, int height) {
238    if (initialized) {
239      Point screenResolution = configManager.getScreenResolution();
240      if (width > screenResolution.x) {
241        width = screenResolution.x;
242      }
243      if (height > screenResolution.y) {
244        height = screenResolution.y;
245      }
246      int leftOffset = (screenResolution.x - width) / 2;
247      int topOffset = (screenResolution.y - height) / 2;
248      framingRect = new Rect(leftOffset, topOffset, leftOffset + width, topOffset + height);
249      Log.d(TAG, "Calculated manual framing rect: " + framingRect);
250      framingRectInPreview = null;
251    } else {
252      requestedFramingRectWidth = width;
253      requestedFramingRectHeight = height;
254    }
255  }
256
257  /**
258   * A factory method to build the appropriate LuminanceSource object based on the format
259   * of the preview buffers, as described by Camera.Parameters.
260   *
261   * @param data A preview frame.
262   * @param width The width of the image.
263   * @param height The height of the image.
264   * @return A PlanarYUVLuminanceSource instance.
265   */
266  public PlanarYUVLuminanceSource buildLuminanceSource(byte[] data, int width, int height) {
267    Rect rect = getFramingRectInPreview();
268    if (rect == null) {
269      return null;
270    }
271    // Go ahead and assume it's YUV rather than die.
272    return new PlanarYUVLuminanceSource(data, width, height, rect.left, rect.top,
273                                        rect.width(), rect.height(), reverseImage);
274  }
275
276}
277