/*
*
* Panbox - encryption for cloud storage
* Copyright (C) 2014-2015 by Fraunhofer SIT and Sirrix AG
*
* 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/>.
*
* Additonally, third party code may be provided with notices and open source
* licenses from communities and third parties that govern the use of those
* portions, and any licenses granted hereunder do not alter any rights and
* obligations you may have under such open source licenses, however, the
* disclaimer of warranty and limitation of liability provisions of the GPLv3
* will apply to all the product.
*
*/
package org.panbox.mobile.android.gui.activity;
import java.io.IOException;
import java.util.List;
import org.panbox.mobile.android.R;
import org.panbox.mobile.android.gui.webcam.CVImageProcessor;
import org.panbox.mobile.android.gui.webcam.QRCodeListener;
import org.panbox.mobile.android.gui.webcam.QRCodeProcessor;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.ImageFormat;
import android.graphics.Paint;
import android.hardware.Camera;
import android.hardware.Camera.Parameters;
import android.hardware.Camera.Size;
import android.os.Bundle;
import android.util.Log;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.Toast;
public class AssistentActivity extends Activity implements OnClickListener, QRCodeListener {
public static int counter = 0;
@Override
protected void onStart() {
Log.v("AssistantActivity", "in onStart()");
super.onStart();
}
@Override
protected void onResume() {
Log.v("AssistantActivity", "in onResume()");
imageProcessor.addSquareCodeListener(AssistentActivity.this); // set the listener that will process the scanning result
super.onResume();
}
@Override
protected void onPause() {
Log.v("AssistantActivity", "in onPause()");
super.onPause();
}
@Override
protected void onStop() {
Log.v("AssistantActivity", "in onStop()");
super.onStop();
}
@Override
protected void onDestroy() {
Log.v("AssistantActivity", "in onDestroy()");
super.onDestroy();
}
private ImageProcessingView processor;
private Preview mPreview;
private QRCodeProcessor imageProcessor = null;
private FixedAspectLayout layout;
private Button btnPairing;
@Override
protected void onCreate(Bundle savedInstanceState) {
Log.v("Assistant Activity", "in onCreate()");
super.onCreate(savedInstanceState);
Log.v("Counter", " " + counter);
setContentView(R.layout.pb_scan_assistent);
getActionBar().hide();
btnPairing = (Button)findViewById(R.id.man_pairing_button);
btnPairing.setOnClickListener(this);
imageProcessor = new QRCodeProcessor();
// Create our Preview view and set it as the content of our activity.
try {
layout = (FixedAspectLayout)findViewById(R.id.qr_scanner);
processor = new ImageProcessingView(this, imageProcessor);
mPreview = new Preview(this, processor);
layout.addView(mPreview);
layout.addView(processor);
} catch (IOException e) {
e.printStackTrace();
new AlertDialog.Builder(this).setMessage(e.getMessage()).create()
.show();
}
//===================================Start testing code (this method must be placed in the qrCodeDetected() method=================================================
//String result = "192.168.1.12:fgkd343alM";
//runGeneralPairingRequester(result);
//==================================End testing code==================================================
}
@Override
public void qrCodeDetected(String pairingPassword) {
counter++;
Log.v("AssistantActivity:", "in qrCodeDetected(), code was detected " + counter + " times");
pairingPassword = pairingPassword.trim();
if (pairingPassword.matches("^[0-9A-Za-z.]+:[A-Z0-9a-z+/]+={0,2}$")) {
Log.v("Assistant Activity",
"Scanned valid QR code! Will start the pairing!!!");
imageProcessor.removeSquareCodeListener(AssistentActivity.this); // need to deregister the listener,
// otherwise this listener gets notified multiple times,
// and qrCodeDetected() method is invoked also multiple times
Intent pairingExecutionActivity = new Intent(this,
PairingExecutionActivity.class);
pairingExecutionActivity.putExtra("pairingPassword",
pairingPassword);
startActivity(pairingExecutionActivity);
} else {
Toast invalidQr = Toast.makeText(getApplicationContext(),
getIntent().getExtras().getString("pb_pairing_error_occurred"),
Toast.LENGTH_SHORT);
invalidQr.show();
}
}
@Override
public void onClick(View v) {
Intent pairingActivity = new Intent(this, PairingActivity.class);
startActivity(pairingActivity);
}
@Override
protected void onNewIntent(Intent intent) {
Log.v("AssistantActivity:", "in onNewIntent()");
super.onNewIntent(intent);
imageProcessor.addSquareCodeListener(AssistentActivity.this); // set the listener that will process the scanning result
}
}
//----------------------------------------------------------------------
class ImageProcessingView extends View implements Camera.PreviewCallback {
public static final int SUBSAMPLING_FACTOR = 4;
private CVImageProcessor imageProcessor;
public ImageProcessingView(AssistentActivity context,
CVImageProcessor processor) throws IOException {
super(context);
this.imageProcessor = processor;
}
public void onPreviewFrame(final byte[] data, final Camera camera) {
try {
Camera.Size size = camera.getParameters().getPreviewSize();
processImage(data, size.width, size.height);
camera.addCallbackBuffer(data);
} catch (RuntimeException e) {
// The camera has probably just been released, ignore.
}
}
protected void processImage(byte[] data, int width, int height) {
int[] argbData = convertYUV420_NV21toARGB8888(data, width, height);
Bitmap bitmap = Bitmap.createBitmap(argbData, width, height, Bitmap.Config.ARGB_8888);
if (imageProcessor != null) {
imageProcessor.process(bitmap);
}
postInvalidate();
}
public static int[] convertYUV420_NV21toARGB8888(byte[] data, int width,
int height) {
int size = width * height;
int offset = size;
int[] pixels = new int[size];
int u, v, y1, y2, y3, y4;
// i along Y and the final pixels
// k along pixels U and V
for (int i = 0, k = 0; i < size; i += 2, k += 2) {
y1 = data[i] & 0xff;
y2 = data[i + 1] & 0xff;
y3 = data[width + i] & 0xff;
y4 = data[width + i + 1] & 0xff;
u = data[offset + k] & 0xff;
v = data[offset + k + 1] & 0xff;
u = u - 128;
v = v - 128;
pixels[i] = convertYUVtoARGB(y1, u, v);
pixels[i + 1] = convertYUVtoARGB(y2, u, v);
pixels[width + i] = convertYUVtoARGB(y3, u, v);
pixels[width + i + 1] = convertYUVtoARGB(y4, u, v);
if (i != 0 && (i + 2) % width == 0)
i += width;
}
return pixels;
}
private static int convertYUVtoARGB(int y, int u, int v) {
int r, g, b;
r = y + (int) 1.402f * u;
g = y - (int) (0.344f * v + 0.714f * u);
b = y + (int) 1.772f * v;
r = r > 255 ? 255 : r < 0 ? 0 : r;
g = g > 255 ? 255 : g < 0 ? 0 : g;
b = b > 255 ? 255 : b < 0 ? 0 : b;
return 0xff000000 | (r << 16) | (g << 8) | b;
}
@SuppressLint("DrawAllocation")
@Override
protected void onDraw(Canvas canvas) {
Paint paint = new Paint();
paint.setColor(Color.RED);
paint.setTextSize(30);
//String s = "Please scan the QR Code by showing it to the camera.";
//float textWidth = paint.measureText(s);
//canvas.drawText(s, (getWidth() - textWidth) / 2, 30, paint);
}
}
//----------------------------------------------------------------------
class Preview extends SurfaceView implements SurfaceHolder.Callback {
SurfaceHolder mHolder;
Camera mCamera;
Camera.PreviewCallback previewCallback;
@SuppressWarnings("deprecation")
Preview(Context context, Camera.PreviewCallback previewCallback) {
super(context);
this.previewCallback = previewCallback;
// Install a SurfaceHolder.Callback so we get notified when the
// underlying surface is created and destroyed.
mHolder = getHolder();
mHolder.addCallback(this);
mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
}
public void surfaceCreated(SurfaceHolder holder) {
// The Surface has been created, acquire the camera and tell it where
// to draw.
mCamera = openFrontAndBackCamera();
try {
try {
Camera.Parameters parameters = mCamera.getParameters();
parameters.setFocusMode(Parameters.FOCUS_MODE_CONTINUOUS_VIDEO);
mCamera.setParameters(parameters);
} catch (Exception e) {
Log.v("AssistentActivity::surfaceCreated", "Failed to set focus auto. Will continue with just default settings.");
}
try {
Camera.Parameters parameters = mCamera.getParameters();
parameters.setFlashMode(Parameters.FLASH_MODE_OFF);
mCamera.setParameters(parameters);
} catch (Exception e) {
Log.v("AssistentActivity::surfaceCreated", "Failed to set flash off. Will continue with just default settings.");
}
mCamera.setPreviewDisplay(holder);
// put the camera in a portrait mode
mCamera.setDisplayOrientation(90);
} catch (IOException exception) {
mCamera.release();
mCamera = null;
// TODO: add more exception handling logic here
}
}
private Camera openFrontAndBackCamera() {
int numberOfCameras = Camera.getNumberOfCameras();
if (numberOfCameras > 0) {
return Camera.open(0); // TODO: Make this configurable!
}
return null;
}
public void surfaceDestroyed(SurfaceHolder holder) {
// Surface will be destroyed when we return, so stop the preview.
// Because the CameraDevice object is not a shared resource, it's very
// important to release it when the activity is paused.
mCamera.stopPreview();
mCamera.release();
mCamera = null;
}
private Size getOptimalPreviewSize(List<Size> sizes, int w, int h) {
final double ASPECT_TOLERANCE = 0.05;
double targetRatio = (double) w / h;
if (sizes == null)
return null;
Size optimalSize = null;
double minDiff = Double.MAX_VALUE;
int targetHeight = h;
// Try to find an size match aspect ratio and size
for (Size size : sizes) {
double ratio = (double) size.width / size.height;
if (Math.abs(ratio - targetRatio) > ASPECT_TOLERANCE)
continue;
if (Math.abs(size.height - targetHeight) < minDiff) {
optimalSize = size;
minDiff = Math.abs(size.height - targetHeight);
}
}
// Cannot find the one match the aspect ratio, ignore the requirement
if (optimalSize == null) {
minDiff = Double.MAX_VALUE;
for (Size size : sizes) {
if (Math.abs(size.height - targetHeight) < minDiff) {
optimalSize = size;
minDiff = Math.abs(size.height - targetHeight);
}
}
}
return optimalSize;
}
public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
// Now that the size is known, set up the camera parameters and begin
// the preview.
Camera.Parameters parameters = mCamera.getParameters();
List<Size> sizes = parameters.getSupportedPreviewSizes();
Size optimalSize = getOptimalPreviewSize(sizes, w, h);
//parameters.setPreviewSize(optimalSize.width, optimalSize.height);
parameters.setPreviewSize(optimalSize.width, optimalSize.height);
mCamera.setParameters(parameters);
if (previewCallback != null) {
mCamera.setPreviewCallbackWithBuffer(previewCallback);
Camera.Size size = parameters.getPreviewSize();
byte[] data = new byte[size.width
* size.height
* ImageFormat
.getBitsPerPixel(parameters.getPreviewFormat()) / 8];
mCamera.addCallbackBuffer(data);
}
mCamera.startPreview();
}
}