Skip to content

Commit 764d665

Browse files
CalumMcCalljohnxnguyen
authored andcommitted
Assets refactoring with public logs (#2047)
* Add basic assets storage test. * Add few tests to AssetMetadataService. * refactor: assets. Add audio metadata extraction. * Add tests for RawAssetStorage. * feat: Add Glide with stub data fetcher and basic transformations * Add asset preview service and refactor asset detail service. * feature assets: small improvements according to changed models * feature: assets. renaming basic assets models. * Fix compilation issues with the new assets engine * Fix issues post rebasing; Adapt data fetchers to new assets engine. * Use new public asset ids and glide * Fix set profile picture * Increase Blur intensity * Adapt video message part for new assets * add asset policies * Show video preview when downloading * Add storage permissions when recording audio, we need it for the cache * fix image compression. fix instrumentation tests. * fix: Share assets with the new flow * Use media recorder to record audio messages directly in mp4 * change ui to handle new assets upload cancellation flow. * Add load file to the action button actions * Correctly open the files externally * Remove unused import * Remove glide/okhttp integration since it was causing crashes * remove public asset id and use user data picture instead. * Bump SE * stylish fixes. * fix: Audio recorder on swipe should stop and send immediately * Fix inconsistent class filename * fix rebasing issues. * Use new build numbers * remove commented code * fix WebLinkPartView. * Fix audio recorder exception. * Initial fixes after merging. * Fix few errors. * Fix more merge errors, add LogShow instances * point to SE build * Fix merge conflicts. * Fix user profiles images loading in conversations list. * Bump SE. * In the middle of fixing collection view in conversation. * Fixing collection view in conversation. * Fix account picture in the settings screen. * Refactor Glide usage. * Fix asset file name while sharing. * Fix asset file upload cancellation. * Add basic helper classes for audio records. * Enable audio filters again. * Add AudioRecordingScreen. Add AudioService docs. * Squash me. * Squash me. * Squash me. * Squash me. * Comment old tests. Fix LinearInterpolation. * Fix WaveBinView. Reduce read buffer size for PCM recording. * Fix WaveBinView audio level normalization. * Squash me. * Add pcm to mp4 conversion. * fix: Recreating the old view of audio filters (#2148) * Add audio recording sending. * fix: Hide the wave view, the time label, and stop playing the sound if the user cancels it (#2151) * Add permissions request on audio message button long press. * Fix/audio recorder dispose (#2156) * Fix wrong thread exceptions * Stop recording when screen dismissed * Prepare target file immediately before sending (#2158) * Use API safe method (#2159) * fix: Playing a sent audio message while recording one will freeze the record screen * Bump SE version. * Unwrap uri for glide (#2161) * fix:image orientation (#2163) * Correct image orientation * Don't attempt to recode a non existent file (#2171) * Bump SE
1 parent 970337a commit 764d665

File tree

159 files changed

+5544
-2286
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

159 files changed

+5544
-2286
lines changed

LICENSE

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -671,4 +671,4 @@ into proprietary programs. If your program is a subroutine library, you
671671
may consider it more useful to permit linking proprietary applications with
672672
the library. If this is what you want to do, use the GNU Lesser General
673673
Public License instead of this License. But first, please read
674-
<http://www.gnu.org/philosophy/why-not-lgpl.html>.
674+
<http://www.gnu.org/philosophy/why-not-lgpl.html>.

app/build.gradle

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
apply plugin: 'com.android.application'
22
apply plugin: 'kotlin-android'
3+
apply plugin: 'kotlin-android-extensions'
34
apply plugin: 'jp.leafytree.android-scala'
45
apply plugin: 'hugo'
56
apply plugin: 'com.mutualmobile.gradle.plugins.dexinfo'
@@ -22,7 +23,7 @@ ext {
2223
playServicesVersion = '15.0.1'
2324
audioVersion = System.getenv("AUDIO_VERSION") ?: '1.209.0@aar'
2425
stethoVersion = '1.5.0'
25-
zMessagingVersion = "141.0.2281"
26+
zMessagingVersion = "142.0.2283"
2627
paging_version = "1.0.0"
2728

2829
avsVersion = '4.9.12@aar'
@@ -350,6 +351,8 @@ dependencies {
350351
implementation "com.android.support:cardview-v7:$supportLibVersion"
351352
implementation 'com.android.support.constraint:constraint-layout:1.1.3'
352353
implementation "android.arch.paging:runtime:$paging_version"
354+
implementation "com.android.support:exifinterface:$supportLibVersion"
355+
implementation "com.android.support:support-media-compat:$supportLibVersion"
353356

354357
// Play services
355358
implementation "com.google.android.gms:play-services-base:$playServicesVersion"
@@ -366,6 +369,8 @@ dependencies {
366369
implementation "android.arch.work:work-runtime:$work_version"
367370

368371
//third party libraries
372+
implementation 'io.reactivex.rxjava2:rxkotlin:2.3.0'
373+
implementation 'io.reactivex.rxjava2:rxandroid:2.1.1'
369374
implementation 'com.evernote:android-job:1.2.6'
370375
implementation 'com.jakewharton.timber:timber:4.7.0'
371376
implementation 'com.jakewharton.threetenabp:threetenabp:1.1.0'
@@ -375,6 +380,8 @@ dependencies {
375380
devImplementation "com.facebook.stetho:stetho:$stethoVersion"
376381
experimentalImplementation "com.facebook.stetho:stetho:$stethoVersion"
377382
internalImplementation "com.facebook.stetho:stetho:$stethoVersion"
383+
implementation "com.github.bumptech.glide:glide:4.8.0"
384+
annotationProcessor 'com.github.bumptech.glide:compiler:4.8.0'
378385

379386
// Test dependencies
380387
testImplementation 'junit:junit:4.12'
@@ -399,6 +406,11 @@ dependencies {
399406
androidTestImplementation 'com.crittercism.dexmaker:dexmaker-mockito:1.4'
400407
androidTestImplementation "android.arch.work:work-testing:$work_version"
401408

409+
androidTestImplementation 'junit:junit:4.12'
410+
androidTestImplementation 'org.mockito:mockito-core:1.10.19'
411+
androidTestImplementation 'com.android.support.test:runner:1.0.2'
412+
androidTestImplementation 'com.android.support.test:rules:1.0.2'
413+
402414
ScalaCompileOptions.metaClass.daemonServer = true
403415
ScalaCompileOptions.metaClass.fork = true
404416
ScalaCompileOptions.metaClass.useAnt = false

app/proguard-rules.txt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,3 +128,11 @@
128128
## JNA classes for calling v3
129129
-keep class * extends com.sun.jna.** { *; }
130130
-keep class com.sun.jna.** { *; }
131+
132+
# Glide
133+
-keep public class * implements com.bumptech.glide.module.GlideModule
134+
-keep public class * extends com.bumptech.glide.module.AppGlideModule
135+
-keep public enum com.bumptech.glide.load.ImageHeaderParser$** {
136+
**[] $VALUES;
137+
public *;
138+
}
11 MB
Binary file not shown.
2.63 KB
Loading
5.01 MB
Binary file not shown.
10.3 MB
Binary file not shown.
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/**
2+
* Wire
3+
* Copyright (C) 2018 Wire Swiss GmbH
4+
*
5+
* This program is free software: you can redistribute it and/or modify
6+
* it under the terms of the GNU General Public License as published by
7+
* the Free Software Foundation, either version 3 of the License, or
8+
* (at your option) any later version.
9+
*
10+
* This program is distributed in the hope that it will be useful,
11+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
* GNU General Public License for more details.
14+
*
15+
* You should have received a copy of the GNU General Public License
16+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
17+
*/
18+
package com.waz.zclient
19+
20+
import java.net.URI
21+
22+
import android.content.Context
23+
24+
import scala.concurrent.duration._
25+
import scala.concurrent.{Await, ExecutionException, Future}
26+
import scala.util.{Failure, Success, Try}
27+
28+
object TestUtils {
29+
30+
val defaultTestTimeout: FiniteDuration = 3.minutes
31+
32+
def asyncTest(body: Future[_]): Unit = {
33+
Try(Await.result(body, defaultTestTimeout)) match {
34+
case Success(_) =>
35+
case Failure(err: ExecutionException) => throw err.getCause
36+
case Failure(err) => throw err
37+
}
38+
}
39+
40+
def getResourceUri(context: Context, resourceId: Int): URI = {
41+
URI.create(s"android.resource://${context.getPackageName}/$resourceId")
42+
}
43+
44+
}
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
/**
2+
* Wire
3+
* Copyright (C) 2018 Wire Swiss GmbH
4+
*
5+
* This program is free software: you can redistribute it and/or modify
6+
* it under the terms of the GNU General Public License as published by
7+
* the Free Software Foundation, either version 3 of the License, or
8+
* (at your option) any later version.
9+
*
10+
* This program is distributed in the hope that it will be useful,
11+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
* GNU General Public License for more details.
14+
*
15+
* You should have received a copy of the GNU General Public License
16+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
17+
*/
18+
package com.waz.zclient.assets2
19+
20+
import java.io._
21+
22+
import android.support.test.InstrumentationRegistry._
23+
import android.support.test.filters.MediumTest
24+
import android.support.test.rule.GrantPermissionRule
25+
import android.support.test.runner.AndroidJUnit4
26+
import com.waz.model.Mime
27+
import com.waz.model.errors._
28+
import com.waz.service.assets2.Asset.{Audio, Image, Video}
29+
import com.waz.service.assets2.Content
30+
import com.waz.utils.IoUtils
31+
import com.waz.zclient.TestUtils._
32+
import com.waz.zclient.dev.test.R
33+
import org.junit.Test
34+
import org.junit.runner.RunWith
35+
import android.Manifest
36+
import org.junit.Rule
37+
38+
import scala.concurrent.ExecutionContext.Implicits.global
39+
40+
@RunWith(classOf[AndroidJUnit4])
41+
@MediumTest
42+
class AssetDetailsServiceTest {
43+
44+
val uriHelper = new AndroidUriHelper(getContext)
45+
val detailsService = new AssetDetailsServiceImpl(uriHelper)(getContext, global)
46+
47+
@Rule
48+
def permissions: GrantPermissionRule =
49+
GrantPermissionRule.grant(Manifest.permission.WRITE_EXTERNAL_STORAGE)
50+
51+
@Test
52+
def extractForImageUri(): Unit = asyncTest {
53+
val uri = getResourceUri(getContext, R.raw.test_img)
54+
for {
55+
errorOrDetails <- detailsService.extract(Mime.Image.Png, Content.Uri(uri)).modelToEither
56+
} yield {
57+
lazy val errorMsg = s"Extracted details: $errorOrDetails"
58+
assert(errorOrDetails.isRight, errorMsg)
59+
val details = errorOrDetails.right.get
60+
61+
assert(details.isInstanceOf[Image], errorMsg)
62+
val imageDetails = details.asInstanceOf[Image]
63+
assert(imageDetails.dimensions.width > 0 && imageDetails.dimensions.height > 0, errorMsg)
64+
}
65+
}
66+
67+
@Test
68+
def extractForVideoUri(): Unit = asyncTest {
69+
val uri = getResourceUri(getContext, R.raw.test_video)
70+
for {
71+
errorOrDetails <- detailsService.extract(Mime.Video.MP4, Content.Uri(uri)).modelToEither
72+
} yield {
73+
lazy val errorMsg = s"Extracted details: $errorOrDetails"
74+
assert(errorOrDetails.isRight, errorMsg)
75+
val details = errorOrDetails.right.get
76+
77+
assert(details.isInstanceOf[Video], errorMsg)
78+
val videoDetails = details.asInstanceOf[Video]
79+
assert(videoDetails.dimensions.width > 0 && videoDetails.dimensions.height > 0 && !videoDetails.duration.isZero, errorMsg)
80+
}
81+
}
82+
83+
@Test
84+
def extractForVideoFile(): Unit = asyncTest {
85+
val uri = getResourceUri(getContext, R.raw.test_video)
86+
val file = new File(getContext.getExternalCacheDir, "test_video")
87+
IoUtils.copy(uriHelper.openInputStream(uri).get, new FileOutputStream(file))
88+
89+
for {
90+
errorOrDetails <- detailsService.extract(Mime.Video.MP4, Content.File(Mime.Video.MP4, file)).modelToEither
91+
} yield {
92+
lazy val errorMsg = s"Extracted details: $errorOrDetails"
93+
assert(errorOrDetails.isRight, errorMsg)
94+
val details = errorOrDetails.right.get
95+
96+
assert(details.isInstanceOf[Video], errorMsg)
97+
val videoDetails = details.asInstanceOf[Video]
98+
assert(videoDetails.dimensions.width > 0 && videoDetails.dimensions.height > 0 && !videoDetails.duration.isZero, errorMsg)
99+
}
100+
}
101+
102+
@Test
103+
def extractForAudioUri(): Unit = asyncTest {
104+
val uri = getResourceUri(getContext, R.raw.test_audio)
105+
for {
106+
errorOrDetails <- detailsService.extract(Mime.Audio.WAV, Content.Uri(uri)).modelToEither
107+
} yield {
108+
lazy val errorMsg = s"Extracted details: $errorOrDetails"
109+
assert(errorOrDetails.isRight, errorMsg)
110+
val details = errorOrDetails.right.get
111+
112+
assert(details.isInstanceOf[Audio], errorMsg)
113+
val audioDetails = details.asInstanceOf[Audio]
114+
assert(audioDetails.loudness.levels.nonEmpty && !audioDetails.duration.isZero, errorMsg)
115+
}
116+
}
117+
118+
@Test
119+
def extractForAudioFile(): Unit = asyncTest {
120+
val uri = getResourceUri(getContext, R.raw.test_audio)
121+
val testAudio = new File(getInstrumentation.getContext.getExternalCacheDir, "test_audio")
122+
testAudio.createNewFile()
123+
IoUtils.copy(uriHelper.openInputStream(uri).get, new FileOutputStream(testAudio))
124+
125+
for {
126+
errorOrDetails <- detailsService.extract(Mime.Audio.WAV, Content.File(Mime.Audio.WAV, testAudio)).modelToEither
127+
} yield {
128+
lazy val errorMsg = s"Extracted details: ${errorOrDetails.left.get.cause.get.toString}"
129+
assert(errorOrDetails.isRight, errorMsg)
130+
val details = errorOrDetails.right.get
131+
132+
assert(details.isInstanceOf[Audio], errorMsg)
133+
val audioDetails = details.asInstanceOf[Audio]
134+
assert(audioDetails.loudness.levels.nonEmpty && !audioDetails.duration.isZero, errorMsg)
135+
}
136+
}
137+
138+
139+
}
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
/**
2+
* Wire
3+
* Copyright (C) 2019 Wire Swiss GmbH
4+
*
5+
* This program is free software: you can redistribute it and/or modify
6+
* it under the terms of the GNU General Public License as published by
7+
* the Free Software Foundation, either version 3 of the License, or
8+
* (at your option) any later version.
9+
*
10+
* This program is distributed in the hope that it will be useful,
11+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
* GNU General Public License for more details.
14+
*
15+
* You should have received a copy of the GNU General Public License
16+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
17+
*/
18+
package com.waz.zclient.assets2
19+
20+
import java.io.File
21+
22+
import android.Manifest
23+
import android.support.test.InstrumentationRegistry
24+
import android.support.test.InstrumentationRegistry.getContext
25+
import android.support.test.filters.MediumTest
26+
import android.support.test.rule.GrantPermissionRule
27+
import android.support.test.runner.AndroidJUnit4
28+
import com.waz.model.Mime
29+
import com.waz.service.assets2
30+
import com.waz.service.assets2.AssetStorageImpl.AssetDao
31+
import com.waz.service.assets2._
32+
import com.waz.sync.client.AssetClient2
33+
import com.waz.sync.client.AssetClient2.Retention
34+
import com.waz.threading.Threading
35+
import com.waz.utils.events.EventContext
36+
import com.waz.utils.wrappers.DB
37+
import com.waz.zclient.TestUtils.{asyncTest, getResourceUri}
38+
import com.waz.zclient.dev.test.R
39+
import com.waz.zclient.storage.GeneralStorageTest.TestSingleDaoDb
40+
import org.junit.runner.RunWith
41+
import org.junit.{After, Before, Rule, Test}
42+
import org.mockito.Mockito
43+
import com.waz.model.errors._
44+
import com.waz.sync.SyncServiceHandle
45+
46+
import scala.concurrent.ExecutionContext.Implicits.global
47+
import scala.concurrent.duration._
48+
import scala.util.Success
49+
50+
@RunWith(classOf[AndroidJUnit4])
51+
@MediumTest
52+
class AssetServiceTest {
53+
54+
val assetsClient: AssetClient2 = Mockito.mock(classOf[AssetClient2], "assetsClient")
55+
val uriHelper: UriHelper = Mockito.mock(classOf[AndroidUriHelper], "uriHelper")
56+
val syncHandler: SyncServiceHandle = Mockito.mock(classOf[SyncServiceHandle], "syncHandler")
57+
58+
val lruCacheDirectory = new File(getContext.getCacheDir, s"assets_${System.currentTimeMillis()}")
59+
val rawCacheDirectory = new File(getContext.getCacheDir, s"raw_assets_${System.currentTimeMillis()}")
60+
61+
val uriHelperImpl = new AndroidUriHelper(getContext)
62+
val detailsService = new AssetDetailsServiceImpl(uriHelperImpl)(getContext, global)
63+
val contentCache = new AssetContentCacheImpl(cacheDirectory = lruCacheDirectory, directorySizeThreshold = 1024 * 1024 * 200L, sizeCheckingInterval = 30.seconds)(Threading.BlockingIO, EventContext.Global)
64+
val uploadContentCache = new UploadAssetContentCacheImpl(rawCacheDirectory)(Threading.BlockingIO)
65+
val transformationsService = new AssetTransformationsServiceImpl(List(new ImageDownscalingCompressing(new AndroidImageRecoder)))
66+
val restrictionsService = new AssetRestrictionsServiceImpl(uriHelperImpl, None)
67+
val previewService = new AssetPreviewServiceImpl()(getContext, global)
68+
69+
lazy val assetService = new AssetServiceImpl(
70+
assetStorage,
71+
uploadAssetStorage,
72+
downloadAssetStorage,
73+
detailsService,
74+
previewService,
75+
transformationsService,
76+
restrictionsService,
77+
uriHelper,
78+
contentCache,
79+
uploadContentCache,
80+
assetsClient,
81+
syncHandler
82+
)(global)
83+
84+
val DatabaseName = s"test_db_${System.currentTimeMillis()}"
85+
var testDB: DB = _
86+
87+
var assetStorage: AssetStorage = _
88+
var uploadAssetStorage: UploadAssetStorage = _
89+
var downloadAssetStorage: DownloadAssetStorage = _
90+
91+
@Rule
92+
def permissions: GrantPermissionRule =
93+
GrantPermissionRule.grant(Manifest.permission.WRITE_EXTERNAL_STORAGE)
94+
95+
@Before
96+
def initializeDB(): Unit = {
97+
val context = InstrumentationRegistry.getTargetContext
98+
testDB = new TestSingleDaoDb(context, DatabaseName, AssetDao).getWritableDatabase
99+
100+
downloadAssetStorage = new assets2.DownloadAssetStorageImpl(context, testDB)(Threading.BlockingIO)
101+
uploadAssetStorage = new assets2.UploadAssetStorageImpl(context, testDB)(Threading.BlockingIO)
102+
assetStorage = new assets2.AssetStorageImpl(context, testDB, Threading.BlockingIO)
103+
}
104+
105+
@After
106+
def removeDb(): Unit = {
107+
val context = InstrumentationRegistry.getTargetContext
108+
testDB.close()
109+
context.deleteDatabase(DatabaseName)
110+
}
111+
112+
@Test
113+
def extractForImageUri(): Unit = asyncTest {
114+
val uri = getResourceUri(getContext, R.raw.test_video_large_preview)
115+
val content = ContentForUpload("test_video_large_preview.mp4", Content.Uri(uri))
116+
117+
Mockito.when(uriHelper.extractMime(uri)).thenReturn(Success(Mime.Video.MP4))
118+
Mockito.when(uriHelper.openInputStream(uri)).thenCallRealMethod()
119+
120+
for {
121+
videoAsset <- assetService.createAndSaveUploadAsset(content, NoEncryption, public = true, Retention.Volatile, None)
122+
errorOrUpdatedAsset <- assetService.createAndSavePreview(videoAsset).modelToEither
123+
} yield {
124+
lazy val errorMsg = s"Updated asset: $errorOrUpdatedAsset"
125+
assert(errorOrUpdatedAsset.isRight, errorMsg)
126+
// val details = errorOrUpdatedAsset.right.get
127+
128+
// assert(details.isInstanceOf[Image], errorMsg)
129+
// val imageDetails = details.asInstanceOf[Image]
130+
// assert(imageDetails.dimensions.width > 0 && imageDetails.dimensions.height > 0, errorMsg)
131+
}
132+
}
133+
134+
}

0 commit comments

Comments
 (0)