Commit ef26761d authored by Aneta Steimarova's avatar Aneta Steimarova

Merge branch 'V3.4' into merge_java_8

parents 9e629d3d e300d981
......@@ -243,6 +243,7 @@ user_rank_10=Rozumbrada
user_rank_11=Vrchní rada
sync_request_dialog_text=Zadej číslo průkazu:
sync_request_qr_dialog_text=Zadej číslo průkazu nebo naskenuj QR kód, který nalezneš ve svém profilu.
sync_request_button=Synchronizovat uživatele
sync_request_wrong_id=Špatně zadaný kód.
sync_request_error=Synchronizace se nezdařila, zkuste to prosím později.
......
......@@ -243,6 +243,7 @@ user_rank_10=Besserwisser
user_rank_11=Oberrat
sync_request_dialog_text=Trage deine Ausweisnummer ein:
sync_request_qr_dialog_text=Trage deine Ausweisnummer ein oder scanne den QR-Code ein, der du in deinem Profil findest.
sync_request_button=Account synchronisieren
sync_request_wrong_id=Falsch geschriebener Code.
sync_request_error=Die synchronisierung ist fehlgeschlagen, bitte versuche es später noch einmal.
......
......@@ -246,6 +246,7 @@ user_rank_10=Rozumbrada
user_rank_11=Šefmajster
sync_request_dialog_text=Zadaj číslo preukazu:
sync_request_qr_dialog_text=Zadaj číslo preukazu alebo naskenuj QR kód, ktorý nájdeš vo svojom profile.
sync_request_button=Synchronizovať užívateľa
sync_request_wrong_id=Zle zadaný kód.
sync_request_error=Synchronizácia sa nepodarila, skúste to prosím neskôr.
......
......@@ -21,7 +21,7 @@ final BUILD_CONFIG_DIR = "${buildDir}/generated/source/buildConfig/"
final BUILD_CONFIG_ENCODING = 'UTF-8'
android {
buildToolsVersion "20.0.0"
buildToolsVersion "24.0.3"
compileSdkVersion 21
packagingOptions {
......@@ -34,11 +34,14 @@ android {
}
defaultConfig {
targetSdkVersion 17
targetSdkVersion 21
versionName tablexiaVersionName
versionCode tablexiaVersionCode
applicationId rootProject.applicationBaseId
testApplicationId rootProject.applicationBaseId + ".test"
jackOptions {
enabled true
}
}
buildTypes {
......@@ -78,9 +81,6 @@ android {
java.srcDirs = ['src/main/java']
jniLibs.srcDirs = ['libs']
}
androidTest {
java.srcDir file('src/androidTest/java')
}
test {
java.srcDir file('src/test/java')
}
......@@ -130,8 +130,8 @@ android {
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_6
targetCompatibility JavaVersion.VERSION_1_6
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
command {
......@@ -261,11 +261,3 @@ idea {
}
}
}
spoon {
debug = true
failOnFailure = false
//testSizes = ['small', 'medium']
adbTimeout = 10*60
failIfNoDeviceConnected = false
}
/*
* Copyright (C) 2016 CZ.NIC, z.s.p.o. (http://www.nic.cz/)
*
* 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 cz.nic.tablexia.android.test.instrumentation;
import android.test.ActivityInstrumentationTestCase2;
import com.squareup.spoon.Spoon;
import java.io.File;
import cz.nic.tablexia.android.AndroidLauncher;
/**
* Created by lhoracek on 5/13/15.
*/
public abstract class AbstractOpenglTest<T extends AndroidLauncher> extends ActivityInstrumentationTestCase2<T> {
public AbstractOpenglTest(Class<T> activityClass) {
super(activityClass);
}
/**
* Sleep the thread to wait for animations to complete
*
* @param seconds
*/
protected void sleep(float seconds) {
try {
Thread.sleep((int) (seconds * 1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
/**
* Take screenshot using custom OpenGL readPixels buffer method instead of using decorView. Lets the Spoon create the file first and then just overwrites it.
* Not the best method, but the simplest.
* @param name
*/
protected void screenshot(String name) {
File screenshot = Spoon.screenshot(getActivity(), name);
getActivity().takeScreenshot(screenshot);
sleep(3);
}
}
......@@ -19,33 +19,27 @@ package cz.nic.tablexia.android;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.Matrix;
import android.net.ConnectivityManager;
import android.os.Build;
import android.os.Bundle;
import com.badlogic.gdx.backends.android.AndroidApplication;
import com.badlogic.gdx.backends.android.AndroidApplicationConfiguration;
import com.flurry.android.FlurryAgent;
import java.io.File;
import java.io.FileOutputStream;
import cz.nic.tablexia.Tablexia;
import cz.nic.tablexia.TablexiaBuildConfig;
import cz.nic.tablexia.TablexiaSettings;
import cz.nic.tablexia.android.camera.AndroidCamera2QRCodeScanner;
import cz.nic.tablexia.android.camera.AndroidQRCodeScanner;
import cz.nic.tablexia.debug.BuildConfig;
import cz.nic.tablexia.debug.R;
import cz.nic.tablexia.screen.loader.IConnectionManager;
import cz.nic.tablexia.util.Log;
import cz.nic.tablexia.util.ui.QRCodeScanner;
public class AndroidLauncher extends AndroidApplication {
public static final Tablexia.SQLConnectionType SQL_CONNECTION_TYPE = new Tablexia.SQLConnectionType("org.sqldroid.SQLDroidDriver", "jdbc:sqldroid:");
protected static final int MULTI_SAMPLING_2X = 2;
public static final boolean HAS_SOFT_BACK_BUTTON = false;
private static final int MULTI_SAMPLING_2X = 2;
private static final boolean HAS_SOFT_BACK_BUTTON = false;
protected Tablexia tablexia;
......@@ -68,6 +62,8 @@ public class AndroidLauncher extends AndroidApplication {
}
}
QRCodeScanner qrCodeScanner = android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP ? new AndroidCamera2QRCodeScanner(this) : new AndroidQRCodeScanner(this);
AndroidApplicationConfiguration config = new AndroidApplicationConfiguration();
config.useWakelock = true;
config.numSamples = MULTI_SAMPLING_2X;
......@@ -76,51 +72,12 @@ public class AndroidLauncher extends AndroidApplication {
getResources().getConfiguration().locale,
SQL_CONNECTION_TYPE,
new AndroidConnectionManager(getContext()),
new AndroidQRCodeScanner(this),
qrCodeScanner,
getResources().getString(R.string.sentry_dsn),
HAS_SOFT_BACK_BUTTON,
savedInstanceState == null,
Build.SERIAL), config);
runTest();
if (TablexiaSettings.getInstance().getBuildType().isBugReport() && TablexiaBuildConfig.FLURRY_KEY != null) {
FlurryAgent.setLogEnabled(false);
FlurryAgent.setVersionName(TablexiaSettings.getInstance().getFullName());
FlurryAgent.init(this, TablexiaBuildConfig.FLURRY_KEY);
}
}
/**
* Method supporting screenshots during UI testing the application using OpenGL
*
* @param file
*/
public void takeScreenshot(final File file) {
tablexia.takeScreenShot(new Tablexia.ScreenshotListener() {
@Override
public void screenshotTaken(int width, int height, int[] screen) {
try {
for (int i = 0; i < screen.length; ++i) {
// The alpha and green channels' positions are preserved while the red and blue are swapped
screen[i] = ((screen[i] & 0xff00ff00)) | ((screen[i] & 0x000000ff) << 16) | ((screen[i] & 0x00ff0000) >> 16);
}
Bitmap sb = Bitmap.createBitmap(screen, width, height, Bitmap.Config.RGB_565);
// flip bitmap vertically
Matrix matrixMirror = new Matrix();
matrixMirror.preScale(1.0f, -1.0f);
sb = Bitmap.createBitmap(sb, 0, 0, sb.getWidth(), sb.getHeight(), matrixMirror, false);
// overwrite file
FileOutputStream fOut = new FileOutputStream(file);
sb.compress(Bitmap.CompressFormat.PNG, 100, fOut);
sb.recycle();
fOut.flush();
fOut.close();
} catch (Exception e) {
e.printStackTrace();
Log.err(this.getClass().getSimpleName(), e.getMessage());
}
}
});
}
public void post(Runnable r) {
......
/*
* Copyright (C) 2016 CZ.NIC, z.s.p.o. (http://www.nic.cz/)
*
* 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 cz.nic.tablexia.android.camera;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.pm.PackageManager;
import android.graphics.ImageFormat;
import android.graphics.Matrix;
import android.graphics.Point;
import android.graphics.RectF;
import android.graphics.SurfaceTexture;
import android.hardware.camera2.CameraAccessException;
import android.hardware.camera2.CameraCaptureSession;
import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CameraDevice;
import android.hardware.camera2.CameraManager;
import android.hardware.camera2.CameraMetadata;
import android.hardware.camera2.CaptureRequest;
import android.hardware.camera2.params.StreamConfigurationMap;
import android.media.Image;
import android.media.ImageReader;
import android.os.Build;
import android.os.Handler;
import android.os.HandlerThread;
import android.util.Size;
import android.view.Surface;
import android.view.TextureView;
import android.view.ViewGroup;
import android.view.ViewParent;
import com.google.zxing.BinaryBitmap;
import com.google.zxing.LuminanceSource;
import com.google.zxing.MultiFormatReader;
import com.google.zxing.PlanarYUVLuminanceSource;
import com.google.zxing.ReaderException;
import com.google.zxing.Result;
import com.google.zxing.common.HybridBinarizer;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import cz.nic.tablexia.android.AndroidLauncher;
import cz.nic.tablexia.util.Log;
import cz.nic.tablexia.util.ui.QRCodeScanner;
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public class AndroidCamera2QRCodeScanner extends QRCodeScanner {
private static final int IMAGE_FORMAT = ImageFormat.YUV_420_888;
private final static String BACKGROUND_THREAD_NAME = "BACKGROUND_THREAD";
private final int IMAGE_READER_BUFFER_LIMIT = 10;
private static final int MAX_PREVIEW_WIDTH = 1280;
private static final int MAX_PREVIEW_HEIGHT = 720;
private AndroidLauncher androidLauncher;
private TextureView textureView;
private String cameraId;
private CameraDevice cameraDevice;
private CaptureRequest.Builder captureRequestBuilder;
private CameraCaptureSession cameraCaptureSessions;
private Handler backgroundHandler;
private HandlerThread backgroundThread;
private ImageReader imageReader;
private MultiFormatReader qrReader;
private Size previewSize;
private CameraManager cameraManager;
private Integer viewRotation;
private boolean cameraAccessible = false;
public AndroidCamera2QRCodeScanner(AndroidLauncher androidLauncher) {
this.androidLauncher = androidLauncher;
cameraAccessible = androidLauncher.getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA);
qrReader = new MultiFormatReader();
}
private final ImageReader.OnImageAvailableListener mOnImageAvailableListener =
new ImageReader.OnImageAvailableListener() {
@Override
public void onImageAvailable(ImageReader reader) {
Image img = null;
img = reader.acquireLatestImage();
Result rawResult = null;
try {
if (img == null) throw new NullPointerException("cannot be null");
ByteBuffer buffer = img.getPlanes()[0].getBuffer();
byte[] data = new byte[buffer.remaining()];
buffer.get(data);
int width = img.getWidth();
int height = img.getHeight();
LuminanceSource source = new PlanarYUVLuminanceSource(data, width, height, width/4, height/4, width/2, height/2, false);
BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source));
rawResult = qrReader.decode(bitmap);
onCodeScanned(rawResult.getText());
} catch (ReaderException e) {
Log.err(getClass(), "Reader shows an exception! ", e);
} catch (NullPointerException e) {
Log.err(getClass(), "NPE while reading", e);
} finally {
qrReader.reset();
Log.debug(getClass(), "Resetting reader");
if (img != null)
img.close();
}
if (rawResult != null) {
Log.debug(getClass(), "Decoding successful!");
} else {
Log.debug(getClass(), "No QR code found…");
}
}
};
public void setCameraDevice(CameraDevice cameraDevice) {
this.cameraDevice = cameraDevice;
}
public CameraDevice getCameraDevice() {
return cameraDevice;
}
@Override
public boolean isCameraAccessible() {
return cameraAccessible;
}
@Override
public void onCameraPreviewStarted() {
textureView = new TextureView(androidLauncher);
textureView.setSurfaceTextureListener(surfaceTextureListener);
textureView.bringToFront();
androidLauncher.post(new Runnable() {
@Override
public void run() {
androidLauncher.addContentView(textureView, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
}
});
}
TextureView.SurfaceTextureListener surfaceTextureListener = new TextureView.SurfaceTextureListener() {
@Override
public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, int width, int height) {
prepareCamera(width, height);
startBackgroundThread();
configureTransform(width, height);
openCamera(width, height);
}
@Override
public void onSurfaceTextureSizeChanged(SurfaceTexture surfaceTexture, int width, int height) {
configureTransform(width, height);
}
@Override
public boolean onSurfaceTextureDestroyed(SurfaceTexture surfaceTexture) {
return false;
}
@Override
public void onSurfaceTextureUpdated(SurfaceTexture surfaceTexture) {
if (viewRotation != null && viewRotation != getOrientation())
configureTransform(textureView.getWidth(), textureView.getHeight());
}
};
@Override
public void onCameraPreviewStopped() {
androidLauncher.post(new Runnable() {
@Override
public void run() {
if (textureView != null) {
ViewParent viewParent = textureView.getParent();
if (viewParent instanceof ViewGroup) {
ViewGroup viewGroup = (ViewGroup) viewParent;
viewGroup.removeView(textureView);
}
}
closeCamera();
if (backgroundThread != null) stopBackgroundThread();
}
});
}
private int getOrientation(){
return androidLauncher.getWindowManager().getDefaultDisplay().getRotation();
}
private void prepareCamera(int width, int height) {
cameraManager = (CameraManager) androidLauncher.getSystemService(Context.CAMERA_SERVICE);
try {
for (String id : cameraManager.getCameraIdList()) {
CameraCharacteristics characteristics = cameraManager.getCameraCharacteristics(id);
Integer lens = characteristics.get(CameraCharacteristics.LENS_FACING);
if (lens != CameraCharacteristics.LENS_FACING_BACK) continue;
cameraId = id;
StreamConfigurationMap map = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
Point displaySize = new Point();
androidLauncher.getWindowManager().getDefaultDisplay().getSize(displaySize);
int maxPreviewWidth = displaySize.x;
int maxPreviewHeight = displaySize.y;
if (maxPreviewWidth > MAX_PREVIEW_WIDTH) {
maxPreviewWidth = MAX_PREVIEW_WIDTH;
}
if (maxPreviewHeight > MAX_PREVIEW_HEIGHT) {
maxPreviewHeight = MAX_PREVIEW_HEIGHT;
}
Size largest = Collections.max(
Arrays.asList(map.getOutputSizes(IMAGE_FORMAT)),
new CompareSizesByArea());
previewSize = chooseOptimalSize(map.getOutputSizes(SurfaceTexture.class),
width, height, maxPreviewWidth,
maxPreviewHeight, largest);
imageReader = ImageReader.newInstance(previewSize.getWidth(), previewSize.getHeight(),
IMAGE_FORMAT, IMAGE_READER_BUFFER_LIMIT);
imageReader.setOnImageAvailableListener(
mOnImageAvailableListener, backgroundHandler);
}
// if none of cameras doesn't fit -> go back to loader screen
if (cameraId == null) androidLauncher.onBackPressed();
} catch (CameraAccessException e) {
Log.err(getClass(), "Error while opening the camera", e);
}
}
private void openCamera(int width, int height) {
// TODO: 22.11.16 add permission requst for API > 23
Log.debug(getClass(), "Opening the camera");
try {
cameraManager.openCamera(cameraId, cameraCallback, backgroundHandler);
} catch (CameraAccessException e) {
Log.err(getClass(), "Error while opening the camera", e);
}
Log.debug(getClass(), "Camera is opened");
}
private CameraDevice.StateCallback cameraCallback = new CameraDevice.StateCallback() {
@Override
public void onOpened(CameraDevice cameraDevice) {
setCameraDevice(cameraDevice);
createCameraPreview();
}
@Override
public void onDisconnected(CameraDevice cameraDevice) {
cameraDevice.close();
}
@Override
public void onError(CameraDevice cameraDevice, int i) {
cameraDevice.close();
setCameraDevice(null);
}
};
private void createCameraPreview() {
try {
SurfaceTexture texture = textureView.getSurfaceTexture();
texture.setDefaultBufferSize(previewSize.getWidth(), previewSize.getHeight());
Surface surface = new Surface(texture);
Surface imageSurface = imageReader.getSurface();
captureRequestBuilder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
captureRequestBuilder.addTarget(imageSurface);
captureRequestBuilder.addTarget(surface);
cameraDevice.createCaptureSession(Arrays.asList(imageSurface, surface), new CameraCaptureSession.StateCallback() {
@Override
public void onConfigured(CameraCaptureSession cameraCaptureSession) {
if (null == cameraDevice) {
return;
}
cameraCaptureSessions = cameraCaptureSession;
updatePreview();
}
@Override
public void onConfigureFailed(CameraCaptureSession cameraCaptureSession) {
Log.err(getClass(), "ConfigureFailed");
}
}, backgroundHandler);
} catch (CameraAccessException e) {
Log.err(getClass(), "Can't access the camera", e);
}
}
private void updatePreview() {
if (null == cameraDevice) {
Log.err(getClass(), "Camera device = null");
}
captureRequestBuilder.set(CaptureRequest.CONTROL_MODE, CameraMetadata.CONTROL_MODE_AUTO);
try {
cameraCaptureSessions.setRepeatingRequest(captureRequestBuilder.build(), null, backgroundHandler);
} catch (CameraAccessException e) {
Log.err(getClass(), "Can't access the camera", e);
}
}
private void closeCamera() {
if (null != cameraDevice) {
cameraDevice.close();
cameraDevice = null;
}
if (null != imageReader) {
imageReader.close();
imageReader = null;
}
if (textureView != null) {
textureView = null;
}
}
protected void startBackgroundThread() {
backgroundThread = new HandlerThread(BACKGROUND_THREAD_NAME);
backgroundThread.start();
backgroundHandler = new Handler(backgroundThread.getLooper());
}
protected void stopBackgroundThread() {
backgroundThread.quitSafely();
try {
backgroundThread.join();
backgroundThread = null;
backgroundHandler = null;
} catch (InterruptedException e) {
Log.err(getClass(), "Error while stopping background thread", e);
}
}
private static Size chooseOptimalSize(Size[] choices, int textureViewWidth,
int textureViewHeight, int maxWidth, int maxHeight, Size aspectRatio) {
List<Size> bigEnough = new ArrayList<Size>();
List<Size> notBigEnough = new ArrayList<Size>();
int w = aspectRatio.getWidth();
int h = aspectRatio.getHeight();
for (Size option : choices) {
if (option.getWidth() <= maxWidth && option.getHeight() <= maxHeight &&
option.getHeight() == option.getWidth() * h / w) {
if (option.getWidth() >= textureViewWidth &&
option.getHeight() >= textureViewHeight) {
bigEnough.add(option);
} else {
notBigEnough.add(option);
}
}
}
if (bigEnough.size() > 0) {
return Collections.min(bigEnough, new CompareSizesByArea());
} else if (notBigEnough.size() > 0) {
return Collections.max(notBigEnough, new CompareSizesByArea());
} else {
return choices[0];
}
}
private void configureTransform(int viewWidth, int viewHeight) {
Log.debug(getClass(), "Transforming the view");
if (textureView == null || previewSize == null) return;
int rotation = getOrientation();
Matrix matrix = new Matrix();
RectF viewRect = new RectF(0, 0, viewWidth, viewHeight);
RectF bufferRect = new RectF(0, 0, previewSize.getHeight(), previewSize.getWidth());
float centerX = viewRect.centerX();
float centerY = viewRect.centerY();
if (Surface.ROTATION_90 == rotation || Surface.ROTATION_270 == rotation) {
bufferRect.offset(centerX - bufferRect.centerX(), centerY - bufferRect.centerY());
matrix.setRectToRect(viewRect, bufferRect, Matrix.ScaleToFit.FILL);
float scale = Math.max(
(float) viewHeight / previewSize.getHeight(),
(float) viewWidth / previewSize.getWidth());
matrix.postScale(scale, scale, centerX, centerY);
matrix.postRotate(90 * (rotation - 2), centerX, centerY);
}
viewRotation = rotation;
textureView.setTransform(matrix);
}
static class CompareSizesByArea implements Comparator<Size> {
@Override
public int compare(Size lhs, Size rhs) {