/* * Copyright 2012-2013 the original author or authors. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package de.schildbach.wallet.digitalcoin.ui; import java.io.IOException; import java.util.EnumMap; import java.util.Map; import android.app.Activity; import android.app.AlertDialog; import android.app.Dialog; import android.content.Context; import android.content.DialogInterface; import android.content.DialogInterface.OnCancelListener; import android.content.DialogInterface.OnClickListener; import android.content.Intent; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.Rect; import android.hardware.Camera; import android.hardware.Camera.PreviewCallback; import android.os.Bundle; import android.os.Handler; import android.os.HandlerThread; import android.os.Process; import android.os.Vibrator; import android.view.KeyEvent; import android.view.SurfaceHolder; import android.view.SurfaceView; import com.google.zxing.BinaryBitmap; import com.google.zxing.DecodeHintType; import com.google.zxing.PlanarYUVLuminanceSource; import com.google.zxing.ReaderException; import com.google.zxing.Result; import com.google.zxing.ResultPoint; import com.google.zxing.ResultPointCallback; import com.google.zxing.common.HybridBinarizer; import com.google.zxing.qrcode.QRCodeReader; import de.schildbach.wallet.digitalcoin.camera.CameraManager; import de.schildbach.wallet.digitalcoin.R; /** * @author Andreas Schildbach */ public final class ScanActivity extends Activity implements SurfaceHolder.Callback { public static final String INTENT_EXTRA_RESULT = "result"; private static final long VIBRATE_DURATION = 50L; private static final long AUTO_FOCUS_INTERVAL_MS = 2500L; private final CameraManager cameraManager = new CameraManager(); private ScannerView scannerView; private SurfaceHolder surfaceHolder; private Vibrator vibrator; private HandlerThread cameraThread; private Handler cameraHandler; private static final int DIALOG_CAMERA_PROBLEM = 0; @Override public void onCreate(final Bundle savedInstanceState) { super.onCreate(savedInstanceState); vibrator = (Vibrator) getSystemService(Context.VIBRATOR_SERVICE); setContentView(R.layout.scan_activity); scannerView = (ScannerView) findViewById(R.id.scan_activity_mask); } @Override protected void onResume() { super.onResume(); cameraThread = new HandlerThread("cameraThread", Process.THREAD_PRIORITY_BACKGROUND); cameraThread.start(); cameraHandler = new Handler(cameraThread.getLooper()); final SurfaceView surfaceView = (SurfaceView) findViewById(R.id.scan_activity_preview); surfaceHolder = surfaceView.getHolder(); surfaceHolder.addCallback(this); surfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS); } public void surfaceCreated(final SurfaceHolder holder) { cameraHandler.post(openRunnable); } public void surfaceDestroyed(final SurfaceHolder holder) { } public void surfaceChanged(final SurfaceHolder holder, final int format, final int width, final int height) { } @Override protected void onPause() { cameraHandler.post(closeRunnable); surfaceHolder.removeCallback(this); super.onPause(); } @Override public void onBackPressed() { setResult(RESULT_CANCELED); finish(); } @Override public boolean onKeyDown(final int keyCode, final KeyEvent event) { switch (keyCode) { case KeyEvent.KEYCODE_FOCUS: case KeyEvent.KEYCODE_CAMERA: // don't launch camera app return true; case KeyEvent.KEYCODE_VOLUME_DOWN: case KeyEvent.KEYCODE_VOLUME_UP: cameraHandler.post(new Runnable() { public void run() { cameraManager.setTorch(keyCode == KeyEvent.KEYCODE_VOLUME_UP); } }); return true; } return super.onKeyDown(keyCode, event); } public void handleResult(final Result scanResult, final Bitmap scanImage) { vibrator.vibrate(VIBRATE_DURATION); // superimpose dots to highlight the key features of the qr code final ResultPoint[] points = scanResult.getResultPoints(); if (points != null && points.length > 0) { final Paint paint = new Paint(); paint.setColor(getResources().getColor(R.color.scan_result_dots)); paint.setStrokeWidth(10.0f); final Canvas canvas = new Canvas(scanImage); for (final ResultPoint point : points) canvas.drawPoint(point.getX(), point.getY(), paint); } scannerView.drawResultBitmap(scanImage); final Intent result = new Intent(); result.putExtra(INTENT_EXTRA_RESULT, scanResult.getText()); setResult(RESULT_OK, result); // delayed finish new Handler().post(new Runnable() { public void run() { finish(); } }); } private final Runnable openRunnable = new Runnable() { public void run() { try { cameraManager.open(surfaceHolder); final Rect framingRect = cameraManager.getFrame(); final Rect framingRectInPreview = cameraManager.getFramePreview(); runOnUiThread(new Runnable() { public void run() { scannerView.setFraming(framingRect, framingRectInPreview); } }); cameraHandler.post(autofocusRunnable); cameraHandler.post(fetchAndDecodeRunnable); } catch (final IOException x) { x.printStackTrace(); showDialog(DIALOG_CAMERA_PROBLEM); } catch (final RuntimeException x) { x.printStackTrace(); showDialog(DIALOG_CAMERA_PROBLEM); } } }; private final Runnable closeRunnable = new Runnable() { public void run() { cameraManager.close(); // cancel background thread cameraHandler.removeCallbacksAndMessages(null); cameraThread.quit(); } }; private final Runnable autofocusRunnable = new Runnable() { public void run() { final Camera camera = cameraManager.getCamera(); final String focusMode = camera.getParameters().getFocusMode(); final boolean useAutoFocus = Camera.Parameters.FOCUS_MODE_AUTO.equals(focusMode) || Camera.Parameters.FOCUS_MODE_MACRO.equals(focusMode); if (useAutoFocus) { camera.autoFocus(new Camera.AutoFocusCallback() { public void onAutoFocus(final boolean success, final Camera camera) { } }); // schedule again cameraHandler.postDelayed(autofocusRunnable, AUTO_FOCUS_INTERVAL_MS); } } }; private final Runnable fetchAndDecodeRunnable = new Runnable() { private final QRCodeReader reader = new QRCodeReader(); private final Map<DecodeHintType, Object> hints = new EnumMap<DecodeHintType, Object>(DecodeHintType.class); public void run() { cameraManager.requestPreviewFrame(new PreviewCallback() { public void onPreviewFrame(final byte[] data, final Camera camera) { decode(data); } }); } private void decode(final byte[] data) { final PlanarYUVLuminanceSource source = cameraManager.buildLuminanceSource(data); final BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source)); try { hints.put(DecodeHintType.NEED_RESULT_POINT_CALLBACK, new ResultPointCallback() { public void foundPossibleResultPoint(final ResultPoint dot) { runOnUiThread(new Runnable() { public void run() { scannerView.addDot(dot); } }); } }); final Result scanResult = reader.decode(bitmap, hints); // success final int sourceWidth = source.getWidth(); final int sourceHeight = source.getHeight(); final Bitmap grayscaleBitmap = Bitmap.createBitmap(sourceWidth, sourceHeight, Bitmap.Config.ARGB_8888); grayscaleBitmap.setPixels(source.renderCroppedGreyscaleBitmap(), 0, sourceWidth, 0, 0, sourceWidth, sourceHeight); runOnUiThread(new Runnable() { public void run() { handleResult(scanResult, grayscaleBitmap); } }); } catch (final ReaderException x) { // retry cameraHandler.post(fetchAndDecodeRunnable); } finally { reader.reset(); } } }; @Override protected Dialog onCreateDialog(final int id) { final AlertDialog.Builder builder = new AlertDialog.Builder(this); if (id == DIALOG_CAMERA_PROBLEM) { builder.setIcon(android.R.drawable.ic_dialog_alert); builder.setTitle(R.string.scan_camera_problem_dialog_title); builder.setMessage(R.string.scan_camera_problem_dialog_message); builder.setNeutralButton(R.string.button_dismiss, new OnClickListener() { public void onClick(final DialogInterface dialog, final int which) { finish(); } }); builder.setOnCancelListener(new OnCancelListener() { public void onCancel(final DialogInterface dialog) { finish(); } }); } return builder.create(); } }