summaryrefslogtreecommitdiff
path: root/src/com/google/zxing/client/android/CameraManager.java
blob: 6a01783d8cb07fccbf6a11e2ce0f30018c44aa7f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
/*
 * Copyright (C) 2008 ZXing authors
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.google.zxing.client.android;

import com.google.zxing.ResultPoint;

import android.content.Context;
import android.graphics.Point;
import android.graphics.Rect;
import android.hardware.Camera;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.view.Display;
import android.view.SurfaceHolder;
import android.view.WindowManager;

import java.io.IOException;

/**
 * This object wraps the Camera service object and expects to be the only one talking to it. The
 * implementation encapsulates the steps needed to take preview-sized images, which are used for
 * both preview and decoding.
 */
final class CameraManager {

  private static final String TAG = "CameraManager";

  private static CameraManager mCameraManager;
  private Camera mCamera;
  private final Context mContext;
  private Point mScreenResolution;
  private Rect mFramingRect;
  private Handler mPreviewHandler;
  private int mPreviewMessage;
  private Handler mAutoFocusHandler;
  private int mAutoFocusMessage;
  private boolean mInitialized;
  private boolean mPreviewing;

  public static void init(Context context) {
    if (mCameraManager == null) {
      mCameraManager = new CameraManager(context);
    }
  }

  public static CameraManager get() {
    return mCameraManager;
  }

  private CameraManager(Context context) {
    mContext = context;
    mCamera = null;
    mInitialized = false;
    mPreviewing = false;
  }

  public void openDriver(SurfaceHolder holder) throws IOException {
    if (mCamera == null) {
      mCamera = Camera.open();
      mCamera.setPreviewDisplay(holder);

      if (!mInitialized) {
        mInitialized = true;
        getScreenResolution();
      }

      setCameraParameters();
    }
  }

  public void closeDriver() {
    if (mCamera != null) {
      mCamera.release();
      mCamera = null;
    }
  }

  public void startPreview() {
    if (mCamera != null && !mPreviewing) {
      mCamera.startPreview();
      mPreviewing = true;
    }
  }

  public void stopPreview() {
    if (mCamera != null && mPreviewing) {
      mCamera.setPreviewCallback(null);
      mCamera.stopPreview();
      mPreviewHandler = null;
      mAutoFocusHandler = null;
      mPreviewing = false;
    }
  }

  /**
   * A single preview frame will be returned to the handler supplied. The data will arrive as byte[]
   * in the message.obj field, with width and height encoded as message.arg1 and message.arg2,
   * respectively.
   *
   * @param handler The handler to send the message to.
   * @param message The what field of the message to be sent.
   */
  public void requestPreviewFrame(Handler handler, int message) {
    if (mCamera != null && mPreviewing) {
      mPreviewHandler = handler;
      mPreviewMessage = message;
      mCamera.setOneShotPreviewCallback(previewCallback);
    }
  }

  public void requestAutoFocus(Handler handler, int message) {
    if (mCamera != null && mPreviewing) {
      mAutoFocusHandler = handler;
      mAutoFocusMessage = message;
      mCamera.autoFocus(autoFocusCallback);
    }
  }

  /**
   * Calculates the framing rect which the UI should draw to show the user where to place the
   * barcode. The actual captured image should be a bit larger than indicated because they might
   * frame the shot too tightly. This target helps with alignment as well as forces the user to hold
   * the device far enough away to ensure the image will be in focus.
   *
   * @return The rectangle to draw on screen in window coordinates.
   */
  public Rect getFramingRect() {
    if (mFramingRect == null) {
      int size = (mScreenResolution.x < mScreenResolution.y ? mScreenResolution.x :
          mScreenResolution.y) * 3 / 4;
      int leftOffset = (mScreenResolution.x - size) / 2;
      int topOffset = (mScreenResolution.y - size) / 2;
      mFramingRect = new Rect(leftOffset, topOffset, leftOffset + size, topOffset + size);
      Log.v(TAG, "Calculated framing rect: " + mFramingRect);
    }
    return mFramingRect;
  }

  /**
   * Converts the result points from still resolution coordinates to screen coordinates.
   *
   * @param points The points returned by the Reader subclass through Result.getResultPoints().
   * @return An array of Points scaled to the size of the framing rect and offset appropriately
   *         so they can be drawn in screen coordinates.
   */
  public Point[] convertResultPoints(ResultPoint[] points) {
    Rect frame = getFramingRect();
    int count = points.length;
    Point[] output = new Point[count];
    for (int x = 0; x < count; x++) {
      output[x] = new Point();
      output[x].x = frame.left + (int) (points[x].getX() + 0.5f);
      output[x].y = frame.top + (int) (points[x].getY() + 0.5f);
    }
    return output;
  }

  /**
   * Preview frames are delivered here, which we pass on to the registered handler. Make sure to
   * clear the handler so it will only receive one message.
   */
  private final Camera.PreviewCallback previewCallback = new Camera.PreviewCallback() {
    public void onPreviewFrame(byte[] data, Camera camera) {
      if (mPreviewHandler != null) {
        Message message = mPreviewHandler.obtainMessage(mPreviewMessage, mScreenResolution.x,
            mScreenResolution.y, data);
        message.sendToTarget();
        mPreviewHandler = null;
      }
    }
  };

  private final Camera.AutoFocusCallback autoFocusCallback = new Camera.AutoFocusCallback() {
    public void onAutoFocus(boolean success, Camera camera) {
      if (mAutoFocusHandler != null) {
        Message message = mAutoFocusHandler.obtainMessage(mAutoFocusMessage, success);
        // Simulate continuous autofocus by sending a focus request every 1.5 seconds.
        mAutoFocusHandler.sendMessageDelayed(message, 1500L);
        mAutoFocusHandler = null;
      }
    }
  };

  /**
   * Sets the camera up to take preview images which are used for both preview and decoding. We're
   * counting on the default YUV420 semi-planar data. If that changes in the future, we'll need to
   * specify it explicitly with setPreviewFormat().
   */
  private void setCameraParameters() {
    Camera.Parameters parameters = mCamera.getParameters();
    parameters.setPreviewSize(mScreenResolution.x, mScreenResolution.y);

    // FIXME: This is a hack to turn the flash off on the Samsung Galaxy. In the future there
    // will hopefully be a standard setting like "off" that all devices will honor.
    parameters.set("flash-mode", "2");
    mCamera.setParameters(parameters);
    Log.v(TAG, "Setting params for preview: width " + mScreenResolution.x + " height " +
        mScreenResolution.y);
  }

  private Point getScreenResolution() {
    if (mScreenResolution == null) {
      WindowManager manager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
      Display display = manager.getDefaultDisplay();
      mScreenResolution = new Point(display.getWidth(), display.getHeight());
    }
    return mScreenResolution;
  }

}