Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
package org.odk.collect.android.feature.formentry

import android.app.Activity
import android.app.Instrumentation
import android.content.Intent
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.provider.MediaStore
import androidx.test.espresso.intent.Intents
import androidx.test.espresso.intent.matcher.IntentMatchers.hasAction
import androidx.test.espresso.intent.matcher.IntentMatchers.hasComponent
import org.hamcrest.Matcher
import org.hamcrest.MatcherAssert.assertThat
import org.hamcrest.Matchers.equalTo
import org.junit.Rule
import org.junit.Test
import org.junit.rules.RuleChain
import org.odk.collect.android.storage.StoragePathProvider
import org.odk.collect.android.support.TestDependencies
import org.odk.collect.android.support.pages.SendFinalizedFormPage
import org.odk.collect.android.support.rules.CollectTestRule
import org.odk.collect.android.support.rules.TestRuleChain.chain
import org.odk.collect.androidtest.RecordedIntentsRule
import org.odk.collect.draw.DrawActivity
import org.odk.collect.strings.R.string
import org.odk.collect.testshared.AssertionFramework
import java.io.File
import java.io.FileOutputStream

class ImageCompressionTest {
private val testDependencies = TestDependencies()
private val rule = CollectTestRule(useDemoProject = false)

@get:Rule
val chain: RuleChain = chain(testDependencies)
.around(RecordedIntentsRule())
.around(rule)

@Test
fun imageWidget_scalesCapturedImageDownToMaxPixels() {
rule.withProject(testDependencies.server, "image-compression.xml")
.startBlankForm("image-compression")
.also {
stubCaptureReturningImage(hasAction(MediaStore.ACTION_IMAGE_CAPTURE), 2000)
}
.clickOnString(string.capture_image, AssertionFramework.COMPOSE)
.clickGoToArrow()
.clickGoToEnd()
.clickFinalize()
.clickSendFinalizedForm(1)
.clickSelectAll()
.clickSendSelected()
.clickOK(SendFinalizedFormPage())

assertSubmittedImageWasScaledTo(1000)
}

@Test
fun annotateWidget_scalesCapturedImageDownToMaxPixels() {
rule.withProject(testDependencies.server, "image-compression.xml")
.startBlankForm("image-compression")
.swipeToNextQuestion("Annotate")
.also {
stubCaptureReturningImage(hasAction(MediaStore.ACTION_IMAGE_CAPTURE), 2000)
}
.clickOnString(string.capture_image)
.clickGoToArrow()
.clickGoToEnd()
.clickFinalize()
.clickSendFinalizedForm(1)
.clickSelectAll()
.clickSendSelected()
.clickOK(SendFinalizedFormPage())

assertSubmittedImageWasScaledTo(1000)
}

@Test
fun drawWidget_scalesCapturedImageDownToMaxPixels() {
rule.withProject(testDependencies.server, "image-compression.xml")
.startBlankForm("image-compression")
.swipeToNextQuestion("Annotate")
.swipeToNextQuestion("Draw")
.also {
stubCaptureReturningImage(hasComponent(DrawActivity::class.java.name), 2000)
}
.clickOnString(string.draw_image)
.clickGoToArrow()
.clickGoToEnd()
.clickFinalize()
.clickSendFinalizedForm(1)
.clickSelectAll()
.clickSendSelected()
.clickOK(SendFinalizedFormPage())

assertSubmittedImageWasScaledTo(1000)
}

@Test
fun signatureWidget_scalesCapturedImageDownToMaxPixels() {
rule.withProject(testDependencies.server, "image-compression.xml")
.startBlankForm("image-compression")
.swipeToNextQuestion("Annotate")
.swipeToNextQuestion("Draw")
.swipeToNextQuestion("Signature")
.also {
stubCaptureReturningImage(hasComponent(DrawActivity::class.java.name), 2000)
}
.clickOnString(string.sign_button)
.clickGoToArrow()
.clickGoToEnd()
.clickFinalize()
.clickSendFinalizedForm(1)
.clickSelectAll()
.clickSendSelected()
.clickOK(SendFinalizedFormPage())

assertSubmittedImageWasScaledTo(1000)
}

private fun stubCaptureReturningImage(captureIntent: Matcher<Intent>, longEdge: Int) {
val bitmap = Bitmap.createBitmap(longEdge, longEdge / 2, Bitmap.Config.ARGB_8888)
val tmpImage = File(StoragePathProvider().getTmpImageFilePath())
FileOutputStream(tmpImage).use { bitmap.compress(Bitmap.CompressFormat.JPEG, 90, it) }

Intents.intending(captureIntent).respondWith(
Instrumentation.ActivityResult(Activity.RESULT_OK, null)
)
}

private fun assertSubmittedImageWasScaledTo(longEdge: Int) {
val image = testDependencies.server.submittedMediaFiles.single {
it.extension.equals("jpg", ignoreCase = true)
}

val options = BitmapFactory.Options().apply { inJustDecodeBounds = true }
BitmapFactory.decodeFile(image.absolutePath, options)
assertThat(maxOf(options.outWidth, options.outHeight), equalTo(longEdge))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ class StubOpenRosaServer : OpenRosaHttpInterface {
* A list of submitted forms, maintained in the original order of submission, with the oldest forms appearing first.
*/
private val submittedForms: MutableList<File> = mutableListOf()
private val submittedAttachments: MutableList<File> = mutableListOf()
private val deletedEntities: MutableList<String> = mutableListOf()
private var includeIntegrityUrl = false

Expand All @@ -44,6 +45,9 @@ class StubOpenRosaServer : OpenRosaHttpInterface {
val submissions: List<File>
get() = submittedForms

val submittedMediaFiles: List<File>
get() = submittedAttachments

var accesses = 0
private set

Expand Down Expand Up @@ -135,6 +139,7 @@ class StubOpenRosaServer : OpenRosaHttpInterface {
return HttpPostResult("", 401, "")
} else if (uri.path == OpenRosaConstants.SUBMISSION) {
submittedForms.add(submissionFile)
submittedAttachments.addAll(fileList)
return HttpPostResult("", 201, "")
} else {
return HttpPostResult("", 404, "")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -309,8 +309,9 @@ abstract class Page<T : Page<T>> {
return destination
}

fun clickOnString(stringID: Int): T {
clickOnText(getTranslatedString(stringID))
@JvmOverloads
fun clickOnString(stringID: Int, assertionFramework: AssertionFramework = AssertionFramework.ESPRESSO): T {
clickOnText(getTranslatedString(stringID), assertionFramework)
return this as T
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,17 @@

import static org.odk.collect.settings.keys.ProjectKeys.KEY_IMAGE_SIZE;

import android.annotation.SuppressLint;
import android.net.Uri;
import android.os.AsyncTask;

import org.javarosa.core.model.Constants;
import org.odk.collect.android.activities.FormFillingActivity;
import org.odk.collect.android.application.Collect;
import org.odk.collect.android.injection.DaggerUtils;
import org.odk.collect.android.utilities.ContentUriHelper;
import org.odk.collect.android.utilities.FileUtils;
import org.odk.collect.android.utilities.ImageCompressionController;
import org.odk.collect.android.widgets.BaseImageWidget;
import org.odk.collect.android.widgets.QuestionWidget;
import org.odk.collect.androidshared.ui.DialogFragmentUtils;
import org.odk.collect.settings.SettingsProvider;
Expand Down Expand Up @@ -42,6 +43,7 @@ public void onAttach(FormFillingActivity formFillingActivity) {
DaggerUtils.getComponent(this.formFillingActivity.get()).inject(this);
}

@SuppressLint("WrongThread")
@Override
protected File doInBackground(Uri... uris) {
if (instanceFile != null) {
Expand All @@ -51,10 +53,10 @@ protected File doInBackground(Uri... uris) {
FileUtils.saveAnswerFileFromUri(uris[0], newFile, Collect.getInstance());
QuestionWidget questionWidget = formFillingActivity.get().getWidgetWaitingForBinaryData();

// apply image conversion if the widget is an image widget
if (questionWidget instanceof BaseImageWidget) {
// apply image conversion if the question is an image question
if (questionWidget.getFormEntryPrompt().getControlType() == Constants.CONTROL_IMAGE_CHOOSE) {
String imageSizeMode = settingsProvider.getUnprotectedSettings().getString(KEY_IMAGE_SIZE);
imageCompressionController.execute(newFile.getPath(), questionWidget, formFillingActivity.get(), imageSizeMode);
imageCompressionController.execute(newFile.getPath(), questionWidget.getFormEntryPrompt(), formFillingActivity.get(), imageSizeMode);
}
return newFile;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
package org.odk.collect.android.utilities

import android.content.Context
import org.javarosa.form.api.FormEntryPrompt
import org.odk.collect.android.R
import org.odk.collect.android.widgets.QuestionWidget
import org.odk.collect.androidshared.bitmap.ImageCompressor
import timber.log.Timber

class ImageCompressionController(private val imageCompressor: ImageCompressor) {
fun execute(
imagePath: String,
questionWidget: QuestionWidget,
prompt: FormEntryPrompt,
context: Context,
imageSizeMode: String
) {
var maxPixels: Int?
maxPixels = getMaxPixelsFromFormIfDefined(questionWidget)
maxPixels = getMaxPixelsFromFormIfDefined(prompt)
if (maxPixels == null) {
maxPixels = getMaxPixelsFromSettings(context, imageSizeMode)
}
Expand All @@ -23,8 +23,8 @@ class ImageCompressionController(private val imageCompressor: ImageCompressor) {
}
}

private fun getMaxPixelsFromFormIfDefined(questionWidget: QuestionWidget): Int? {
for (bindAttribute in questionWidget.formEntryPrompt.bindAttributes) {
private fun getMaxPixelsFromFormIfDefined(prompt: FormEntryPrompt): Int? {
for (bindAttribute in prompt.bindAttributes) {
if ("max-pixels" == bindAttribute.name && ApplicationConstants.Namespaces.XML_OPENROSA_NAMESPACE == bindAttribute.namespace) {
try {
return bindAttribute.attributeValue?.toInt()
Expand Down
Loading