Fix app stuck and memory issues while uploading images (#2287)

* Do not use an image array to store all bitmap pixels at once

* Extract image preprocessing to a different service and use computation thread

* Add java docs

* Cleanup code to remove temp file logic

* Add logs in upload flow

* Fix tests

* Fix more tests
This commit is contained in:
Vivek Maskara 2019-01-16 22:02:25 +05:30 committed by neslihanturan
parent 21f82dd346
commit 559127dfa3
21 changed files with 320 additions and 891 deletions

View file

@ -1,25 +1,21 @@
package fr.free.nrw.commons.upload
import android.app.Application
import android.content.ContentResolver
import android.content.Context
import android.content.SharedPreferences
import android.graphics.BitmapRegionDecoder
import android.net.Uri
import fr.free.nrw.commons.auth.SessionManager
import fr.free.nrw.commons.kvstore.BasicKvStore
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.IMAGE_OK
import fr.free.nrw.commons.utils.ImageUtilsWrapper
import io.reactivex.Single
import org.junit.After
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
import org.mockito.ArgumentMatchers.*
import org.mockito.ArgumentMatchers.any
import org.mockito.ArgumentMatchers.anyString
import org.mockito.InjectMocks
import org.mockito.Mock
import org.mockito.Mockito.`when`
@ -27,6 +23,7 @@ import org.mockito.Mockito.mock
import org.mockito.MockitoAnnotations
import java.io.FileInputStream
import java.io.InputStream
import java.util.*
import javax.inject.Inject
import javax.inject.Named
@ -51,11 +48,9 @@ class UploadModelTest {
@Mock
internal var fileUtilsWrapper: FileUtilsWrapper? = null
@Mock
internal var imageUtilsWrapper: ImageUtilsWrapper? = null
@Mock
internal var bitmapRegionDecoderWrapper: BitmapRegionDecoderWrapper? = null
@Mock
internal var fileProcessor: FileProcessor? = null
@Mock
internal var imageProcessingService: ImageProcessingService? = null
@InjectMocks
var uploadModel: UploadModel? = null
@ -67,8 +62,6 @@ class UploadModelTest {
`when`(context!!.applicationContext)
.thenReturn(mock(Application::class.java))
`when`(fileUtilsWrapper!!.createCopyPathAndCopy(anyBoolean(), any(Uri::class.java), nullable(ContentResolver::class.java), any(Context::class.java)))
.thenReturn("file.jpg")
`when`(fileUtilsWrapper!!.getFileExt(anyString()))
.thenReturn("jpg")
`when`(fileUtilsWrapper!!.getSHA1(any(InputStream::class.java)))
@ -77,12 +70,10 @@ class UploadModelTest {
.thenReturn(mock(FileInputStream::class.java))
`when`(fileUtilsWrapper!!.getGeolocationOfFile(anyString()))
.thenReturn("")
`when`(imageUtilsWrapper!!.checkIfImageIsTooDark(any(BitmapRegionDecoder::class.java)))
.thenReturn(IMAGE_OK)
`when`(imageUtilsWrapper!!.checkImageGeolocationIsDifferent(anyString(), any(LatLng::class.java)))
.thenReturn(false)
`when`(bitmapRegionDecoderWrapper!!.newInstance(any(FileInputStream::class.java), anyBoolean()))
.thenReturn(mock(BitmapRegionDecoder::class.java))
`when`(imageProcessingService!!.checkImageQuality(anyString()))
.thenReturn(Single.just(IMAGE_OK))
`when`(imageProcessingService!!.checkImageQuality(any(Place::class.java), anyString()))
.thenReturn(Single.just(IMAGE_OK))
}
@ -93,154 +84,99 @@ class UploadModelTest {
@Test
fun receive() {
val element = mock(Uri::class.java)
val element2 = mock(Uri::class.java)
val element = getElement()
val element2 = getElement()
var uriList: List<Uri> = mutableListOf<Uri>(element, element2)
uploadModel!!.receive(uriList, "image/jpeg", "external") { _, _ -> }
assertTrue(uploadModel!!.items.size == 2)
}
@Test
fun receiveDirect() {
val element = mock(Uri::class.java)
uploadModel!!.receiveDirect(element, "image/jpeg", "external", mock(Place::class.java)) { _, _ -> }
assertTrue(uploadModel!!.items.size == 1)
}
@Test
fun verifyPreviousNotAvailableForDirectUpload() {
val element = mock(Uri::class.java)
uploadModel!!.receiveDirect(element, "image/jpeg", "external", mock(Place::class.java)) { _, _ -> }
assertFalse(uploadModel!!.isPreviousAvailable)
}
@Test
fun verifyNextAvailableForDirectUpload() {
val element = mock(Uri::class.java)
uploadModel!!.receiveDirect(element, "image/jpeg", "external", mock(Place::class.java)) { _, _ -> }
assertTrue(uploadModel!!.isNextAvailable)
val preProcessImages = uploadModel!!.preProcessImages(uriList, "image/jpeg", mock(Place::class.java), "external") { _, _ -> }
preProcessImages.doOnComplete {
assertTrue(uploadModel!!.items.size == 2)
}
}
@Test
fun verifyPreviousNotAvailable() {
val element = mock(Uri::class.java)
val element2 = mock(Uri::class.java)
val element = getElement()
val element2 = getElement()
var uriList: List<Uri> = mutableListOf<Uri>(element, element2)
uploadModel!!.receive(uriList, "image/jpeg", "external") { _, _ -> }
uploadModel!!.preProcessImages(uriList, "image/jpeg", mock(Place::class.java), "external") { _, _ -> }
assertFalse(uploadModel!!.isPreviousAvailable)
}
@Test
fun verifyNextAvailable() {
val element = mock(Uri::class.java)
val element2 = mock(Uri::class.java)
val element = getElement()
val element2 = getElement()
var uriList: List<Uri> = mutableListOf<Uri>(element, element2)
uploadModel!!.receive(uriList, "image/jpeg", "external") { _, _ -> }
uploadModel!!.preProcessImages(uriList, "image/jpeg", mock(Place::class.java), "external") { _, _ -> }
assertTrue(uploadModel!!.isNextAvailable)
}
@Test
fun isSubmitAvailable() {
val element = mock(Uri::class.java)
val element2 = mock(Uri::class.java)
val element = getElement()
val element2 = getElement()
var uriList: List<Uri> = mutableListOf<Uri>(element, element2)
uploadModel!!.receive(uriList, "image/jpeg", "external") { _, _ -> }
uploadModel!!.preProcessImages(uriList, "image/jpeg", mock(Place::class.java), "external") { _, _ -> }
assertTrue(uploadModel!!.isNextAvailable)
}
@Test
fun isSubmitAvailableForDirectUpload() {
val element = mock(Uri::class.java)
uploadModel!!.receiveDirect(element, "image/jpeg", "external", mock(Place::class.java)) { _, _ -> }
assertTrue(uploadModel!!.isNextAvailable)
}
@Test
fun getCurrentStepForDirectUpload() {
val element = mock(Uri::class.java)
uploadModel!!.receiveDirect(element, "image/jpeg", "external", mock(Place::class.java)) { _, _ -> }
assertTrue(uploadModel!!.currentStep == 1)
}
@Test
fun getCurrentStep() {
val element = mock(Uri::class.java)
val element2 = mock(Uri::class.java)
val element = getElement()
val element2 = getElement()
var uriList: List<Uri> = mutableListOf<Uri>(element, element2)
uploadModel!!.receive(uriList, "image/jpeg", "external") { _, _ -> }
uploadModel!!.preProcessImages(uriList, "image/jpeg", mock(Place::class.java), "external") { _, _ -> }
assertTrue(uploadModel!!.currentStep == 1)
}
@Test
fun getStepCount() {
val element = mock(Uri::class.java)
val element2 = mock(Uri::class.java)
val element = getElement()
val element2 = getElement()
var uriList: List<Uri> = mutableListOf<Uri>(element, element2)
uploadModel!!.receive(uriList, "image/jpeg", "external") { _, _ -> }
assertTrue(uploadModel!!.stepCount == 4)
}
@Test
fun getStepCountForDirectUpload() {
val element = mock(Uri::class.java)
uploadModel!!.receiveDirect(element, "image/jpeg", "external", mock(Place::class.java)) { _, _ -> }
assertTrue(uploadModel!!.stepCount == 3)
}
@Test
fun getDirectCount() {
val element = mock(Uri::class.java)
uploadModel!!.receiveDirect(element, "image/jpeg", "external", mock(Place::class.java)) { _, _ -> }
assertTrue(uploadModel!!.count == 1)
val preProcessImages = uploadModel!!.preProcessImages(uriList, "image/jpeg", mock(Place::class.java), "external") { _, _ -> }
preProcessImages.doOnComplete {
assertTrue(uploadModel!!.stepCount == 4)
}
}
@Test
fun getCount() {
val element = mock(Uri::class.java)
val element2 = mock(Uri::class.java)
val element = getElement()
val element2 = getElement()
var uriList: List<Uri> = mutableListOf<Uri>(element, element2)
uploadModel!!.receive(uriList, "image/jpeg", "external") { _, _ -> }
assertTrue(uploadModel!!.count == 2)
val preProcessImages = uploadModel!!.preProcessImages(uriList, "image/jpeg", mock(Place::class.java), "external") { _, _ -> }
preProcessImages.doOnComplete {
assertTrue(uploadModel!!.count == 2)
}
}
@Test
fun getUploads() {
val element = mock(Uri::class.java)
val element2 = mock(Uri::class.java)
val element = getElement()
val element2 = getElement()
var uriList: List<Uri> = mutableListOf<Uri>(element, element2)
uploadModel!!.receive(uriList, "image/jpeg", "external") { _, _ -> }
assertTrue(uploadModel!!.uploads.size == 2)
}
@Test
fun getDirectUploads() {
val element = mock(Uri::class.java)
uploadModel!!.receiveDirect(element, "image/jpeg", "external", mock(Place::class.java)) { _, _ -> }
assertTrue(uploadModel!!.uploads.size == 1)
val preProcessImages = uploadModel!!.preProcessImages(uriList, "image/jpeg", mock(Place::class.java), "external") { _, _ -> }
preProcessImages.doOnComplete {
assertTrue(uploadModel!!.uploads.size == 2)
}
}
@Test
fun isTopCardState() {
val element = mock(Uri::class.java)
val element2 = mock(Uri::class.java)
val element = getElement()
val element2 = getElement()
var uriList: List<Uri> = mutableListOf<Uri>(element, element2)
uploadModel!!.receive(uriList, "image/jpeg", "external") { _, _ -> }
assertTrue(uploadModel!!.isTopCardState)
}
@Test
fun isTopCardStateForDirectUpload() {
val element = mock(Uri::class.java)
uploadModel!!.receiveDirect(element, "image/jpeg", "external", mock(Place::class.java)) { _, _ -> }
uploadModel!!.preProcessImages(uriList, "image/jpeg", mock(Place::class.java), "external") { _, _ -> }
assertTrue(uploadModel!!.isTopCardState)
}
@Test
fun next() {
val element = mock(Uri::class.java)
val element2 = mock(Uri::class.java)
val element = getElement()
val element2 = getElement()
var uriList: List<Uri> = mutableListOf<Uri>(element, element2)
uploadModel!!.receive(uriList, "image/jpeg", "external") { _, _ -> }
uploadModel!!.preProcessImages(uriList, "image/jpeg", mock(Place::class.java), "external") { _, _ -> }
assertTrue(uploadModel!!.currentStep == 1)
uploadModel!!.next()
assertTrue(uploadModel!!.currentStep == 2)
@ -248,10 +184,10 @@ class UploadModelTest {
@Test
fun previous() {
val element = mock(Uri::class.java)
val element2 = mock(Uri::class.java)
val element = getElement()
val element2 = getElement()
var uriList: List<Uri> = mutableListOf<Uri>(element, element2)
uploadModel!!.receive(uriList, "image/jpeg", "external") { _, _ -> }
uploadModel!!.preProcessImages(uriList, "image/jpeg", mock(Place::class.java), "external") { _, _ -> }
assertTrue(uploadModel!!.currentStep == 1)
uploadModel!!.next()
assertTrue(uploadModel!!.currentStep == 2)
@ -261,11 +197,19 @@ class UploadModelTest {
@Test
fun isShowingItem() {
val element = mock(Uri::class.java)
val element2 = mock(Uri::class.java)
val element = getElement()
val element2 = getElement()
var uriList: List<Uri> = mutableListOf<Uri>(element, element2)
uploadModel!!.receive(uriList, "image/jpeg", "external") { _, _ -> }
assertTrue(uploadModel!!.isShowingItem)
val preProcessImages = uploadModel!!.preProcessImages(uriList, "image/jpeg", 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")
return mock
}
@Test

View file

@ -3,12 +3,12 @@ package fr.free.nrw.commons.upload
import android.net.Uri
import fr.free.nrw.commons.mwapi.MediaWikiApi
import fr.free.nrw.commons.nearby.Place
import io.reactivex.Observable
import org.junit.Before
import org.junit.Test
import org.mockito.InjectMocks
import org.mockito.Mock
import org.mockito.Mockito
import org.mockito.MockitoAnnotations
import org.mockito.*
import org.mockito.Mockito.`when`
import org.mockito.Mockito.mock
class UploadPresenterTest {
@ -26,6 +26,12 @@ class UploadPresenterTest {
@Throws(Exception::class)
fun setUp() {
MockitoAnnotations.initMocks(this)
`when`(uploadModel!!.preProcessImages(ArgumentMatchers.anyListOf(Uri::class.java),
ArgumentMatchers.anyString(),
ArgumentMatchers.any(Place::class.java),
ArgumentMatchers.anyString(),
ArgumentMatchers.any(SimilarImageInterface::class.java)))
.thenReturn(Observable.just(mock(UploadModel.UploadItem::class.java)))
}
@Test
@ -33,18 +39,6 @@ class UploadPresenterTest {
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")
}
@Test
fun receiveSingleItem() {
val element = Mockito.mock(Uri::class.java)
uploadPresenter!!.receive(element, "image/jpeg", "external")
}
@Test
fun receiveDirect() {
val element = Mockito.mock(Uri::class.java)
uploadModel!!.receiveDirect(element, "image/jpeg", "external", Mockito.mock(Place::class.java)) { _, _ -> }
uploadPresenter!!.receive(uriList, "image/jpeg", "external", mock(Place::class.java))
}
}

View file

@ -7,17 +7,6 @@ import org.junit.Test
import java.io.*
class FileUtilsTest {
@Test
fun copiedFileIsIdenticalToSource() {
val source = File.createTempFile("temp", "")
val dest = File.createTempFile("temp", "")
writeToFile(source, "Hello, World")
FileUtils.copy(FileInputStream(source), FileOutputStream(dest))
assertEquals(getString(source), getString(dest))
}
@Test
fun deleteFile() {
val file = File.createTempFile("testfile", "")