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 (at) 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 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