Add module for file picker for camera and gallery uploads (#2375)

* Use easy image for image picker

* Do not use harcoded mime type

* Use uploadable file for image uploads

* Add picker files in filepicker module

* Remove redundant checks for file

* Make usage of file extensions consistent

* Add javadocs

* Fix tests

* Enable image upload using bookmark activity

* Fix multiple uploads

* Fix external image uploads

* Fix chooser intents

* Fix image quality checks

* Segregate internal and external upload intents

* Invoke all error messages from one place

* Minor changes

* Fix tests

* Add image processing service tests
This commit is contained in:
Vivek Maskara 2019-02-04 02:10:31 +05:30 committed by neslihanturan
parent fb5a40bba5
commit 52ab39381e
39 changed files with 1553 additions and 574 deletions

View file

@ -176,7 +176,7 @@ class ContributionDaoTest {
@Test
fun saveNewContribution_nullableImageUrlUsesFileAsBackup() {
whenever(client.insert(isA(), isA())).thenReturn(contentUri)
val contribution = createContribution(true, null, null, null, "file")
val contribution = createContribution(true, null, null, null, "filePath")
testObject.save(contribution)
@ -186,7 +186,7 @@ class ContributionDaoTest {
// Nullable fields are absent if null
assertFalse(it.containsKey(Table.COLUMN_LOCAL_URI))
assertFalse(it.containsKey(Table.COLUMN_UPLOADED))
assertEquals(Utils.makeThumbBaseUrl("file"), it.getAsString(Table.COLUMN_IMAGE_URL))
assertEquals(Utils.makeThumbBaseUrl("filePath"), it.getAsString(Table.COLUMN_IMAGE_URL))
}
}
@ -285,7 +285,7 @@ class ContributionDaoTest {
createCursor(created, uploaded, false, localUri).let { mc ->
testObject.fromCursor(mc).let {
assertEquals(uriForId(111), it.contentUri)
assertEquals("file", it.filename)
assertEquals("filePath", it.filename)
assertEquals(localUri, it.localUri.toString())
assertEquals("image", it.imageUrl)
assertEquals(created, it.dateCreated.time)
@ -335,7 +335,7 @@ class ContributionDaoTest {
private fun createCursor(created: Long, uploaded: Long, multiple: Boolean, localUri: String) =
MatrixCursor(Table.ALL_FIELDS, 1).apply {
addRow(listOf("111", "file", localUri, "image",
addRow(listOf("111", "filePath", localUri, "image",
created, STATE_QUEUED, 222L, uploaded, 88L, SOURCE_GALLERY, "desc",
"create", if (multiple) 1 else 0, 640, 480, "007", "Q1"))
moveToFirst()

View file

@ -0,0 +1,137 @@
package fr.free.nrw.commons.upload
import android.graphics.BitmapRegionDecoder
import android.net.Uri
import fr.free.nrw.commons.location.LatLng
import fr.free.nrw.commons.mwapi.MediaWikiApi
import fr.free.nrw.commons.nearby.Place
import fr.free.nrw.commons.utils.BitmapRegionDecoderWrapper
import fr.free.nrw.commons.utils.ImageUtils
import fr.free.nrw.commons.utils.ImageUtilsWrapper
import io.reactivex.Single
import org.junit.Assert.assertEquals
import org.junit.Before
import org.junit.Test
import org.mockito.ArgumentMatchers
import org.mockito.InjectMocks
import org.mockito.Mock
import org.mockito.Mockito.*
import org.mockito.MockitoAnnotations
import java.io.FileInputStream
class ImageProcessingServiceTest {
@Mock
internal var fileUtilsWrapper: FileUtilsWrapper? = null
@Mock
internal var bitmapRegionDecoderWrapper: BitmapRegionDecoderWrapper? = null
@Mock
internal var imageUtilsWrapper: ImageUtilsWrapper? = null
@Mock
internal var mwApi: MediaWikiApi? = null
@InjectMocks
var imageProcessingService: ImageProcessingService? = null
@Mock
internal lateinit var uploadItem: UploadModel.UploadItem
@Before
@Throws(Exception::class)
fun setUp() {
MockitoAnnotations.initMocks(this)
val mediaUri = mock(Uri::class.java)
val mockPlace = mock(Place::class.java)
val mockTitle = mock(Title::class.java)
`when`(mockPlace.wikiDataEntityId).thenReturn("Q1")
`when`(mockPlace.getLocation()).thenReturn(mock(LatLng::class.java))
`when`(mediaUri.path).thenReturn("filePath")
`when`(mockTitle.isEmpty).thenReturn(false)
`when`(mockTitle.isSet).thenReturn(true)
`when`(uploadItem.mediaUri).thenReturn(mediaUri)
`when`(uploadItem.imageQuality).thenReturn(ImageUtils.IMAGE_WAIT)
`when`(uploadItem.title).thenReturn(mockTitle)
`when`(uploadItem.place).thenReturn(mockPlace)
`when`(fileUtilsWrapper!!.getFileInputStream(ArgumentMatchers.anyString()))
.thenReturn(mock(FileInputStream::class.java))
`when`(fileUtilsWrapper!!.getSHA1(any(FileInputStream::class.java)))
.thenReturn("fileSha")
`when`(fileUtilsWrapper!!.getGeolocationOfFile(ArgumentMatchers.anyString()))
.thenReturn("latLng")
`when`(bitmapRegionDecoderWrapper!!.newInstance(any(FileInputStream::class.java), anyBoolean()))
.thenReturn(mock(BitmapRegionDecoder::class.java))
`when`(imageUtilsWrapper!!.checkIfImageIsTooDark(any(BitmapRegionDecoder::class.java)))
.thenReturn(Single.just(ImageUtils.IMAGE_OK))
`when`(imageUtilsWrapper!!.checkImageGeolocationIsDifferent(ArgumentMatchers.anyString(), any(LatLng::class.java)))
.thenReturn(Single.just(ImageUtils.IMAGE_OK))
`when`(fileUtilsWrapper!!.getFileInputStream(ArgumentMatchers.anyString()))
.thenReturn(mock(FileInputStream::class.java))
`when`(fileUtilsWrapper!!.getSHA1(any(FileInputStream::class.java)))
.thenReturn("fileSha")
`when`(mwApi!!.existingFile(ArgumentMatchers.anyString()))
.thenReturn(false)
`when`(mwApi!!.fileExistsWithName(ArgumentMatchers.anyString()))
.thenReturn(false)
}
@Test
fun validateImageForKeepImage() {
`when`(uploadItem.imageQuality).thenReturn(ImageUtils.IMAGE_KEEP)
val validateImage = imageProcessingService!!.validateImage(uploadItem, false)
assertEquals(ImageUtils.IMAGE_OK, validateImage.blockingGet())
}
@Test
fun validateImageForDuplicateImage() {
`when`(mwApi!!.existingFile(ArgumentMatchers.anyString()))
.thenReturn(true)
val validateImage = imageProcessingService!!.validateImage(uploadItem, false)
assertEquals(ImageUtils.IMAGE_DUPLICATE, validateImage.blockingGet())
}
@Test
fun validateImageForOkImage() {
val validateImage = imageProcessingService!!.validateImage(uploadItem, false)
assertEquals(ImageUtils.IMAGE_OK, validateImage.blockingGet())
}
@Test
fun validateImageForDarkImage() {
`when`(imageUtilsWrapper!!.checkIfImageIsTooDark(any(BitmapRegionDecoder::class.java)))
.thenReturn(Single.just(ImageUtils.IMAGE_DARK))
val validateImage = imageProcessingService!!.validateImage(uploadItem, false)
assertEquals(ImageUtils.IMAGE_DARK, validateImage.blockingGet())
}
@Test
fun validateImageForWrongGeoLocation() {
`when`(imageUtilsWrapper!!.checkImageGeolocationIsDifferent(ArgumentMatchers.anyString(), any(LatLng::class.java)))
.thenReturn(Single.just(ImageUtils.IMAGE_GEOLOCATION_DIFFERENT))
val validateImage = imageProcessingService!!.validateImage(uploadItem, false)
assertEquals(ImageUtils.IMAGE_GEOLOCATION_DIFFERENT, validateImage.blockingGet())
}
@Test
fun validateImageForFileNameExistsWithCheckTitleOff() {
`when`(mwApi!!.fileExistsWithName(ArgumentMatchers.anyString()))
.thenReturn(true)
val validateImage = imageProcessingService!!.validateImage(uploadItem, false)
assertEquals(ImageUtils.IMAGE_OK, validateImage.blockingGet())
}
@Test
fun validateImageForFileNameExistsWithCheckTitleOn() {
`when`(mwApi!!.fileExistsWithName(ArgumentMatchers.nullable(String::class.java)))
.thenReturn(true)
val validateImage = imageProcessingService!!.validateImage(uploadItem, true)
assertEquals(ImageUtils.FILE_NAME_EXISTS, validateImage.blockingGet())
}
}

View file

@ -2,8 +2,8 @@ package fr.free.nrw.commons.upload
import android.app.Application
import android.content.Context
import android.net.Uri
import fr.free.nrw.commons.auth.SessionManager
import fr.free.nrw.commons.contributions.UploadableFile
import fr.free.nrw.commons.kvstore.BasicKvStore
import fr.free.nrw.commons.mwapi.MediaWikiApi
import fr.free.nrw.commons.nearby.Place
@ -14,8 +14,7 @@ import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
import org.mockito.ArgumentMatchers.any
import org.mockito.ArgumentMatchers.anyString
import org.mockito.ArgumentMatchers.*
import org.mockito.InjectMocks
import org.mockito.Mock
import org.mockito.Mockito.`when`
@ -70,10 +69,8 @@ class UploadModelTest {
.thenReturn(mock(FileInputStream::class.java))
`when`(fileUtilsWrapper!!.getGeolocationOfFile(anyString()))
.thenReturn("")
`when`(imageProcessingService!!.checkImageQuality(anyString()))
.thenReturn(Single.just(IMAGE_OK))
`when`(imageProcessingService!!.checkImageQuality(any(Place::class.java), anyString()))
.thenReturn(Single.just(IMAGE_OK))
`when`(imageProcessingService!!.validateImage(any(UploadModel.UploadItem::class.java), anyBoolean()))
.thenReturn(Single.just(IMAGE_OK));
}
@ -84,10 +81,7 @@ class UploadModelTest {
@Test
fun receive() {
val element = getElement()
val element2 = getElement()
var uriList: List<Uri> = mutableListOf<Uri>(element, element2)
val preProcessImages = uploadModel!!.preProcessImages(uriList, "image/jpeg", mock(Place::class.java), "external") { _, _ -> }
val preProcessImages = uploadModel!!.preProcessImages(getMediaList(), mock(Place::class.java), "external") { _, _ -> }
preProcessImages.doOnComplete {
assertTrue(uploadModel!!.items.size == 2)
}
@ -95,46 +89,31 @@ class UploadModelTest {
@Test
fun verifyPreviousNotAvailable() {
val element = getElement()
val element2 = getElement()
var uriList: List<Uri> = mutableListOf<Uri>(element, element2)
uploadModel!!.preProcessImages(uriList, "image/jpeg", mock(Place::class.java), "external") { _, _ -> }
uploadModel!!.preProcessImages(getMediaList(), mock(Place::class.java), "external") { _, _ -> }
assertFalse(uploadModel!!.isPreviousAvailable)
}
@Test
fun verifyNextAvailable() {
val element = getElement()
val element2 = getElement()
var uriList: List<Uri> = mutableListOf<Uri>(element, element2)
uploadModel!!.preProcessImages(uriList, "image/jpeg", mock(Place::class.java), "external") { _, _ -> }
uploadModel!!.preProcessImages(getMediaList(), mock(Place::class.java), "external") { _, _ -> }
assertTrue(uploadModel!!.isNextAvailable)
}
@Test
fun isSubmitAvailable() {
val element = getElement()
val element2 = getElement()
var uriList: List<Uri> = mutableListOf<Uri>(element, element2)
uploadModel!!.preProcessImages(uriList, "image/jpeg", mock(Place::class.java), "external") { _, _ -> }
uploadModel!!.preProcessImages(getMediaList(), mock(Place::class.java), "external") { _, _ -> }
assertTrue(uploadModel!!.isNextAvailable)
}
@Test
fun getCurrentStep() {
val element = getElement()
val element2 = getElement()
var uriList: List<Uri> = mutableListOf<Uri>(element, element2)
uploadModel!!.preProcessImages(uriList, "image/jpeg", mock(Place::class.java), "external") { _, _ -> }
uploadModel!!.preProcessImages(getMediaList(), mock(Place::class.java), "external") { _, _ -> }
assertTrue(uploadModel!!.currentStep == 1)
}
@Test
fun getStepCount() {
val element = getElement()
val element2 = getElement()
var uriList: List<Uri> = mutableListOf<Uri>(element, element2)
val preProcessImages = uploadModel!!.preProcessImages(uriList, "image/jpeg", mock(Place::class.java), "external") { _, _ -> }
val preProcessImages = uploadModel!!.preProcessImages(getMediaList(), mock(Place::class.java), "external") { _, _ -> }
preProcessImages.doOnComplete {
assertTrue(uploadModel!!.stepCount == 4)
}
@ -142,10 +121,7 @@ class UploadModelTest {
@Test
fun getCount() {
val element = getElement()
val element2 = getElement()
var uriList: List<Uri> = mutableListOf<Uri>(element, element2)
val preProcessImages = uploadModel!!.preProcessImages(uriList, "image/jpeg", mock(Place::class.java), "external") { _, _ -> }
val preProcessImages = uploadModel!!.preProcessImages(getMediaList(), mock(Place::class.java), "external") { _, _ -> }
preProcessImages.doOnComplete {
assertTrue(uploadModel!!.count == 2)
}
@ -153,10 +129,7 @@ class UploadModelTest {
@Test
fun getUploads() {
val element = getElement()
val element2 = getElement()
var uriList: List<Uri> = mutableListOf<Uri>(element, element2)
val preProcessImages = uploadModel!!.preProcessImages(uriList, "image/jpeg", mock(Place::class.java), "external") { _, _ -> }
val preProcessImages = uploadModel!!.preProcessImages(getMediaList(), mock(Place::class.java), "external") { _, _ -> }
preProcessImages.doOnComplete {
assertTrue(uploadModel!!.uploads.size == 2)
}
@ -164,19 +137,13 @@ class UploadModelTest {
@Test
fun isTopCardState() {
val element = getElement()
val element2 = getElement()
var uriList: List<Uri> = mutableListOf<Uri>(element, element2)
uploadModel!!.preProcessImages(uriList, "image/jpeg", mock(Place::class.java), "external") { _, _ -> }
uploadModel!!.preProcessImages(getMediaList(), mock(Place::class.java), "external") { _, _ -> }
assertTrue(uploadModel!!.isTopCardState)
}
@Test
fun next() {
val element = getElement()
val element2 = getElement()
var uriList: List<Uri> = mutableListOf<Uri>(element, element2)
uploadModel!!.preProcessImages(uriList, "image/jpeg", mock(Place::class.java), "external") { _, _ -> }
uploadModel!!.preProcessImages(getMediaList(), mock(Place::class.java), "external") { _, _ -> }
assertTrue(uploadModel!!.currentStep == 1)
uploadModel!!.next()
assertTrue(uploadModel!!.currentStep == 2)
@ -184,10 +151,7 @@ class UploadModelTest {
@Test
fun previous() {
val element = getElement()
val element2 = getElement()
var uriList: List<Uri> = mutableListOf<Uri>(element, element2)
uploadModel!!.preProcessImages(uriList, "image/jpeg", mock(Place::class.java), "external") { _, _ -> }
uploadModel!!.preProcessImages(getMediaList(), mock(Place::class.java), "external") { _, _ -> }
assertTrue(uploadModel!!.currentStep == 1)
uploadModel!!.next()
assertTrue(uploadModel!!.currentStep == 2)
@ -197,18 +161,22 @@ class UploadModelTest {
@Test
fun isShowingItem() {
val element = getElement()
val element2 = getElement()
var uriList: List<Uri> = mutableListOf<Uri>(element, element2)
val preProcessImages = uploadModel!!.preProcessImages(uriList, "image/jpeg", mock(Place::class.java), "external") { _, _ -> }
val preProcessImages = uploadModel!!.preProcessImages(getMediaList(), mock(Place::class.java), "external") { _, _ -> }
preProcessImages.doOnComplete {
assertTrue(uploadModel!!.isShowingItem)
}
}
private fun getElement(): Uri {
val mock = mock(Uri::class.java)
`when`(mock.path).thenReturn(UUID.randomUUID().toString() + "/file.jpg")
private fun getMediaList(): List<UploadableFile> {
val element = getElement()
val element2 = getElement()
var uriList: List<UploadableFile> = mutableListOf(element, element2)
return uriList
}
private fun getElement(): UploadableFile {
val mock = mock(UploadableFile::class.java)
`when`(mock.filePath).thenReturn(UUID.randomUUID().toString() + "/filePath.jpg")
return mock
}

View file

@ -1,6 +1,6 @@
package fr.free.nrw.commons.upload
import android.net.Uri
import fr.free.nrw.commons.contributions.UploadableFile
import fr.free.nrw.commons.mwapi.MediaWikiApi
import fr.free.nrw.commons.nearby.Place
import io.reactivex.Observable
@ -26,8 +26,7 @@ class UploadPresenterTest {
@Throws(Exception::class)
fun setUp() {
MockitoAnnotations.initMocks(this)
`when`(uploadModel!!.preProcessImages(ArgumentMatchers.anyListOf(Uri::class.java),
ArgumentMatchers.anyString(),
`when`(uploadModel!!.preProcessImages(ArgumentMatchers.anyListOf(UploadableFile::class.java),
ArgumentMatchers.any(Place::class.java),
ArgumentMatchers.anyString(),
ArgumentMatchers.any(SimilarImageInterface::class.java)))
@ -36,9 +35,9 @@ class UploadPresenterTest {
@Test
fun receiveMultipleItems() {
val element = Mockito.mock(Uri::class.java)
val element2 = Mockito.mock(Uri::class.java)
var uriList: List<Uri> = mutableListOf<Uri>(element, element2)
uploadPresenter!!.receive(uriList, "image/jpeg", "external", mock(Place::class.java))
val element = Mockito.mock(UploadableFile::class.java)
val element2 = Mockito.mock(UploadableFile::class.java)
var uriList: List<UploadableFile> = mutableListOf<UploadableFile>(element, element2)
uploadPresenter!!.receive(uriList, "external", mock(Place::class.java))
}
}