package com.mygeopay.wallet.ui;
/*
* Copyright 2012-2014 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/>.
*/
import android.app.Activity;
import android.content.Context;
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.Build;
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.mygeopay.wallet.R;
import com.mygeopay.wallet.camera.CameraManager;
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 org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.EnumMap;
import java.util.Map;
/**
* @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;
private static boolean DISABLE_CONTINUOUS_AUTOFOCUS = Build.MODEL.equals("GT-I9100") // Galaxy S2
|| Build.MODEL.equals("SGH-T989") // Galaxy S2
|| Build.MODEL.equals("SGH-T989D") // Galaxy S2 X
|| Build.MODEL.equals("SAMSUNG-SGH-I727") // Galaxy S2 Skyrocket
|| Build.MODEL.equals("GT-I9300") // Galaxy S3
|| Build.MODEL.equals("GT-N7000"); // Galaxy Note
private static final Logger log = LoggerFactory.getLogger(ScanActivity.class);
@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);
}
@Override
public void surfaceCreated(final SurfaceHolder holder)
{
cameraHandler.post(openRunnable);
}
@Override
public void surfaceDestroyed(final SurfaceHolder holder)
{
}
@Override
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()
{
@Override
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 thumbnailImage, final float thumbnailScaleFactor)
{
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(thumbnailImage);
canvas.scale(thumbnailScaleFactor, thumbnailScaleFactor);
for (final ResultPoint point : points)
canvas.drawPoint(point.getX(), point.getY(), paint);
}
scannerView.drawResultBitmap(thumbnailImage);
final Intent result = new Intent();
result.putExtra(INTENT_EXTRA_RESULT, scanResult.getText());
setResult(RESULT_OK, result);
// delayed finish
new Handler().post(new Runnable()
{
@Override
public void run()
{
finish();
}
});
}
private final Runnable openRunnable = new Runnable()
{
@Override
public void run()
{
try
{
final Camera camera = cameraManager.open(surfaceHolder, !DISABLE_CONTINUOUS_AUTOFOCUS);
final Rect framingRect = cameraManager.getFrame();
final Rect framingRectInPreview = cameraManager.getFramePreview();
runOnUiThread(new Runnable()
{
@Override
public void run()
{
scannerView.setFraming(framingRect, framingRectInPreview);
}
});
final String focusMode = camera.getParameters().getFocusMode();
final boolean nonContinuousAutoFocus = Camera.Parameters.FOCUS_MODE_AUTO.equals(focusMode)
|| Camera.Parameters.FOCUS_MODE_MACRO.equals(focusMode);
if (nonContinuousAutoFocus)
cameraHandler.post(new AutoFocusRunnable(camera));
cameraHandler.post(fetchAndDecodeRunnable);
}
catch (final IOException x)
{
log.info("problem opening camera", x);
//TODO show dialog
// showDialog(DIALOG_CAMERA_PROBLEM);
}
catch (final RuntimeException x)
{
log.info("problem opening camera", x);
//TODO show dialog
// showDialog(DIALOG_CAMERA_PROBLEM);
}
}
};
private final Runnable closeRunnable = new Runnable()
{
@Override
public void run()
{
cameraManager.close();
// cancel background thread
cameraHandler.removeCallbacksAndMessages(null);
cameraThread.quit();
}
};
private final class AutoFocusRunnable implements Runnable
{
private final Camera camera;
public AutoFocusRunnable(final Camera camera)
{
this.camera = camera;
}
@Override
public void run()
{
camera.autoFocus(new Camera.AutoFocusCallback()
{
@Override
public void onAutoFocus(final boolean success, final Camera camera)
{
// schedule again
cameraHandler.postDelayed(AutoFocusRunnable.this, 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);
@Override
public void run()
{
cameraManager.requestPreviewFrame(new PreviewCallback()
{
@Override
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()
{
@Override
public void foundPossibleResultPoint(final ResultPoint dot)
{
runOnUiThread(new Runnable()
{
@Override
public void run()
{
scannerView.addDot(dot);
}
});
}
});
final Result scanResult = reader.decode(bitmap, hints);
final int thumbnailWidth = source.getThumbnailWidth();
final int thumbnailHeight = source.getThumbnailHeight();
final float thumbnailScaleFactor = (float) thumbnailWidth / source.getWidth();
final Bitmap thumbnailImage = Bitmap.createBitmap(thumbnailWidth, thumbnailHeight, Bitmap.Config.ARGB_8888);
thumbnailImage.setPixels(source.renderThumbnail(), 0, thumbnailWidth, 0, 0, thumbnailWidth, thumbnailHeight);
runOnUiThread(new Runnable()
{
@Override
public void run()
{
handleResult(scanResult, thumbnailImage, thumbnailScaleFactor);
}
});
}
catch (final ReaderException x)
{
// retry
cameraHandler.post(fetchAndDecodeRunnable);
}
finally
{
reader.reset();
}
}
};
/** TODO
*
* Use the new {@link android.app.DialogFragment} class with
* {@link android.app.FragmentManager} instead
*
*/
// @Override
// protected Dialog onCreateDialog(final int id)
// {
// if (id == DIALOG_CAMERA_PROBLEM)
// {
// final DialogBuilder dialog = DialogBuilder.warn(this, R.string.scan_camera_problem_dialog_title);
// dialog.setMessage(R.string.scan_camera_problem_dialog_message);
// dialog.singleDismissButton(new OnClickListener()
// {
// @Override
// public void onClick(final DialogInterface dialog, final int which)
// {
// finish();
// }
// });
// dialog.setOnCancelListener(new OnCancelListener()
// {
// @Override
// public void onCancel(final DialogInterface dialog)
// {
// finish();
// }
// });
//
// return dialog.create();
// }
// else
// {
// throw new IllegalArgumentException();
// }
// }
}