diff --git a/.travis.yml b/.travis.yml
index c7c60469..a6115c5f 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,3 +1,5 @@
+# https://github.com/andstatus/andstatus/blob/master/.travis.yml
+
language: android
branches:
@@ -12,9 +14,11 @@ jdk:
env:
global:
- # Run android tests on api level 22
- - EMULATOR_API=22
+ # Where to run androidTests
+ - EMULATOR_API=22 # 24 has some issues, probably some overlayed window
- EMULATOR_ABI=armeabi-v7a
+ - EMULATOR_TAG=default
+ - PATH=$ANDROID_HOME:$ANDROID_HOME/emulator:$ANDROID_HOME/platform-tools:$PATH
android:
components:
@@ -23,16 +27,44 @@ android:
- build-tools-26.0.1
- android-26
- doc-26
- # Android tests
- - android-$EMULATOR_API
- - sys-img-$EMULATOR_ABI-android-$EMULATOR_API
- # sys-img-x86-google_apis-26
+
+install:
+ # Setup
+ - echo $ANDROID_HOME # We assume this is correctly set when setting path
+ - sdkmanager --list || true # Look at the packages
+ - echo yes | sdkmanager "tools" # Ensure tools is updated
+ - echo yes | sdkmanager "emulator" # Ensure emulator is present
+
+ # Install emulator
+ - export EMULATOR="system-images;android-$EMULATOR_API;$EMULATOR_TAG;$EMULATOR_ABI"
+ - echo yes | sdkmanager "platforms;android-$EMULATOR_API" # Install sdk
+ - echo yes | sdkmanager "$EMULATOR" # Install system image
+ - sdkmanager --list || true # Check everything is updated
+
+ # Create adn start emulator
+ - echo no | avdmanager create avd -n test -k "$EMULATOR" -f # Create emulator
+ - which emulator # ensure we are using the right emulator (home/emulator/)
+ - emulator -avd test -no-window -camera-back emulated -camera-front emulated -memory 2048 -writable-system & # Launch
+ - adb wait-for-device # Wait for adb process
+ - adb remount # Mount as writable
before_script:
- - echo no | android create avd --force --name test --target android-$EMULATOR_API --abi $EMULATOR_ABI
- - emulator -avd test -no-audio -no-window &
- - android-wait-for-emulator
- - adb shell input keyevent 82 &
+ # Wait for emulator
+ - android-wait-for-emulator # Wait for emulator ready to interact
+ - adb shell settings put global window_animation_scale 0 & # Disable animations
+ - adb shell settings put global transition_animation_scale 0 & # Disable animations
+ - adb shell settings put global animator_duration_scale 0 & # Disable animations
+
+ # Unlock and configure logs.
+ # Would be great to use -v color to adb logcat but looks not supported on travis.
+ - sleep 20 # Sleep 20 seconds just in case
+ - adb shell input keyevent 82 & # Dispatch unlock event
+ - adb logcat --help # See if this version supports color
+ - adb logcat -c # Clear logcat
+ - adb logcat Test:V TestRunner:V CameraView:V CameraController:V Camera1:V WorkerHandler:V THREAD_STATE:S *:E &
+ # - export LOGCAT_PID=$! # Save PID of the logcat process. Should kill later with kill $LOGCAT_PID
+
+
script:
- ./gradlew clean testDebugUnitTest connectedCheck mergedCoverageReport
diff --git a/README.md b/README.md
index 8f9095bb..3f139f44 100644
--- a/README.md
+++ b/README.md
@@ -433,6 +433,7 @@ Other APIs not mentioned above are provided, and are well documented and comment
|`toggleFlash()`|Toggles the flash value between `Flash.OFF`, `Flash.ON`, and `Flash.AUTO`.|
|`setLocation(Location)`|Sets location data to be appended to picture/video metadata.|
|`setLocation(double, double)`|Sets latitude and longitude to be appended to picture/video metadata.|
+|`getLocation()`|Retrieves location data previously applied with setLocation().|
|`startAutoFocus(float, float)`|Starts an autofocus process at the given coordinates, with respect to the view dimensions.|
|`getPreviewSize()`|Returns the size of the preview surface. If CameraView was not constrained in its layout phase (e.g. it was `wrap_content`), this will return the same aspect ratio of CameraView.|
|`getSnapshotSize()`|Returns `getPreviewSize()`, since a snapshot is a preview frame.|
diff --git a/cameraview/src/androidTest/AndroidManifest.xml b/cameraview/src/androidTest/AndroidManifest.xml
index 1795db1e..431ee7a8 100644
--- a/cameraview/src/androidTest/AndroidManifest.xml
+++ b/cameraview/src/androidTest/AndroidManifest.xml
@@ -3,9 +3,13 @@
+
+
+
diff --git a/cameraview/src/androidTest/java/com/otaliastudios/cameraview/AspectRatioTest.java b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/AspectRatioTest.java
index 715a95b5..427c936c 100644
--- a/cameraview/src/androidTest/java/com/otaliastudios/cameraview/AspectRatioTest.java
+++ b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/AspectRatioTest.java
@@ -16,7 +16,7 @@ import static org.junit.Assert.*;
@RunWith(AndroidJUnit4.class)
@SmallTest
-public class AspectRatioTest {
+public class AspectRatioTest extends BaseTest {
@Test
public void testConstructor() {
@@ -29,13 +29,31 @@ public class AspectRatioTest {
@Test
public void testEquals() {
AspectRatio ratio = AspectRatio.of(50, 10);
+ assertFalse(ratio.equals(null));
+ assertTrue(ratio.equals(ratio));
+
AspectRatio ratio1 = AspectRatio.of(5, 1);
assertTrue(ratio.equals(ratio1));
+ AspectRatio.sCache.clear();
+ AspectRatio ratio2 = AspectRatio.of(500, 100);
+ assertTrue(ratio.equals(ratio2));
+
Size size = new Size(500, 100);
assertTrue(ratio.matches(size));
}
+ @Test
+ public void testCompare() {
+ AspectRatio ratio1 = AspectRatio.of(10, 10);
+ AspectRatio ratio2 = AspectRatio.of(10, 2);
+ AspectRatio ratio3 = AspectRatio.of(2, 10);
+ assertTrue(ratio1.compareTo(ratio2) < 0);
+ assertTrue(ratio1.compareTo(ratio3) > 0);
+ assertTrue(ratio1.compareTo(ratio1) == 0);
+ assertNotEquals(ratio1.hashCode(), ratio2.hashCode());
+ }
+
@Test
public void testInverse() {
AspectRatio ratio = AspectRatio.of(50, 10);
diff --git a/cameraview/src/androidTest/java/com/otaliastudios/cameraview/BaseTest.java b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/BaseTest.java
index 7f505479..7d2e3a59 100644
--- a/cameraview/src/androidTest/java/com/otaliastudios/cameraview/BaseTest.java
+++ b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/BaseTest.java
@@ -1,19 +1,76 @@
package com.otaliastudios.cameraview;
+import android.annotation.SuppressLint;
+import android.app.KeyguardManager;
import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Rect;
+import android.graphics.YuvImage;
+import android.os.Build;
import android.os.Handler;
import android.os.Looper;
+import android.os.PowerManager;
import android.support.test.InstrumentationRegistry;
import android.support.test.annotation.UiThreadTest;
+import android.support.test.espresso.core.internal.deps.guava.collect.ObjectArrays;
import android.support.test.rule.ActivityTestRule;
import android.view.View;
+import org.junit.After;
+import org.junit.AfterClass;
import org.junit.Before;
+import org.junit.BeforeClass;
import org.junit.Rule;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+import org.mockito.stubbing.Stubber;
+
+import java.io.ByteArrayOutputStream;
+import java.io.OutputStream;
+import java.util.concurrent.CountDownLatch;
+
+import static android.content.Context.KEYGUARD_SERVICE;
+import static android.content.Context.POWER_SERVICE;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
public class BaseTest {
+ public static CameraLogger LOG = CameraLogger.create("Test");
+
+ private static KeyguardManager.KeyguardLock keyguardLock;
+ private static PowerManager.WakeLock wakeLock;
+
+ // https://github.com/linkedin/test-butler/blob/bc2bb4df13d0a554d2e2b0ea710795017717e710/test-butler-app/src/main/java/com/linkedin/android/testbutler/ButlerService.java#L121
+ @BeforeClass
+ @SuppressWarnings("MissingPermission")
+ public static void wakeUp() {
+ CameraLogger.setLogLevel(CameraLogger.LEVEL_VERBOSE);
+
+ // Acquire a keyguard lock to prevent the lock screen from randomly appearing and breaking tests
+ KeyguardManager keyguardManager = (KeyguardManager) context().getSystemService(KEYGUARD_SERVICE);
+ keyguardLock = keyguardManager.newKeyguardLock("CameraViewLock");
+ keyguardLock.disableKeyguard();
+
+ // Acquire a wake lock to prevent the cpu from going to sleep and breaking tests
+ PowerManager powerManager = (PowerManager) context().getSystemService(POWER_SERVICE);
+ wakeLock = powerManager.newWakeLock(PowerManager.FULL_WAKE_LOCK
+ | PowerManager.ACQUIRE_CAUSES_WAKEUP
+ | PowerManager.ON_AFTER_RELEASE, "CameraViewLock");
+ wakeLock.acquire();
+ }
+
+ @AfterClass
+ @SuppressWarnings("MissingPermission")
+ public static void releaseWakeUp() {
+ wakeLock.release();
+ keyguardLock.reenableKeyguard();
+ }
+
public static void ui(Runnable runnable) {
InstrumentationRegistry.getInstrumentation().runOnMainSync(runnable);
}
@@ -35,7 +92,82 @@ public class BaseTest {
});
}
- public static void waitUi() {
+ public static void idle() {
InstrumentationRegistry.getInstrumentation().waitForIdleSync();
}
+
+ public static void sleep(long time) {
+ try {
+ Thread.sleep(time);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+
+ public static void grantPermissions() {
+ grantPermission("android.permission.CAMERA");
+ grantPermission("android.permission.RECORD_AUDIO");
+ grantPermission("android.permission.WRITE_EXTERNAL_STORAGE");
+ }
+
+ public static void grantPermission(String permission) {
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) return;
+ String command = "pm grant " + context().getPackageName() + " " + permission;
+ InstrumentationRegistry.getInstrumentation().getUiAutomation().executeShellCommand(command);
+ }
+
+ public static byte[] mockJpeg(int width, int height) {
+ Bitmap source = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+ ByteArrayOutputStream os = new ByteArrayOutputStream();
+ source.compress(Bitmap.CompressFormat.JPEG, 100, os);
+ return os.toByteArray();
+ }
+
+ public static YuvImage mockYuv(int width, int height) {
+ YuvImage y = mock(YuvImage.class);
+ when(y.getWidth()).thenReturn(width);
+ when(y.getHeight()).thenReturn(height);
+ when(y.compressToJpeg(any(Rect.class), anyInt(), any(OutputStream.class))).thenAnswer(new Answer() {
+ @Override
+ public Boolean answer(InvocationOnMock invocation) throws Throwable {
+ Rect rect = (Rect) invocation.getArguments()[0];
+ OutputStream stream = (OutputStream) invocation.getArguments()[2];
+ stream.write(mockJpeg(rect.width(), rect.height()));
+ return true;
+ }
+ });
+ return y;
+ }
+
+ public static Stubber doCountDown(final CountDownLatch latch) {
+ return doAnswer(new Answer