1/*
2 * Copyright (C) 2008 ZXing authors
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5 * use this file except in compliance with the License. You may obtain a copy of
6 * 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, WITHOUT
12 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13 * License for the specific language governing permissions and limitations under
14 * the License.
15 */
16
17package com.google.zxing.client.android;
18
19import com.google.zxing.BarcodeFormat;
20import com.google.zxing.Result;
21import com.google.zxing.ResultPoint;
22import com.google.zxing.client.android.camera.CameraManager;
23
24import android.app.Activity;
25import android.app.AlertDialog;
26import android.content.Intent;
27import android.graphics.Bitmap;
28import android.graphics.Canvas;
29import android.graphics.Paint;
30import android.graphics.Rect;
31import android.os.Bundle;
32import android.os.Handler;
33import android.util.Log;
34import android.view.KeyEvent;
35import android.view.SurfaceHolder;
36import android.view.SurfaceView;
37import android.view.View;
38import android.view.Window;
39import android.view.WindowManager;
40import android.widget.TextView;
41
42import java.io.IOException;
43
44/**
45 * This activity opens the camera and does the actual scanning on a background
46 * thread. It draws a viewfinder to help the user place the barcode correctly,
47 * shows feedback as the image processing is happening, and then overlays the
48 * results when a scan is successful.
49 *
50 * @author dswitkin@google.com (Daniel Switkin)
51 * @author Sean Owen
52 */
53public final class CaptureActivity extends Activity implements SurfaceHolder.Callback {
54
55  private static final String LOG_TAG = CaptureActivity.class.getCanonicalName();
56
57  private CameraManager cameraManager;
58  private CaptureActivityHandler handler;
59  private ViewfinderView viewfinderView;
60  private TextView statusView;
61  private boolean hasSurface;
62  private String characterSet;
63  private InactivityTimer inactivityTimer;
64
65  ViewfinderView getViewfinderView() {
66    return viewfinderView;
67  }
68
69  public Handler getHandler() {
70    return handler;
71  }
72
73  CameraManager getCameraManager() {
74    return cameraManager;
75  }
76
77  @Override
78  public void onCreate(Bundle icicle) {
79    super.onCreate(icicle);
80
81    Window window = getWindow();
82    window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
83    setContentView(R.layout.capture);
84
85    statusView = (TextView) findViewById(R.id.status_view);
86    handler = null;
87    hasSurface = false;
88    inactivityTimer = new InactivityTimer(this);
89  }
90
91  @Override
92  protected void onResume() {
93    super.onResume();
94
95    // CameraManager must be initialized here, not in onCreate(). This is
96    // necessary because we don't want to open the camera driver and measure the
97    // screen size if we're going to show the help on first launch. That led to
98    // bugs where the scanning rectangle was the wrong size and partially off
99    // screen.
100    cameraManager = new CameraManager(getApplication());
101    viewfinderView = (ViewfinderView) findViewById(R.id.viewfinder_view);
102    viewfinderView.setCameraManager(cameraManager);
103
104    resetStatusView();
105
106    SurfaceView surfaceView = (SurfaceView) findViewById(R.id.preview_view);
107    SurfaceHolder surfaceHolder = surfaceView.getHolder();
108    if (hasSurface) {
109      // The activity was paused but not stopped, so the surface still exists.
110      // Therefore
111      // surfaceCreated() won't be called, so init the camera here.
112      initCamera(surfaceHolder);
113    } else {
114      // Install the callback and wait for surfaceCreated() to init the camera.
115      surfaceHolder.addCallback(this);
116      surfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
117    }
118
119    Intent intent = getIntent();
120    String action = intent == null ? null : intent.getAction();
121    String dataString = intent == null ? null : intent.getDataString();
122    if (intent != null && action != null) {
123      if (action.equals(Intents.Scan.ACTION)) {
124        if (intent.hasExtra(Intents.Scan.WIDTH) && intent.hasExtra(Intents.Scan.HEIGHT)) {
125          int width = intent.getIntExtra(Intents.Scan.WIDTH, 0);
126          int height = intent.getIntExtra(Intents.Scan.HEIGHT, 0);
127          if (width > 0 && height > 0) {
128            cameraManager.setManualFramingRect(width, height);
129          }
130        }
131      }
132      characterSet = intent.getStringExtra(Intents.Scan.CHARACTER_SET);
133    } else {
134      finish();
135    }
136
137    inactivityTimer.onResume();
138  }
139
140  @Override
141  protected void onPause() {
142    if (handler != null) {
143      handler.quitSynchronously();
144      handler = null;
145    }
146    inactivityTimer.onPause();
147    cameraManager.closeDriver();
148    if (!hasSurface) {
149      SurfaceView surfaceView = (SurfaceView) findViewById(R.id.preview_view);
150      SurfaceHolder surfaceHolder = surfaceView.getHolder();
151      surfaceHolder.removeCallback(this);
152    }
153    super.onPause();
154  }
155
156  @Override
157  protected void onDestroy() {
158    inactivityTimer.shutdown();
159    super.onDestroy();
160  }
161
162  @Override
163  public boolean onKeyDown(int keyCode, KeyEvent event) {
164    if (keyCode == KeyEvent.KEYCODE_BACK) {
165      setResult(RESULT_CANCELED);
166      finish();
167      return true;
168    } else if (keyCode == KeyEvent.KEYCODE_FOCUS || keyCode == KeyEvent.KEYCODE_CAMERA) {
169      // Handle these events so they don't launch the Camera app
170      return true;
171    }
172    return super.onKeyDown(keyCode, event);
173  }
174
175  public void surfaceCreated(SurfaceHolder holder) {
176    if (holder == null) {
177      Log.e(LOG_TAG, "*** WARNING *** surfaceCreated() gave us a null surface!");
178    }
179    if (!hasSurface) {
180      hasSurface = true;
181      initCamera(holder);
182    }
183  }
184
185  public void surfaceDestroyed(SurfaceHolder holder) {
186    hasSurface = false;
187  }
188
189  public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
190
191  }
192
193  /**
194   * A valid barcode has been found, so give an indication of success and show
195   * the results.
196   *
197   * @param rawResult The contents of the barcode.
198   * @param barcode A greyscale bitmap of the camera data which was decoded.
199   */
200  public void handleDecode(Result rawResult, Bitmap barcode) {
201    inactivityTimer.onActivity();
202
203    if (barcode == null) {
204      Log.e(LOG_TAG, "Barcode not recognized");
205      setResult(RESULT_CANCELED, null);
206    } else {
207      drawResultPoints(barcode, rawResult);
208      Log.d(LOG_TAG, "Barcode is: " + rawResult.getText());
209      Intent result = new Intent();
210      result.putExtra("SCAN_RESULT", rawResult.getText());
211      setResult(RESULT_OK, result);
212    }
213    finish();
214  }
215
216  /**
217   * Superimpose a line for 1D or dots for 2D to highlight the key features of
218   * the barcode.
219   *
220   * @param barcode A bitmap of the captured image.
221   * @param rawResult The decoded results which contains the points to draw.
222   */
223  private void drawResultPoints(Bitmap barcode, Result rawResult) {
224    ResultPoint[] points = rawResult.getResultPoints();
225    if (points != null && points.length > 0) {
226      Canvas canvas = new Canvas(barcode);
227      Paint paint = new Paint();
228      paint.setColor(getResources().getColor(R.color.result_image_border));
229      paint.setStrokeWidth(3.0f);
230      paint.setStyle(Paint.Style.STROKE);
231      Rect border = new Rect(2, 2, barcode.getWidth() - 2, barcode.getHeight() - 2);
232      canvas.drawRect(border, paint);
233
234      paint.setColor(getResources().getColor(R.color.result_points));
235      if (points.length == 2) {
236        paint.setStrokeWidth(4.0f);
237        drawLine(canvas, paint, points[0], points[1]);
238      } else if (points.length == 4 && (rawResult.getBarcodeFormat() == BarcodeFormat.UPC_A
239          || rawResult.getBarcodeFormat() == BarcodeFormat.EAN_13)) {
240        // Hacky special case -- draw two lines, for the barcode and metadata
241        drawLine(canvas, paint, points[0], points[1]);
242        drawLine(canvas, paint, points[2], points[3]);
243      } else {
244        paint.setStrokeWidth(10.0f);
245        for (ResultPoint point : points) {
246          canvas.drawPoint(point.getX(), point.getY(), paint);
247        }
248      }
249    }
250  }
251
252  private static void drawLine(Canvas canvas, Paint paint, ResultPoint a, ResultPoint b) {
253    canvas.drawLine(a.getX(), a.getY(), b.getX(), b.getY(), paint);
254  }
255
256  private void initCamera(SurfaceHolder surfaceHolder) {
257    try {
258      cameraManager.openDriver(surfaceHolder);
259      // Creating the handler starts the preview, which can also throw a
260      // RuntimeException.
261      if (handler == null) {
262        handler = new CaptureActivityHandler(this, characterSet, cameraManager);
263      }
264    } catch (IOException ioe) {
265      Log.w(LOG_TAG, ioe);
266      displayFrameworkBugMessageAndExit();
267    } catch (RuntimeException e) {
268      // Barcode Scanner has seen crashes in the wild of this variety:
269      // java.?lang.?RuntimeException: Fail to connect to camera service
270      Log.w(LOG_TAG, "Unexpected error initializing camera", e);
271      displayFrameworkBugMessageAndExit();
272    }
273  }
274
275  private void displayFrameworkBugMessageAndExit() {
276    AlertDialog.Builder builder = new AlertDialog.Builder(this);
277    builder.setTitle("");
278    builder.setMessage(getString(R.string.msg_camera_framework_bug));
279    builder.setPositiveButton(android.R.string.ok, new FinishListener(this));
280    builder.setOnCancelListener(new FinishListener(this));
281    builder.show();
282  }
283
284  private void resetStatusView() {
285    statusView.setText(R.string.msg_default_status);
286    statusView.setVisibility(View.VISIBLE);
287    viewfinderView.setVisibility(View.VISIBLE);
288  }
289
290  public void drawViewfinder() {
291    viewfinderView.drawViewfinder();
292  }
293}
294