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 
17 package com.google.zxing.client.android;
18 
19 import com.google.zxing.BarcodeFormat;
20 import com.google.zxing.Result;
21 import com.google.zxing.ResultPoint;
22 import com.google.zxing.client.android.camera.CameraManager;
23 
24 import android.app.Activity;
25 import android.app.AlertDialog;
26 import android.content.Intent;
27 import android.graphics.Bitmap;
28 import android.graphics.Canvas;
29 import android.graphics.Paint;
30 import android.graphics.Rect;
31 import android.os.Bundle;
32 import android.os.Handler;
33 import android.util.Log;
34 import android.view.KeyEvent;
35 import android.view.SurfaceHolder;
36 import android.view.SurfaceView;
37 import android.view.View;
38 import android.view.Window;
39 import android.view.WindowManager;
40 import android.widget.TextView;
41 
42 import 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  */
53 public 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 
getViewfinderView()65   ViewfinderView getViewfinderView() {
66     return viewfinderView;
67   }
68 
getHandler()69   public Handler getHandler() {
70     return handler;
71   }
72 
getCameraManager()73   CameraManager getCameraManager() {
74     return cameraManager;
75   }
76 
77   @Override
onCreate(Bundle icicle)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
onResume()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
onPause()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
onDestroy()157   protected void onDestroy() {
158     inactivityTimer.shutdown();
159     super.onDestroy();
160   }
161 
162   @Override
onKeyDown(int keyCode, KeyEvent event)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 
surfaceCreated(SurfaceHolder holder)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 
surfaceDestroyed(SurfaceHolder holder)185   public void surfaceDestroyed(SurfaceHolder holder) {
186     hasSurface = false;
187   }
188 
surfaceChanged(SurfaceHolder holder, int format, int width, int height)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    */
handleDecode(Result rawResult, Bitmap barcode)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    */
drawResultPoints(Bitmap barcode, Result rawResult)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 
drawLine(Canvas canvas, Paint paint, ResultPoint a, ResultPoint b)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 
initCamera(SurfaceHolder surfaceHolder)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 
displayFrameworkBugMessageAndExit()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 
resetStatusView()284   private void resetStatusView() {
285     statusView.setText(R.string.msg_default_status);
286     statusView.setVisibility(View.VISIBLE);
287     viewfinderView.setVisibility(View.VISIBLE);
288   }
289 
drawViewfinder()290   public void drawViewfinder() {
291     viewfinderView.drawViewfinder();
292   }
293 }
294