diff --git a/app/build.gradle b/app/build.gradle
index 6723dfeddb..5782c96e7d 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -143,8 +143,8 @@ dependencies {
testImplementation 'org.robolectric:shadows-multidex:4.2'
}
-def canonicalVersionCode = 188
-def canonicalVersionName = "1.11.0"
+def canonicalVersionCode = 189
+def canonicalVersionName = "1.11.1"
def postFixSize = 10
def abiPostFix = ['armeabi-v7a' : 1,
@@ -194,8 +194,8 @@ android {
versionCode canonicalVersionCode * postFixSize
versionName canonicalVersionName
- minSdkVersion 23
- targetSdkVersion 30
+ minSdkVersion androidMinimumSdkVersion
+ targetSdkVersion androidCompileSdkVersion
multiDexEnabled = true
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 32869dd1f1..a95a9baebe 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -221,7 +221,12 @@
+ android:parentActivityName="org.thoughtcrime.securesms.loki.activities.HomeActivity"
+ android:theme="@style/Theme.Session.DayNight.FlatActionBar">
+
+
> {
+ future.addListener(object : ListenableFuture.Listener> {
- override fun onSuccess(result: Pair) {
- val audioSlide = AudioSlide(this@ConversationActivityV2, result.first, result.second!!, MediaTypes.AUDIO_AAC, true)
+ override fun onSuccess(result: Pair) {
+ val audioSlide = AudioSlide(this@ConversationActivityV2, result.first, result.second, MediaTypes.AUDIO_AAC, true)
val slideDeck = SlideDeck()
slideDeck.addSlide(audioSlide)
sendAttachments(slideDeck.asAttachments(), null)
diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/input_bar/InputBar.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/input_bar/InputBar.kt
index cfb1e38726..f10ad10e2b 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/input_bar/InputBar.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/input_bar/InputBar.kt
@@ -122,7 +122,7 @@ class InputBar : RelativeLayout, InputBarEditTextDelegate, QuoteViewDelegate, Li
val maxContentWidth = (screenWidth - 2 * resources.getDimension(R.dimen.medium_spacing) - toPx(16, resources) - toPx(30, resources)).roundToInt()
val sender = if (message.isOutgoing) TextSecurePreferences.getLocalNumber(context)!! else message.individualRecipient.address.serialize()
quoteView.bind(sender, message.body, attachments,
- thread, true, maxContentWidth, message.isOpenGroupInvitation, message.threadId, glide)
+ thread, true, maxContentWidth, message.isOpenGroupInvitation, message.threadId, false, glide)
// The 6 DP below is the padding the quote view applies to itself, which isn't included in the
// intrinsic height calculation.
val quoteViewIntrinsicHeight = quoteView.getIntrinsicHeight(maxContentWidth) + toPx(6, resources)
diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/QuoteView.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/QuoteView.kt
index 2ab119950a..b03b4be278 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/QuoteView.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/QuoteView.kt
@@ -110,7 +110,8 @@ class QuoteView : LinearLayout {
// region Updating
fun bind(authorPublicKey: String, body: String?, attachments: SlideDeck?, thread: Recipient,
- isOutgoingMessage: Boolean, maxContentWidth: Int, isOpenGroupInvitation: Boolean, threadID: Long, glide: GlideRequests) {
+ isOutgoingMessage: Boolean, maxContentWidth: Int, isOpenGroupInvitation: Boolean, threadID: Long,
+ isOriginalMissing: Boolean, glide: GlideRequests) {
val contactDB = DatabaseFactory.getSessionContactDatabase(context)
// Reduce the max body text view line count to 2 if this is a group thread because
// we'll be showing the author text view and we don't want the overall quote view height
@@ -128,7 +129,7 @@ class QuoteView : LinearLayout {
quoteViewBodyTextView.text = if (isOpenGroupInvitation) resources.getString(R.string.open_group_invitation_view__open_group_invitation) else MentionUtilities.highlightMentions((body ?: "").toSpannable(), threadID, context);
quoteViewBodyTextView.setTextColor(getTextColor(isOutgoingMessage))
// Accent line / attachment preview
- val hasAttachments = (attachments != null && attachments.asAttachments().isNotEmpty())
+ val hasAttachments = (attachments != null && attachments.asAttachments().isNotEmpty()) && !isOriginalMissing
quoteViewAccentLine.isVisible = !hasAttachments
quoteViewAttachmentPreviewContainer.isVisible = hasAttachments
if (!hasAttachments) {
@@ -136,8 +137,7 @@ class QuoteView : LinearLayout {
accentLineLayoutParams.height = getIntrinsicContentHeight(maxContentWidth) // Match the intrinsic * content * height
quoteViewAccentLine.layoutParams = accentLineLayoutParams
quoteViewAccentLine.setBackgroundColor(getLineColor(isOutgoingMessage))
- } else {
- attachments!!
+ } else if (attachments != null) {
quoteViewAttachmentPreviewImageView.imageTintList = ColorStateList.valueOf(ResourcesCompat.getColor(resources, R.color.white, context.theme))
val backgroundColorID = if (UiModeUtilities.isDayUiMode(context)) R.color.black else R.color.accent
val backgroundColor = ResourcesCompat.getColor(resources, backgroundColorID, context.theme)
diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageContentView.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageContentView.kt
index 16721b1625..e2587c9dac 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageContentView.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageContentView.kt
@@ -86,8 +86,14 @@ class VisibleMessageContentView : LinearLayout {
// quote view content area's start margin. This unfortunately has to be calculated manually
// here to get the layout right.
val maxContentWidth = (maxWidth - 2 * resources.getDimension(R.dimen.medium_spacing) - toPx(16, resources)).roundToInt()
- quoteView.bind(quote.author.toString(), quote.text, quote.attachment, thread,
- message.isOutgoing, maxContentWidth, message.isOpenGroupInvitation, message.threadId, glide)
+ val quoteText = if (quote.isOriginalMissing) {
+ context.getString(R.string.QuoteView_original_missing)
+ } else {
+ quote.text
+ }
+ quoteView.bind(quote.author.toString(), quoteText, quote.attachment, thread,
+ message.isOutgoing, maxContentWidth, message.isOpenGroupInvitation, message.threadId,
+ quote.isOriginalMissing, glide)
mainContainer.addView(quoteView)
val bodyTextView = VisibleMessageContentView.getBodyTextView(context, message, searchQuery)
ViewUtil.setPaddingTop(bodyTextView, 0)
diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VoiceMessageView.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VoiceMessageView.kt
index b957b0a166..894a078b30 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VoiceMessageView.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VoiceMessageView.kt
@@ -10,9 +10,11 @@ import android.widget.RelativeLayout
import androidx.core.view.isVisible
import kotlinx.android.synthetic.main.view_voice_message.view.*
import network.loki.messenger.R
+import org.session.libsession.messaging.sending_receiving.attachments.DatabaseAttachment
import org.thoughtcrime.securesms.audio.AudioSlidePlayer
import org.thoughtcrime.securesms.components.CornerMask
import org.thoughtcrime.securesms.conversation.v2.utilities.MessageBubbleUtilities
+import org.thoughtcrime.securesms.database.DatabaseFactory
import org.thoughtcrime.securesms.database.model.MmsMessageRecord
import java.util.concurrent.TimeUnit
import kotlin.math.roundToInt
@@ -44,27 +46,30 @@ class VoiceMessageView : LinearLayout, AudioSlidePlayer.Listener {
val audio = message.slideDeck.audioSlide!!
val player = AudioSlidePlayer.createFor(context, audio, this)
this.player = player
- isPreparing = true
- if (!audio.isPendingDownload && !audio.isInProgress) {
- player.play(0.0)
- }
voiceMessageViewLoader.isVisible = audio.isPendingDownload
val cornerRadii = MessageBubbleUtilities.calculateRadii(context, isStartOfMessageCluster, isEndOfMessageCluster, message.isOutgoing)
cornerMask.setTopLeftRadius(cornerRadii[0])
cornerMask.setTopRightRadius(cornerRadii[1])
cornerMask.setBottomRightRadius(cornerRadii[2])
cornerMask.setBottomLeftRadius(cornerRadii[3])
+
+ // only process audio if downloaded
+ if (audio.isPendingDownload || audio.isInProgress) return
+
+ (audio.asAttachment() as? DatabaseAttachment)?.let { attachment ->
+ DatabaseFactory.getAttachmentDatabase(context).getAttachmentAudioExtras(attachment.attachmentId)?.let { audioExtras ->
+ if (audioExtras.durationMs > 0) {
+ duration = audioExtras.durationMs
+ voiceMessageViewDurationTextView.visibility = View.VISIBLE
+ voiceMessageViewDurationTextView.text = String.format("%01d:%02d",
+ TimeUnit.MILLISECONDS.toMinutes(audioExtras.durationMs),
+ TimeUnit.MILLISECONDS.toSeconds(audioExtras.durationMs))
+ }
+ }
+ }
}
- override fun onPlayerStart(player: AudioSlidePlayer) {
- if (!isPreparing) { return }
- isPreparing = false
- duration = player.duration
- voiceMessageViewDurationTextView.text = String.format("%01d:%02d",
- TimeUnit.MILLISECONDS.toMinutes(duration),
- TimeUnit.MILLISECONDS.toSeconds(duration))
- player.stop()
- }
+ override fun onPlayerStart(player: AudioSlidePlayer) {}
override fun onPlayerProgress(player: AudioSlidePlayer, progress: Double, unused: Long) {
if (progress == 1.0) {
diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.java
index ce2ddba24a..87b59132d4 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.java
@@ -63,6 +63,7 @@ import org.thoughtcrime.securesms.attachments.MmsNotificationAttachment;
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper;
import org.thoughtcrime.securesms.database.model.MediaMmsMessageRecord;
import org.thoughtcrime.securesms.database.model.MessageRecord;
+import org.thoughtcrime.securesms.database.model.MmsMessageRecord;
import org.thoughtcrime.securesms.database.model.NotificationMmsMessageRecord;
import org.thoughtcrime.securesms.database.model.Quote;
import org.thoughtcrime.securesms.mms.MmsException;
@@ -881,6 +882,20 @@ public class MmsDatabase extends MessagingDatabase {
}
}
+ public void deleteQuotedFromMessages(MessageRecord toDeleteRecord) {
+ String query = THREAD_ID + " = ?";
+ Cursor threadMmsCursor = rawQuery(query, new String[]{String.valueOf(toDeleteRecord.getThreadId())});
+ Reader reader = readerFor(threadMmsCursor);
+ MmsMessageRecord messageRecord;
+
+ while ((messageRecord = (MmsMessageRecord) reader.getNext()) != null) {
+ if (messageRecord.getQuote() != null && toDeleteRecord.getDateSent() == messageRecord.getQuote().getId()) {
+ setQuoteMissing(messageRecord.getId());
+ }
+ }
+ reader.close();
+ }
+
public boolean delete(long messageId) {
long threadId = getThreadIdForMessage(messageId);
AttachmentDatabase attachmentDatabase = DatabaseFactory.getAttachmentDatabase(context);
@@ -889,6 +904,12 @@ public class MmsDatabase extends MessagingDatabase {
GroupReceiptDatabase groupReceiptDatabase = DatabaseFactory.getGroupReceiptDatabase(context);
groupReceiptDatabase.deleteRowsForMessage(messageId);
+ MessageRecord toDelete;
+ try (Cursor messageCursor = getMessage(messageId)) {
+ toDelete = readerFor(messageCursor).getNext();
+ }
+
+ deleteQuotedFromMessages(toDelete);
SQLiteDatabase database = databaseHelper.getWritableDatabase();
database.delete(TABLE_NAME, ID_WHERE, new String[] {messageId+""});
boolean threadDeleted = DatabaseFactory.getThreadDatabase(context).update(threadId, false);
@@ -1066,6 +1087,14 @@ public class MmsDatabase extends MessagingDatabase {
return new OutgoingMessageReader(message, threadId);
}
+ public int setQuoteMissing(long messageId) {
+ ContentValues contentValues = new ContentValues();
+ contentValues.put(QUOTE_MISSING, 1);
+ SQLiteDatabase database = databaseHelper.getWritableDatabase();
+ int rows = database.update(TABLE_NAME, contentValues, ID + " = ?", new String[]{ String.valueOf(messageId) });
+ return rows;
+ }
+
public static class Status {
public static final int DOWNLOAD_INITIALIZED = 1;
public static final int DOWNLOAD_NO_CONNECTIVITY = 2;
diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java
index 6706f5fe77..f66479c41f 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java
@@ -514,6 +514,12 @@ public class SmsDatabase extends MessagingDatabase {
Log.i("MessageDatabase", "Deleting: " + messageId);
SQLiteDatabase db = databaseHelper.getWritableDatabase();
long threadId = getThreadIdForMessage(messageId);
+ try {
+ SmsMessageRecord toDelete = getMessage(messageId);
+ DatabaseFactory.getMmsDatabase(context).deleteQuotedFromMessages(toDelete);
+ } catch (NoSuchMessageException e) {
+ Log.e(TAG, "Couldn't find message record for messageId "+messageId, e);
+ }
db.delete(TABLE_NAME, ID_WHERE, new String[] {messageId+""});
boolean threadDeleted = DatabaseFactory.getThreadDatabase(context).update(threadId, false);
notifyConversationListeners(threadId);
diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt
index 15b91048e2..7fe982a9e3 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt
@@ -27,7 +27,6 @@ import org.session.libsignal.utilities.KeyHelper
import org.session.libsignal.utilities.guava.Optional
import org.thoughtcrime.securesms.ApplicationContext
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper
-import org.thoughtcrime.securesms.database.model.MessageRecord
import org.thoughtcrime.securesms.jobs.RetrieveProfileAvatarJob
import org.thoughtcrime.securesms.loki.api.OpenGroupManager
import org.thoughtcrime.securesms.loki.database.LokiThreadDatabase
@@ -190,7 +189,7 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context,
override fun resumeMessageSendJobIfNeeded(messageSendJobID: String) {
val job = DatabaseFactory.getSessionJobDatabase(context).getMessageSendJob(messageSendJobID) ?: return
- JobQueue.shared.add(job)
+ JobQueue.shared.resumePendingSendMessage(job)
}
override fun isJobCanceled(job: Job): Boolean {
diff --git a/app/src/main/java/org/thoughtcrime/securesms/loki/api/PrepareAttachmentAudioExtrasJob.kt b/app/src/main/java/org/thoughtcrime/securesms/loki/api/PrepareAttachmentAudioExtrasJob.kt
index 6c1a96d1de..fa92ba3d29 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/loki/api/PrepareAttachmentAudioExtrasJob.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/loki/api/PrepareAttachmentAudioExtrasJob.kt
@@ -9,10 +9,11 @@ import org.session.libsession.messaging.utilities.Data
import org.session.libsession.messaging.sending_receiving.attachments.Attachment
import org.session.libsession.messaging.sending_receiving.attachments.AttachmentId
import org.session.libsession.messaging.sending_receiving.attachments.DatabaseAttachmentAudioExtras
+import org.session.libsession.utilities.DecodedAudio
+import org.session.libsession.utilities.InputStreamMediaDataSource
import org.thoughtcrime.securesms.database.DatabaseFactory
import org.thoughtcrime.securesms.jobmanager.Job
import org.thoughtcrime.securesms.jobs.BaseJob
-import org.thoughtcrime.securesms.loki.utilities.DecodedAudio
import org.thoughtcrime.securesms.mms.PartAuthority
import java.io.InputStream
import java.lang.IllegalStateException
@@ -133,35 +134,4 @@ class PrepareAttachmentAudioExtrasJob : BaseJob {
/** Gets dispatched once the audio extras have been updated. */
data class AudioExtrasUpdatedEvent(val attachmentId: AttachmentId)
-
- @RequiresApi(Build.VERSION_CODES.M)
- private class InputStreamMediaDataSource: MediaDataSource {
-
- private val data: ByteArray
-
- constructor(inputStream: InputStream): super() {
- this.data = inputStream.readBytes()
- }
-
- override fun readAt(position: Long, buffer: ByteArray, offset: Int, size: Int): Int {
- val length: Int = data.size
- if (position >= length) {
- return -1 // -1 indicates EOF
- }
- var actualSize = size
- if (position + size > length) {
- actualSize -= (position + size - length).toInt()
- }
- System.arraycopy(data, position.toInt(), buffer, offset, actualSize)
- return actualSize
- }
-
- override fun getSize(): Long {
- return data.size.toLong()
- }
-
- override fun close() {
- // We don't need to close the wrapped stream.
- }
- }
}
\ No newline at end of file
diff --git a/app/src/main/java/org/thoughtcrime/securesms/loki/views/WaveformSeekBar.kt b/app/src/main/java/org/thoughtcrime/securesms/loki/views/WaveformSeekBar.kt
index 86028521f1..df74bd9ca0 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/loki/views/WaveformSeekBar.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/loki/views/WaveformSeekBar.kt
@@ -14,7 +14,7 @@ import android.view.ViewConfiguration
import android.view.animation.DecelerateInterpolator
import androidx.core.math.MathUtils
import network.loki.messenger.R
-import org.thoughtcrime.securesms.loki.utilities.byteToNormalizedFloat
+import org.session.libsession.utilities.byteToNormalizedFloat
import kotlin.math.abs
import kotlin.math.max
import kotlin.math.min
diff --git a/build.gradle b/build.gradle
index 3343145c60..884e6ed085 100644
--- a/build.gradle
+++ b/build.gradle
@@ -50,6 +50,7 @@ allprojects {
}
project.ext {
+ androidMinimumSdkVersion = 23
androidCompileSdkVersion = 30
}
}
\ No newline at end of file
diff --git a/libsession/build.gradle b/libsession/build.gradle
index 6763e0cff5..8bd46532eb 100644
--- a/libsession/build.gradle
+++ b/libsession/build.gradle
@@ -6,6 +6,10 @@ plugins {
android {
compileSdkVersion androidCompileSdkVersion
+ defaultConfig {
+ minSdkVersion androidMinimumSdkVersion
+ }
+
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
diff --git a/libsession/src/main/java/org/session/libsession/database/MessageDataProvider.kt b/libsession/src/main/java/org/session/libsession/database/MessageDataProvider.kt
index 4cf93a429d..37d6d44e4a 100644
--- a/libsession/src/main/java/org/session/libsession/database/MessageDataProvider.kt
+++ b/libsession/src/main/java/org/session/libsession/database/MessageDataProvider.kt
@@ -20,6 +20,7 @@ interface MessageDataProvider {
fun getSignalAttachmentPointer(attachmentId: Long): SignalServiceAttachmentPointer?
fun setAttachmentState(attachmentState: AttachmentState, attachmentId: Long, messageID: Long)
fun insertAttachment(messageId: Long, attachmentId: AttachmentId, stream : InputStream)
+ fun updateAudioAttachmentDuration(attachmentId: AttachmentId, durationMs: Long)
fun isOutgoingMessage(timestamp: Long): Boolean
fun handleSuccessfulAttachmentUpload(attachmentId: Long, attachmentStream: SignalServiceAttachmentStream, attachmentKey: ByteArray, uploadResult: UploadResult)
fun handleFailedAttachmentUpload(attachmentId: Long)
diff --git a/libsession/src/main/java/org/session/libsession/messaging/jobs/AttachmentDownloadJob.kt b/libsession/src/main/java/org/session/libsession/messaging/jobs/AttachmentDownloadJob.kt
index a5aaeaa429..605052f85c 100644
--- a/libsession/src/main/java/org/session/libsession/messaging/jobs/AttachmentDownloadJob.kt
+++ b/libsession/src/main/java/org/session/libsession/messaging/jobs/AttachmentDownloadJob.kt
@@ -1,16 +1,24 @@
package org.session.libsession.messaging.jobs
+import android.content.ContentResolver
+import android.media.MediaDataSource
+import android.media.MediaExtractor
import okhttp3.HttpUrl
import org.session.libsession.messaging.MessagingModuleConfiguration
import org.session.libsession.messaging.open_groups.OpenGroupAPIV2
import org.session.libsession.messaging.sending_receiving.attachments.AttachmentState
+import org.session.libsession.messaging.sending_receiving.attachments.DatabaseAttachment
import org.session.libsession.messaging.utilities.Data
+import org.session.libsession.snode.OnionRequestAPI
+import org.session.libsession.utilities.DecodedAudio
import org.session.libsession.utilities.DownloadUtilities
+import org.session.libsession.utilities.InputStreamMediaDataSource
import org.session.libsignal.streams.AttachmentCipherInputStream
import org.session.libsignal.utilities.Base64
import org.session.libsignal.utilities.Log
import java.io.File
import java.io.FileInputStream
+import java.io.InputStream
class AttachmentDownloadJob(val attachmentID: Long, val databaseMessageID: Long) : Job {
override var delegate: JobDelegate? = null
@@ -37,46 +45,64 @@ class AttachmentDownloadJob(val attachmentID: Long, val databaseMessageID: Long)
val storage = MessagingModuleConfiguration.shared.storage
val messageDataProvider = MessagingModuleConfiguration.shared.messageDataProvider
val handleFailure: (java.lang.Exception) -> Unit = { exception ->
- if (exception == Error.NoAttachment) {
+ if (exception == Error.NoAttachment
+ || (exception is OnionRequestAPI.HTTPRequestFailedAtDestinationException && exception.statusCode == 400)) {
messageDataProvider.setAttachmentState(AttachmentState.FAILED, attachmentID, databaseMessageID)
this.handlePermanentFailure(exception)
} else {
this.handleFailure(exception)
}
}
+ var tempFile: File? = null
try {
val attachment = messageDataProvider.getDatabaseAttachment(attachmentID)
?: return handleFailure(Error.NoAttachment)
messageDataProvider.setAttachmentState(AttachmentState.STARTED, attachmentID, this.databaseMessageID)
- val tempFile = createTempFile()
+ tempFile = createTempFile()
val threadID = storage.getThreadIdForMms(databaseMessageID)
val openGroupV2 = storage.getV2OpenGroup(threadID)
- val inputStream = if (openGroupV2 == null) {
+ if (openGroupV2 == null) {
DownloadUtilities.downloadFile(tempFile, attachment.url)
- // Assume we're retrieving an attachment for an open group server if the digest is not set
- if (attachment.digest?.size ?: 0 == 0 || attachment.key.isNullOrEmpty()) {
- FileInputStream(tempFile)
- } else {
- AttachmentCipherInputStream.createForAttachment(tempFile, attachment.size, Base64.decode(attachment.key), attachment.digest)
- }
} else {
val url = HttpUrl.parse(attachment.url)!!
val fileID = url.pathSegments().last()
OpenGroupAPIV2.download(fileID.toLong(), openGroupV2.room, openGroupV2.server).get().let {
tempFile.writeBytes(it)
}
- FileInputStream(tempFile)
}
+ val inputStream = getInputStream(tempFile, attachment)
+
messageDataProvider.insertAttachment(databaseMessageID, attachment.attachmentId, inputStream)
+ if (attachment.contentType.startsWith("audio/")) {
+ // process the duration
+ try {
+ InputStreamMediaDataSource(getInputStream(tempFile, attachment)).use { mediaDataSource ->
+ val durationMs = (DecodedAudio.create(mediaDataSource).totalDuration / 1000.0).toLong()
+ messageDataProvider.updateAudioAttachmentDuration(attachment.attachmentId, durationMs)
+ }
+ } catch (e: Exception) {
+ Log.e("Loki", "Couldn't process audio attachment", e)
+ }
+ }
tempFile.delete()
handleSuccess()
} catch (e: Exception) {
+ tempFile?.delete()
return handleFailure(e)
}
}
+ private fun getInputStream(tempFile: File, attachment: DatabaseAttachment): InputStream {
+ // Assume we're retrieving an attachment for an open group server if the digest is not set
+ return if (attachment.digest?.size ?: 0 == 0 || attachment.key.isNullOrEmpty()) {
+ FileInputStream(tempFile)
+ } else {
+ AttachmentCipherInputStream.createForAttachment(tempFile, attachment.size, Base64.decode(attachment.key), attachment.digest)
+ }
+ }
+
private fun handleSuccess() {
- Log.w(AttachmentUploadJob.TAG, "Attachment downloaded successfully.")
+ Log.w("AttachmentDownloadJob", "Attachment downloaded successfully.")
delegate?.handleJobSucceeded(this)
}
diff --git a/libsession/src/main/java/org/session/libsession/messaging/jobs/AttachmentUploadJob.kt b/libsession/src/main/java/org/session/libsession/messaging/jobs/AttachmentUploadJob.kt
index e4cc76974f..332d674115 100644
--- a/libsession/src/main/java/org/session/libsession/messaging/jobs/AttachmentUploadJob.kt
+++ b/libsession/src/main/java/org/session/libsession/messaging/jobs/AttachmentUploadJob.kt
@@ -11,6 +11,8 @@ import org.session.libsession.messaging.messages.Message
import org.session.libsession.messaging.open_groups.OpenGroupAPIV2
import org.session.libsession.messaging.sending_receiving.MessageSender
import org.session.libsession.messaging.utilities.Data
+import org.session.libsession.utilities.DecodedAudio
+import org.session.libsession.utilities.InputStreamMediaDataSource
import org.session.libsession.utilities.UploadResult
import org.session.libsignal.streams.AttachmentCipherOutputStream
import org.session.libsignal.messages.SignalServiceAttachmentStream
@@ -108,7 +110,22 @@ class AttachmentUploadJob(val attachmentID: Long, val threadID: String, val mess
private fun handleSuccess(attachment: SignalServiceAttachmentStream, attachmentKey: ByteArray, uploadResult: UploadResult) {
Log.d(TAG, "Attachment uploaded successfully.")
delegate?.handleJobSucceeded(this)
- MessagingModuleConfiguration.shared.messageDataProvider.handleSuccessfulAttachmentUpload(attachmentID, attachment, attachmentKey, uploadResult)
+ val messageDataProvider = MessagingModuleConfiguration.shared.messageDataProvider
+ messageDataProvider.handleSuccessfulAttachmentUpload(attachmentID, attachment, attachmentKey, uploadResult)
+ if (attachment.contentType.startsWith("audio/")) {
+ // process the duration
+ try {
+ val inputStream = messageDataProvider.getAttachmentStream(attachmentID)!!.inputStream!!
+ InputStreamMediaDataSource(inputStream).use { mediaDataSource ->
+ val durationMs = (DecodedAudio.create(mediaDataSource).totalDuration / 1000.0).toLong()
+ messageDataProvider.getDatabaseAttachment(attachmentID)?.attachmentId?.let { attachmentId ->
+ messageDataProvider.updateAudioAttachmentDuration(attachmentId, durationMs)
+ }
+ }
+ } catch (e: Exception) {
+ Log.e("Loki", "Couldn't process audio attachment", e)
+ }
+ }
MessagingModuleConfiguration.shared.storage.resumeMessageSendJobIfNeeded(messageSendJobID)
}
@@ -140,13 +157,13 @@ class AttachmentUploadJob(val attachmentID: Long, val threadID: String, val mess
val kryo = Kryo()
kryo.isRegistrationRequired = false
val serializedMessage = ByteArray(4096)
- val output = Output(serializedMessage)
- kryo.writeObject(output, message)
+ val output = Output(serializedMessage, Job.MAX_BUFFER_SIZE)
+ kryo.writeClassAndObject(output, message)
output.close()
return Data.Builder()
.putLong(ATTACHMENT_ID_KEY, attachmentID)
.putString(THREAD_ID_KEY, threadID)
- .putByteArray(MESSAGE_KEY, serializedMessage)
+ .putByteArray(MESSAGE_KEY, output.toBytes())
.putString(MESSAGE_SEND_JOB_ID_KEY, messageSendJobID)
.build()
}
@@ -157,18 +174,24 @@ class AttachmentUploadJob(val attachmentID: Long, val threadID: String, val mess
class Factory: Job.Factory {
- override fun create(data: Data): AttachmentUploadJob {
+ override fun create(data: Data): AttachmentUploadJob? {
val serializedMessage = data.getByteArray(MESSAGE_KEY)
val kryo = Kryo()
kryo.isRegistrationRequired = false
val input = Input(serializedMessage)
- val message = kryo.readObject(input, Message::class.java)
+ val message: Message
+ try {
+ message = kryo.readClassAndObject(input) as Message
+ } catch (e: Exception) {
+ Log.e("Loki","Couldn't serialize the AttachmentUploadJob", e)
+ return null
+ }
input.close()
return AttachmentUploadJob(
- data.getLong(ATTACHMENT_ID_KEY),
- data.getString(THREAD_ID_KEY)!!,
- message,
- data.getString(MESSAGE_SEND_JOB_ID_KEY)!!
+ data.getLong(ATTACHMENT_ID_KEY),
+ data.getString(THREAD_ID_KEY)!!,
+ message,
+ data.getString(MESSAGE_SEND_JOB_ID_KEY)!!
)
}
}
diff --git a/libsession/src/main/java/org/session/libsession/messaging/jobs/JobQueue.kt b/libsession/src/main/java/org/session/libsession/messaging/jobs/JobQueue.kt
index e6803e89dc..9ced85b110 100644
--- a/libsession/src/main/java/org/session/libsession/messaging/jobs/JobQueue.kt
+++ b/libsession/src/main/java/org/session/libsession/messaging/jobs/JobQueue.kt
@@ -23,6 +23,7 @@ class JobQueue : JobDelegate {
private val attachmentDispatcher = Executors.newFixedThreadPool(2).asCoroutineDispatcher()
private val scope = GlobalScope + SupervisorJob()
private val queue = Channel(UNLIMITED)
+ private val pendingJobIds = mutableSetOf()
val timer = Timer()
@@ -86,6 +87,19 @@ class JobQueue : JobDelegate {
MessagingModuleConfiguration.shared.storage.persistJob(job)
}
+ fun resumePendingSendMessage(job: Job) {
+ val id = job.id ?: run {
+ Log.e("Loki", "tried to resume pending send job with no ID")
+ return
+ }
+ if (!pendingJobIds.add(id)) {
+ Log.e("Loki","tried to re-queue pending/in-progress job")
+ return
+ }
+ queue.offer(job)
+ Log.d("Loki", "resumed pending send message $id")
+ }
+
fun resumePendingJobs() {
if (hasResumedPendingJobs) {
Log.d("Loki", "resumePendingJobs() should only be called once.")
@@ -120,6 +134,7 @@ class JobQueue : JobDelegate {
override fun handleJobSucceeded(job: Job) {
val jobId = job.id ?: return
MessagingModuleConfiguration.shared.storage.markJobAsSucceeded(jobId)
+ pendingJobIds.remove(jobId)
}
override fun handleJobFailed(job: Job, error: Exception) {
@@ -169,4 +184,7 @@ class JobQueue : JobDelegate {
val maxBackoff = (10 * 60).toDouble() // 10 minutes
return (1000 * 0.25 * min(maxBackoff, (2.0).pow(job.failureCount))).roundToLong()
}
+
+ private fun Job.isSend() = this is MessageSendJob || this is AttachmentUploadJob
+
}
\ No newline at end of file
diff --git a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/pollers/OpenGroupPollerV2.kt b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/pollers/OpenGroupPollerV2.kt
index 7cfc44801f..b08fa66f34 100644
--- a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/pollers/OpenGroupPollerV2.kt
+++ b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/pollers/OpenGroupPollerV2.kt
@@ -94,7 +94,9 @@ class OpenGroupPollerV2(private val server: String, private val executorService:
if (actualMax > 0) {
storage.setLastMessageServerID(room, server, actualMax)
}
- JobQueue.shared.add(TrimThreadJob(threadId))
+ if (messages.isNotEmpty()) {
+ JobQueue.shared.add(TrimThreadJob(threadId))
+ }
}
private fun handleDeletedMessages(room: String, openGroupID: String, deletions: List) {
diff --git a/app/src/main/java/org/thoughtcrime/securesms/loki/utilities/DecodedAudio.kt b/libsession/src/main/java/org/session/libsession/utilities/DecodedAudio.kt
similarity index 94%
rename from app/src/main/java/org/thoughtcrime/securesms/loki/utilities/DecodedAudio.kt
rename to libsession/src/main/java/org/session/libsession/utilities/DecodedAudio.kt
index 77f7f88983..ef21abe4c0 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/loki/utilities/DecodedAudio.kt
+++ b/libsession/src/main/java/org/session/libsession/utilities/DecodedAudio.kt
@@ -1,4 +1,4 @@
-package org.thoughtcrime.securesms.loki.utilities
+package org.session.libsession.utilities
import android.media.AudioFormat
import android.media.MediaCodec
@@ -11,6 +11,7 @@ import androidx.annotation.RequiresApi
import java.io.FileDescriptor
import java.io.IOException
+import java.io.InputStream
import java.nio.ByteBuffer
import java.nio.ByteOrder
import java.nio.ShortBuffer
@@ -365,4 +366,34 @@ inline fun byteToNormalizedFloat(value: Byte): Float {
/** Turns a [0..1] float into a signed byte. */
inline fun normalizedFloatToByte(value: Float): Byte {
return (255f * value - 128f).roundToInt().toByte()
+}
+
+class InputStreamMediaDataSource: MediaDataSource {
+
+ private val data: ByteArray
+
+ constructor(inputStream: InputStream): super() {
+ this.data = inputStream.readBytes()
+ }
+
+ override fun readAt(position: Long, buffer: ByteArray, offset: Int, size: Int): Int {
+ val length: Int = data.size
+ if (position >= length) {
+ return -1 // -1 indicates EOF
+ }
+ var actualSize = size
+ if (position + size > length) {
+ actualSize -= (position + size - length).toInt()
+ }
+ System.arraycopy(data, position.toInt(), buffer, offset, actualSize)
+ return actualSize
+ }
+
+ override fun getSize(): Long {
+ return data.size.toLong()
+ }
+
+ override fun close() {
+ // We don't need to close the wrapped stream.
+ }
}
\ No newline at end of file