mirror of
https://github.com/oxen-io/session-android.git
synced 2024-12-25 09:17:44 +00:00
commit
983fb928e4
@ -143,8 +143,8 @@ dependencies {
|
|||||||
testImplementation 'org.robolectric:shadows-multidex:4.2'
|
testImplementation 'org.robolectric:shadows-multidex:4.2'
|
||||||
}
|
}
|
||||||
|
|
||||||
def canonicalVersionCode = 188
|
def canonicalVersionCode = 189
|
||||||
def canonicalVersionName = "1.11.0"
|
def canonicalVersionName = "1.11.1"
|
||||||
|
|
||||||
def postFixSize = 10
|
def postFixSize = 10
|
||||||
def abiPostFix = ['armeabi-v7a' : 1,
|
def abiPostFix = ['armeabi-v7a' : 1,
|
||||||
@ -194,8 +194,8 @@ android {
|
|||||||
versionCode canonicalVersionCode * postFixSize
|
versionCode canonicalVersionCode * postFixSize
|
||||||
versionName canonicalVersionName
|
versionName canonicalVersionName
|
||||||
|
|
||||||
minSdkVersion 23
|
minSdkVersion androidMinimumSdkVersion
|
||||||
targetSdkVersion 30
|
targetSdkVersion androidCompileSdkVersion
|
||||||
|
|
||||||
multiDexEnabled = true
|
multiDexEnabled = true
|
||||||
|
|
||||||
|
@ -221,7 +221,12 @@
|
|||||||
<activity
|
<activity
|
||||||
android:name="org.thoughtcrime.securesms.conversation.v2.ConversationActivityV2"
|
android:name="org.thoughtcrime.securesms.conversation.v2.ConversationActivityV2"
|
||||||
android:screenOrientation="portrait"
|
android:screenOrientation="portrait"
|
||||||
android:theme="@style/Theme.Session.DayNight.FlatActionBar" />
|
android:parentActivityName="org.thoughtcrime.securesms.loki.activities.HomeActivity"
|
||||||
|
android:theme="@style/Theme.Session.DayNight.FlatActionBar">
|
||||||
|
<meta-data
|
||||||
|
android:name="android.support.PARENT_ACTIVITY"
|
||||||
|
android:value="org.thoughtcrime.securesms.loki.activities.HomeActivity" />
|
||||||
|
</activity>
|
||||||
<activity
|
<activity
|
||||||
android:name="org.thoughtcrime.securesms.loki.activities.OpenGroupGuidelinesActivity"
|
android:name="org.thoughtcrime.securesms.loki.activities.OpenGroupGuidelinesActivity"
|
||||||
android:screenOrientation="portrait"
|
android:screenOrientation="portrait"
|
||||||
|
@ -97,6 +97,14 @@ class DatabaseAttachmentProvider(context: Context, helper: SQLCipherOpenHelper)
|
|||||||
attachmentDatabase.insertAttachmentsForPlaceholder(messageId, attachmentId, stream)
|
attachmentDatabase.insertAttachmentsForPlaceholder(messageId, attachmentId, stream)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun updateAudioAttachmentDuration(attachmentId: AttachmentId, durationMs: Long) {
|
||||||
|
DatabaseFactory.getAttachmentDatabase(context).setAttachmentAudioExtras(DatabaseAttachmentAudioExtras(
|
||||||
|
attachmentId = attachmentId,
|
||||||
|
visualSamples = byteArrayOf(),
|
||||||
|
durationMs = durationMs
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
override fun isOutgoingMessage(timestamp: Long): Boolean {
|
override fun isOutgoingMessage(timestamp: Long): Boolean {
|
||||||
val smsDatabase = DatabaseFactory.getSmsDatabase(context)
|
val smsDatabase = DatabaseFactory.getSmsDatabase(context)
|
||||||
val mmsDatabase = DatabaseFactory.getMmsDatabase(context)
|
val mmsDatabase = DatabaseFactory.getMmsDatabase(context)
|
||||||
|
@ -885,7 +885,8 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
|
|||||||
message.text = body
|
message.text = body
|
||||||
val quote = quotedMessage?.let {
|
val quote = quotedMessage?.let {
|
||||||
val quotedAttachments = (it as? MmsMessageRecord)?.slideDeck?.asAttachments() ?: listOf()
|
val quotedAttachments = (it as? MmsMessageRecord)?.slideDeck?.asAttachments() ?: listOf()
|
||||||
QuoteModel(it.dateSent, it.individualRecipient.address, it.body, false, quotedAttachments)
|
val sender = if (it.isOutgoing) fromSerialized(TextSecurePreferences.getLocalNumber(this)!!) else it.individualRecipient.address
|
||||||
|
QuoteModel(it.dateSent, sender, it.body, false, quotedAttachments)
|
||||||
}
|
}
|
||||||
val outgoingTextMessage = OutgoingMediaMessage.from(message, thread, attachments, quote, linkPreview)
|
val outgoingTextMessage = OutgoingMediaMessage.from(message, thread, attachments, quote, linkPreview)
|
||||||
// Clear the input bar
|
// Clear the input bar
|
||||||
@ -1031,10 +1032,10 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
|
|||||||
window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
|
window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
|
||||||
val future = audioRecorder.stopRecording()
|
val future = audioRecorder.stopRecording()
|
||||||
stopAudioHandler.removeCallbacks(stopVoiceMessageRecordingTask)
|
stopAudioHandler.removeCallbacks(stopVoiceMessageRecordingTask)
|
||||||
future.addListener(object : ListenableFuture.Listener<Pair<Uri?, Long?>> {
|
future.addListener(object : ListenableFuture.Listener<Pair<Uri, Long>> {
|
||||||
|
|
||||||
override fun onSuccess(result: Pair<Uri?, Long?>) {
|
override fun onSuccess(result: Pair<Uri, Long>) {
|
||||||
val audioSlide = AudioSlide(this@ConversationActivityV2, result.first, result.second!!, MediaTypes.AUDIO_AAC, true)
|
val audioSlide = AudioSlide(this@ConversationActivityV2, result.first, result.second, MediaTypes.AUDIO_AAC, true)
|
||||||
val slideDeck = SlideDeck()
|
val slideDeck = SlideDeck()
|
||||||
slideDeck.addSlide(audioSlide)
|
slideDeck.addSlide(audioSlide)
|
||||||
sendAttachments(slideDeck.asAttachments(), null)
|
sendAttachments(slideDeck.asAttachments(), null)
|
||||||
|
@ -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 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()
|
val sender = if (message.isOutgoing) TextSecurePreferences.getLocalNumber(context)!! else message.individualRecipient.address.serialize()
|
||||||
quoteView.bind(sender, message.body, attachments,
|
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
|
// The 6 DP below is the padding the quote view applies to itself, which isn't included in the
|
||||||
// intrinsic height calculation.
|
// intrinsic height calculation.
|
||||||
val quoteViewIntrinsicHeight = quoteView.getIntrinsicHeight(maxContentWidth) + toPx(6, resources)
|
val quoteViewIntrinsicHeight = quoteView.getIntrinsicHeight(maxContentWidth) + toPx(6, resources)
|
||||||
|
@ -110,7 +110,8 @@ class QuoteView : LinearLayout {
|
|||||||
|
|
||||||
// region Updating
|
// region Updating
|
||||||
fun bind(authorPublicKey: String, body: String?, attachments: SlideDeck?, thread: Recipient,
|
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)
|
val contactDB = DatabaseFactory.getSessionContactDatabase(context)
|
||||||
// Reduce the max body text view line count to 2 if this is a group thread because
|
// 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
|
// 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.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))
|
quoteViewBodyTextView.setTextColor(getTextColor(isOutgoingMessage))
|
||||||
// Accent line / attachment preview
|
// Accent line / attachment preview
|
||||||
val hasAttachments = (attachments != null && attachments.asAttachments().isNotEmpty())
|
val hasAttachments = (attachments != null && attachments.asAttachments().isNotEmpty()) && !isOriginalMissing
|
||||||
quoteViewAccentLine.isVisible = !hasAttachments
|
quoteViewAccentLine.isVisible = !hasAttachments
|
||||||
quoteViewAttachmentPreviewContainer.isVisible = hasAttachments
|
quoteViewAttachmentPreviewContainer.isVisible = hasAttachments
|
||||||
if (!hasAttachments) {
|
if (!hasAttachments) {
|
||||||
@ -136,8 +137,7 @@ class QuoteView : LinearLayout {
|
|||||||
accentLineLayoutParams.height = getIntrinsicContentHeight(maxContentWidth) // Match the intrinsic * content * height
|
accentLineLayoutParams.height = getIntrinsicContentHeight(maxContentWidth) // Match the intrinsic * content * height
|
||||||
quoteViewAccentLine.layoutParams = accentLineLayoutParams
|
quoteViewAccentLine.layoutParams = accentLineLayoutParams
|
||||||
quoteViewAccentLine.setBackgroundColor(getLineColor(isOutgoingMessage))
|
quoteViewAccentLine.setBackgroundColor(getLineColor(isOutgoingMessage))
|
||||||
} else {
|
} else if (attachments != null) {
|
||||||
attachments!!
|
|
||||||
quoteViewAttachmentPreviewImageView.imageTintList = ColorStateList.valueOf(ResourcesCompat.getColor(resources, R.color.white, context.theme))
|
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 backgroundColorID = if (UiModeUtilities.isDayUiMode(context)) R.color.black else R.color.accent
|
||||||
val backgroundColor = ResourcesCompat.getColor(resources, backgroundColorID, context.theme)
|
val backgroundColor = ResourcesCompat.getColor(resources, backgroundColorID, context.theme)
|
||||||
|
@ -86,8 +86,14 @@ class VisibleMessageContentView : LinearLayout {
|
|||||||
// quote view content area's start margin. This unfortunately has to be calculated manually
|
// quote view content area's start margin. This unfortunately has to be calculated manually
|
||||||
// here to get the layout right.
|
// here to get the layout right.
|
||||||
val maxContentWidth = (maxWidth - 2 * resources.getDimension(R.dimen.medium_spacing) - toPx(16, resources)).roundToInt()
|
val maxContentWidth = (maxWidth - 2 * resources.getDimension(R.dimen.medium_spacing) - toPx(16, resources)).roundToInt()
|
||||||
quoteView.bind(quote.author.toString(), quote.text, quote.attachment, thread,
|
val quoteText = if (quote.isOriginalMissing) {
|
||||||
message.isOutgoing, maxContentWidth, message.isOpenGroupInvitation, message.threadId, glide)
|
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)
|
mainContainer.addView(quoteView)
|
||||||
val bodyTextView = VisibleMessageContentView.getBodyTextView(context, message, searchQuery)
|
val bodyTextView = VisibleMessageContentView.getBodyTextView(context, message, searchQuery)
|
||||||
ViewUtil.setPaddingTop(bodyTextView, 0)
|
ViewUtil.setPaddingTop(bodyTextView, 0)
|
||||||
|
@ -10,9 +10,11 @@ import android.widget.RelativeLayout
|
|||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import kotlinx.android.synthetic.main.view_voice_message.view.*
|
import kotlinx.android.synthetic.main.view_voice_message.view.*
|
||||||
import network.loki.messenger.R
|
import network.loki.messenger.R
|
||||||
|
import org.session.libsession.messaging.sending_receiving.attachments.DatabaseAttachment
|
||||||
import org.thoughtcrime.securesms.audio.AudioSlidePlayer
|
import org.thoughtcrime.securesms.audio.AudioSlidePlayer
|
||||||
import org.thoughtcrime.securesms.components.CornerMask
|
import org.thoughtcrime.securesms.components.CornerMask
|
||||||
import org.thoughtcrime.securesms.conversation.v2.utilities.MessageBubbleUtilities
|
import org.thoughtcrime.securesms.conversation.v2.utilities.MessageBubbleUtilities
|
||||||
|
import org.thoughtcrime.securesms.database.DatabaseFactory
|
||||||
import org.thoughtcrime.securesms.database.model.MmsMessageRecord
|
import org.thoughtcrime.securesms.database.model.MmsMessageRecord
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
@ -44,27 +46,30 @@ class VoiceMessageView : LinearLayout, AudioSlidePlayer.Listener {
|
|||||||
val audio = message.slideDeck.audioSlide!!
|
val audio = message.slideDeck.audioSlide!!
|
||||||
val player = AudioSlidePlayer.createFor(context, audio, this)
|
val player = AudioSlidePlayer.createFor(context, audio, this)
|
||||||
this.player = player
|
this.player = player
|
||||||
isPreparing = true
|
|
||||||
if (!audio.isPendingDownload && !audio.isInProgress) {
|
|
||||||
player.play(0.0)
|
|
||||||
}
|
|
||||||
voiceMessageViewLoader.isVisible = audio.isPendingDownload
|
voiceMessageViewLoader.isVisible = audio.isPendingDownload
|
||||||
val cornerRadii = MessageBubbleUtilities.calculateRadii(context, isStartOfMessageCluster, isEndOfMessageCluster, message.isOutgoing)
|
val cornerRadii = MessageBubbleUtilities.calculateRadii(context, isStartOfMessageCluster, isEndOfMessageCluster, message.isOutgoing)
|
||||||
cornerMask.setTopLeftRadius(cornerRadii[0])
|
cornerMask.setTopLeftRadius(cornerRadii[0])
|
||||||
cornerMask.setTopRightRadius(cornerRadii[1])
|
cornerMask.setTopRightRadius(cornerRadii[1])
|
||||||
cornerMask.setBottomRightRadius(cornerRadii[2])
|
cornerMask.setBottomRightRadius(cornerRadii[2])
|
||||||
cornerMask.setBottomLeftRadius(cornerRadii[3])
|
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) {
|
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 onPlayerProgress(player: AudioSlidePlayer, progress: Double, unused: Long) {
|
override fun onPlayerProgress(player: AudioSlidePlayer, progress: Double, unused: Long) {
|
||||||
if (progress == 1.0) {
|
if (progress == 1.0) {
|
||||||
|
@ -63,6 +63,7 @@ import org.thoughtcrime.securesms.attachments.MmsNotificationAttachment;
|
|||||||
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper;
|
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper;
|
||||||
import org.thoughtcrime.securesms.database.model.MediaMmsMessageRecord;
|
import org.thoughtcrime.securesms.database.model.MediaMmsMessageRecord;
|
||||||
import org.thoughtcrime.securesms.database.model.MessageRecord;
|
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.NotificationMmsMessageRecord;
|
||||||
import org.thoughtcrime.securesms.database.model.Quote;
|
import org.thoughtcrime.securesms.database.model.Quote;
|
||||||
import org.thoughtcrime.securesms.mms.MmsException;
|
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) {
|
public boolean delete(long messageId) {
|
||||||
long threadId = getThreadIdForMessage(messageId);
|
long threadId = getThreadIdForMessage(messageId);
|
||||||
AttachmentDatabase attachmentDatabase = DatabaseFactory.getAttachmentDatabase(context);
|
AttachmentDatabase attachmentDatabase = DatabaseFactory.getAttachmentDatabase(context);
|
||||||
@ -889,6 +904,12 @@ public class MmsDatabase extends MessagingDatabase {
|
|||||||
GroupReceiptDatabase groupReceiptDatabase = DatabaseFactory.getGroupReceiptDatabase(context);
|
GroupReceiptDatabase groupReceiptDatabase = DatabaseFactory.getGroupReceiptDatabase(context);
|
||||||
groupReceiptDatabase.deleteRowsForMessage(messageId);
|
groupReceiptDatabase.deleteRowsForMessage(messageId);
|
||||||
|
|
||||||
|
MessageRecord toDelete;
|
||||||
|
try (Cursor messageCursor = getMessage(messageId)) {
|
||||||
|
toDelete = readerFor(messageCursor).getNext();
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteQuotedFromMessages(toDelete);
|
||||||
SQLiteDatabase database = databaseHelper.getWritableDatabase();
|
SQLiteDatabase database = databaseHelper.getWritableDatabase();
|
||||||
database.delete(TABLE_NAME, ID_WHERE, new String[] {messageId+""});
|
database.delete(TABLE_NAME, ID_WHERE, new String[] {messageId+""});
|
||||||
boolean threadDeleted = DatabaseFactory.getThreadDatabase(context).update(threadId, false);
|
boolean threadDeleted = DatabaseFactory.getThreadDatabase(context).update(threadId, false);
|
||||||
@ -1066,6 +1087,14 @@ public class MmsDatabase extends MessagingDatabase {
|
|||||||
return new OutgoingMessageReader(message, threadId);
|
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 class Status {
|
||||||
public static final int DOWNLOAD_INITIALIZED = 1;
|
public static final int DOWNLOAD_INITIALIZED = 1;
|
||||||
public static final int DOWNLOAD_NO_CONNECTIVITY = 2;
|
public static final int DOWNLOAD_NO_CONNECTIVITY = 2;
|
||||||
|
@ -514,6 +514,12 @@ public class SmsDatabase extends MessagingDatabase {
|
|||||||
Log.i("MessageDatabase", "Deleting: " + messageId);
|
Log.i("MessageDatabase", "Deleting: " + messageId);
|
||||||
SQLiteDatabase db = databaseHelper.getWritableDatabase();
|
SQLiteDatabase db = databaseHelper.getWritableDatabase();
|
||||||
long threadId = getThreadIdForMessage(messageId);
|
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+""});
|
db.delete(TABLE_NAME, ID_WHERE, new String[] {messageId+""});
|
||||||
boolean threadDeleted = DatabaseFactory.getThreadDatabase(context).update(threadId, false);
|
boolean threadDeleted = DatabaseFactory.getThreadDatabase(context).update(threadId, false);
|
||||||
notifyConversationListeners(threadId);
|
notifyConversationListeners(threadId);
|
||||||
|
@ -27,7 +27,6 @@ import org.session.libsignal.utilities.KeyHelper
|
|||||||
import org.session.libsignal.utilities.guava.Optional
|
import org.session.libsignal.utilities.guava.Optional
|
||||||
import org.thoughtcrime.securesms.ApplicationContext
|
import org.thoughtcrime.securesms.ApplicationContext
|
||||||
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper
|
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper
|
||||||
import org.thoughtcrime.securesms.database.model.MessageRecord
|
|
||||||
import org.thoughtcrime.securesms.jobs.RetrieveProfileAvatarJob
|
import org.thoughtcrime.securesms.jobs.RetrieveProfileAvatarJob
|
||||||
import org.thoughtcrime.securesms.loki.api.OpenGroupManager
|
import org.thoughtcrime.securesms.loki.api.OpenGroupManager
|
||||||
import org.thoughtcrime.securesms.loki.database.LokiThreadDatabase
|
import org.thoughtcrime.securesms.loki.database.LokiThreadDatabase
|
||||||
@ -190,7 +189,7 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context,
|
|||||||
|
|
||||||
override fun resumeMessageSendJobIfNeeded(messageSendJobID: String) {
|
override fun resumeMessageSendJobIfNeeded(messageSendJobID: String) {
|
||||||
val job = DatabaseFactory.getSessionJobDatabase(context).getMessageSendJob(messageSendJobID) ?: return
|
val job = DatabaseFactory.getSessionJobDatabase(context).getMessageSendJob(messageSendJobID) ?: return
|
||||||
JobQueue.shared.add(job)
|
JobQueue.shared.resumePendingSendMessage(job)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun isJobCanceled(job: Job): Boolean {
|
override fun isJobCanceled(job: Job): Boolean {
|
||||||
|
@ -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.Attachment
|
||||||
import org.session.libsession.messaging.sending_receiving.attachments.AttachmentId
|
import org.session.libsession.messaging.sending_receiving.attachments.AttachmentId
|
||||||
import org.session.libsession.messaging.sending_receiving.attachments.DatabaseAttachmentAudioExtras
|
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.database.DatabaseFactory
|
||||||
import org.thoughtcrime.securesms.jobmanager.Job
|
import org.thoughtcrime.securesms.jobmanager.Job
|
||||||
import org.thoughtcrime.securesms.jobs.BaseJob
|
import org.thoughtcrime.securesms.jobs.BaseJob
|
||||||
import org.thoughtcrime.securesms.loki.utilities.DecodedAudio
|
|
||||||
import org.thoughtcrime.securesms.mms.PartAuthority
|
import org.thoughtcrime.securesms.mms.PartAuthority
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
import java.lang.IllegalStateException
|
import java.lang.IllegalStateException
|
||||||
@ -133,35 +134,4 @@ class PrepareAttachmentAudioExtrasJob : BaseJob {
|
|||||||
|
|
||||||
/** Gets dispatched once the audio extras have been updated. */
|
/** Gets dispatched once the audio extras have been updated. */
|
||||||
data class AudioExtrasUpdatedEvent(val attachmentId: AttachmentId)
|
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.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
@ -14,7 +14,7 @@ import android.view.ViewConfiguration
|
|||||||
import android.view.animation.DecelerateInterpolator
|
import android.view.animation.DecelerateInterpolator
|
||||||
import androidx.core.math.MathUtils
|
import androidx.core.math.MathUtils
|
||||||
import network.loki.messenger.R
|
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.abs
|
||||||
import kotlin.math.max
|
import kotlin.math.max
|
||||||
import kotlin.math.min
|
import kotlin.math.min
|
||||||
|
@ -50,6 +50,7 @@ allprojects {
|
|||||||
}
|
}
|
||||||
|
|
||||||
project.ext {
|
project.ext {
|
||||||
|
androidMinimumSdkVersion = 23
|
||||||
androidCompileSdkVersion = 30
|
androidCompileSdkVersion = 30
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -6,6 +6,10 @@ plugins {
|
|||||||
android {
|
android {
|
||||||
compileSdkVersion androidCompileSdkVersion
|
compileSdkVersion androidCompileSdkVersion
|
||||||
|
|
||||||
|
defaultConfig {
|
||||||
|
minSdkVersion androidMinimumSdkVersion
|
||||||
|
}
|
||||||
|
|
||||||
compileOptions {
|
compileOptions {
|
||||||
sourceCompatibility JavaVersion.VERSION_1_8
|
sourceCompatibility JavaVersion.VERSION_1_8
|
||||||
targetCompatibility JavaVersion.VERSION_1_8
|
targetCompatibility JavaVersion.VERSION_1_8
|
||||||
|
@ -20,6 +20,7 @@ interface MessageDataProvider {
|
|||||||
fun getSignalAttachmentPointer(attachmentId: Long): SignalServiceAttachmentPointer?
|
fun getSignalAttachmentPointer(attachmentId: Long): SignalServiceAttachmentPointer?
|
||||||
fun setAttachmentState(attachmentState: AttachmentState, attachmentId: Long, messageID: Long)
|
fun setAttachmentState(attachmentState: AttachmentState, attachmentId: Long, messageID: Long)
|
||||||
fun insertAttachment(messageId: Long, attachmentId: AttachmentId, stream : InputStream)
|
fun insertAttachment(messageId: Long, attachmentId: AttachmentId, stream : InputStream)
|
||||||
|
fun updateAudioAttachmentDuration(attachmentId: AttachmentId, durationMs: Long)
|
||||||
fun isOutgoingMessage(timestamp: Long): Boolean
|
fun isOutgoingMessage(timestamp: Long): Boolean
|
||||||
fun handleSuccessfulAttachmentUpload(attachmentId: Long, attachmentStream: SignalServiceAttachmentStream, attachmentKey: ByteArray, uploadResult: UploadResult)
|
fun handleSuccessfulAttachmentUpload(attachmentId: Long, attachmentStream: SignalServiceAttachmentStream, attachmentKey: ByteArray, uploadResult: UploadResult)
|
||||||
fun handleFailedAttachmentUpload(attachmentId: Long)
|
fun handleFailedAttachmentUpload(attachmentId: Long)
|
||||||
|
@ -1,16 +1,24 @@
|
|||||||
package org.session.libsession.messaging.jobs
|
package org.session.libsession.messaging.jobs
|
||||||
|
|
||||||
|
import android.content.ContentResolver
|
||||||
|
import android.media.MediaDataSource
|
||||||
|
import android.media.MediaExtractor
|
||||||
import okhttp3.HttpUrl
|
import okhttp3.HttpUrl
|
||||||
import org.session.libsession.messaging.MessagingModuleConfiguration
|
import org.session.libsession.messaging.MessagingModuleConfiguration
|
||||||
import org.session.libsession.messaging.open_groups.OpenGroupAPIV2
|
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.AttachmentState
|
||||||
|
import org.session.libsession.messaging.sending_receiving.attachments.DatabaseAttachment
|
||||||
import org.session.libsession.messaging.utilities.Data
|
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.DownloadUtilities
|
||||||
|
import org.session.libsession.utilities.InputStreamMediaDataSource
|
||||||
import org.session.libsignal.streams.AttachmentCipherInputStream
|
import org.session.libsignal.streams.AttachmentCipherInputStream
|
||||||
import org.session.libsignal.utilities.Base64
|
import org.session.libsignal.utilities.Base64
|
||||||
import org.session.libsignal.utilities.Log
|
import org.session.libsignal.utilities.Log
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.FileInputStream
|
import java.io.FileInputStream
|
||||||
|
import java.io.InputStream
|
||||||
|
|
||||||
class AttachmentDownloadJob(val attachmentID: Long, val databaseMessageID: Long) : Job {
|
class AttachmentDownloadJob(val attachmentID: Long, val databaseMessageID: Long) : Job {
|
||||||
override var delegate: JobDelegate? = null
|
override var delegate: JobDelegate? = null
|
||||||
@ -37,46 +45,64 @@ class AttachmentDownloadJob(val attachmentID: Long, val databaseMessageID: Long)
|
|||||||
val storage = MessagingModuleConfiguration.shared.storage
|
val storage = MessagingModuleConfiguration.shared.storage
|
||||||
val messageDataProvider = MessagingModuleConfiguration.shared.messageDataProvider
|
val messageDataProvider = MessagingModuleConfiguration.shared.messageDataProvider
|
||||||
val handleFailure: (java.lang.Exception) -> Unit = { exception ->
|
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)
|
messageDataProvider.setAttachmentState(AttachmentState.FAILED, attachmentID, databaseMessageID)
|
||||||
this.handlePermanentFailure(exception)
|
this.handlePermanentFailure(exception)
|
||||||
} else {
|
} else {
|
||||||
this.handleFailure(exception)
|
this.handleFailure(exception)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
var tempFile: File? = null
|
||||||
try {
|
try {
|
||||||
val attachment = messageDataProvider.getDatabaseAttachment(attachmentID)
|
val attachment = messageDataProvider.getDatabaseAttachment(attachmentID)
|
||||||
?: return handleFailure(Error.NoAttachment)
|
?: return handleFailure(Error.NoAttachment)
|
||||||
messageDataProvider.setAttachmentState(AttachmentState.STARTED, attachmentID, this.databaseMessageID)
|
messageDataProvider.setAttachmentState(AttachmentState.STARTED, attachmentID, this.databaseMessageID)
|
||||||
val tempFile = createTempFile()
|
tempFile = createTempFile()
|
||||||
val threadID = storage.getThreadIdForMms(databaseMessageID)
|
val threadID = storage.getThreadIdForMms(databaseMessageID)
|
||||||
val openGroupV2 = storage.getV2OpenGroup(threadID)
|
val openGroupV2 = storage.getV2OpenGroup(threadID)
|
||||||
val inputStream = if (openGroupV2 == null) {
|
if (openGroupV2 == null) {
|
||||||
DownloadUtilities.downloadFile(tempFile, attachment.url)
|
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 {
|
} else {
|
||||||
val url = HttpUrl.parse(attachment.url)!!
|
val url = HttpUrl.parse(attachment.url)!!
|
||||||
val fileID = url.pathSegments().last()
|
val fileID = url.pathSegments().last()
|
||||||
OpenGroupAPIV2.download(fileID.toLong(), openGroupV2.room, openGroupV2.server).get().let {
|
OpenGroupAPIV2.download(fileID.toLong(), openGroupV2.room, openGroupV2.server).get().let {
|
||||||
tempFile.writeBytes(it)
|
tempFile.writeBytes(it)
|
||||||
}
|
}
|
||||||
FileInputStream(tempFile)
|
|
||||||
}
|
}
|
||||||
|
val inputStream = getInputStream(tempFile, attachment)
|
||||||
|
|
||||||
messageDataProvider.insertAttachment(databaseMessageID, attachment.attachmentId, inputStream)
|
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()
|
tempFile.delete()
|
||||||
handleSuccess()
|
handleSuccess()
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
|
tempFile?.delete()
|
||||||
return handleFailure(e)
|
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() {
|
private fun handleSuccess() {
|
||||||
Log.w(AttachmentUploadJob.TAG, "Attachment downloaded successfully.")
|
Log.w("AttachmentDownloadJob", "Attachment downloaded successfully.")
|
||||||
delegate?.handleJobSucceeded(this)
|
delegate?.handleJobSucceeded(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -11,6 +11,8 @@ import org.session.libsession.messaging.messages.Message
|
|||||||
import org.session.libsession.messaging.open_groups.OpenGroupAPIV2
|
import org.session.libsession.messaging.open_groups.OpenGroupAPIV2
|
||||||
import org.session.libsession.messaging.sending_receiving.MessageSender
|
import org.session.libsession.messaging.sending_receiving.MessageSender
|
||||||
import org.session.libsession.messaging.utilities.Data
|
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.libsession.utilities.UploadResult
|
||||||
import org.session.libsignal.streams.AttachmentCipherOutputStream
|
import org.session.libsignal.streams.AttachmentCipherOutputStream
|
||||||
import org.session.libsignal.messages.SignalServiceAttachmentStream
|
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) {
|
private fun handleSuccess(attachment: SignalServiceAttachmentStream, attachmentKey: ByteArray, uploadResult: UploadResult) {
|
||||||
Log.d(TAG, "Attachment uploaded successfully.")
|
Log.d(TAG, "Attachment uploaded successfully.")
|
||||||
delegate?.handleJobSucceeded(this)
|
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)
|
MessagingModuleConfiguration.shared.storage.resumeMessageSendJobIfNeeded(messageSendJobID)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -140,13 +157,13 @@ class AttachmentUploadJob(val attachmentID: Long, val threadID: String, val mess
|
|||||||
val kryo = Kryo()
|
val kryo = Kryo()
|
||||||
kryo.isRegistrationRequired = false
|
kryo.isRegistrationRequired = false
|
||||||
val serializedMessage = ByteArray(4096)
|
val serializedMessage = ByteArray(4096)
|
||||||
val output = Output(serializedMessage)
|
val output = Output(serializedMessage, Job.MAX_BUFFER_SIZE)
|
||||||
kryo.writeObject(output, message)
|
kryo.writeClassAndObject(output, message)
|
||||||
output.close()
|
output.close()
|
||||||
return Data.Builder()
|
return Data.Builder()
|
||||||
.putLong(ATTACHMENT_ID_KEY, attachmentID)
|
.putLong(ATTACHMENT_ID_KEY, attachmentID)
|
||||||
.putString(THREAD_ID_KEY, threadID)
|
.putString(THREAD_ID_KEY, threadID)
|
||||||
.putByteArray(MESSAGE_KEY, serializedMessage)
|
.putByteArray(MESSAGE_KEY, output.toBytes())
|
||||||
.putString(MESSAGE_SEND_JOB_ID_KEY, messageSendJobID)
|
.putString(MESSAGE_SEND_JOB_ID_KEY, messageSendJobID)
|
||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
@ -157,18 +174,24 @@ class AttachmentUploadJob(val attachmentID: Long, val threadID: String, val mess
|
|||||||
|
|
||||||
class Factory: Job.Factory<AttachmentUploadJob> {
|
class Factory: Job.Factory<AttachmentUploadJob> {
|
||||||
|
|
||||||
override fun create(data: Data): AttachmentUploadJob {
|
override fun create(data: Data): AttachmentUploadJob? {
|
||||||
val serializedMessage = data.getByteArray(MESSAGE_KEY)
|
val serializedMessage = data.getByteArray(MESSAGE_KEY)
|
||||||
val kryo = Kryo()
|
val kryo = Kryo()
|
||||||
kryo.isRegistrationRequired = false
|
kryo.isRegistrationRequired = false
|
||||||
val input = Input(serializedMessage)
|
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()
|
input.close()
|
||||||
return AttachmentUploadJob(
|
return AttachmentUploadJob(
|
||||||
data.getLong(ATTACHMENT_ID_KEY),
|
data.getLong(ATTACHMENT_ID_KEY),
|
||||||
data.getString(THREAD_ID_KEY)!!,
|
data.getString(THREAD_ID_KEY)!!,
|
||||||
message,
|
message,
|
||||||
data.getString(MESSAGE_SEND_JOB_ID_KEY)!!
|
data.getString(MESSAGE_SEND_JOB_ID_KEY)!!
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -23,6 +23,7 @@ class JobQueue : JobDelegate {
|
|||||||
private val attachmentDispatcher = Executors.newFixedThreadPool(2).asCoroutineDispatcher()
|
private val attachmentDispatcher = Executors.newFixedThreadPool(2).asCoroutineDispatcher()
|
||||||
private val scope = GlobalScope + SupervisorJob()
|
private val scope = GlobalScope + SupervisorJob()
|
||||||
private val queue = Channel<Job>(UNLIMITED)
|
private val queue = Channel<Job>(UNLIMITED)
|
||||||
|
private val pendingJobIds = mutableSetOf<String>()
|
||||||
|
|
||||||
val timer = Timer()
|
val timer = Timer()
|
||||||
|
|
||||||
@ -86,6 +87,19 @@ class JobQueue : JobDelegate {
|
|||||||
MessagingModuleConfiguration.shared.storage.persistJob(job)
|
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() {
|
fun resumePendingJobs() {
|
||||||
if (hasResumedPendingJobs) {
|
if (hasResumedPendingJobs) {
|
||||||
Log.d("Loki", "resumePendingJobs() should only be called once.")
|
Log.d("Loki", "resumePendingJobs() should only be called once.")
|
||||||
@ -120,6 +134,7 @@ class JobQueue : JobDelegate {
|
|||||||
override fun handleJobSucceeded(job: Job) {
|
override fun handleJobSucceeded(job: Job) {
|
||||||
val jobId = job.id ?: return
|
val jobId = job.id ?: return
|
||||||
MessagingModuleConfiguration.shared.storage.markJobAsSucceeded(jobId)
|
MessagingModuleConfiguration.shared.storage.markJobAsSucceeded(jobId)
|
||||||
|
pendingJobIds.remove(jobId)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun handleJobFailed(job: Job, error: Exception) {
|
override fun handleJobFailed(job: Job, error: Exception) {
|
||||||
@ -169,4 +184,7 @@ class JobQueue : JobDelegate {
|
|||||||
val maxBackoff = (10 * 60).toDouble() // 10 minutes
|
val maxBackoff = (10 * 60).toDouble() // 10 minutes
|
||||||
return (1000 * 0.25 * min(maxBackoff, (2.0).pow(job.failureCount))).roundToLong()
|
return (1000 * 0.25 * min(maxBackoff, (2.0).pow(job.failureCount))).roundToLong()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun Job.isSend() = this is MessageSendJob || this is AttachmentUploadJob
|
||||||
|
|
||||||
}
|
}
|
@ -94,7 +94,9 @@ class OpenGroupPollerV2(private val server: String, private val executorService:
|
|||||||
if (actualMax > 0) {
|
if (actualMax > 0) {
|
||||||
storage.setLastMessageServerID(room, server, actualMax)
|
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<OpenGroupAPIV2.MessageDeletion>) {
|
private fun handleDeletedMessages(room: String, openGroupID: String, deletions: List<OpenGroupAPIV2.MessageDeletion>) {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
package org.thoughtcrime.securesms.loki.utilities
|
package org.session.libsession.utilities
|
||||||
|
|
||||||
import android.media.AudioFormat
|
import android.media.AudioFormat
|
||||||
import android.media.MediaCodec
|
import android.media.MediaCodec
|
||||||
@ -11,6 +11,7 @@ import androidx.annotation.RequiresApi
|
|||||||
|
|
||||||
import java.io.FileDescriptor
|
import java.io.FileDescriptor
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
|
import java.io.InputStream
|
||||||
import java.nio.ByteBuffer
|
import java.nio.ByteBuffer
|
||||||
import java.nio.ByteOrder
|
import java.nio.ByteOrder
|
||||||
import java.nio.ShortBuffer
|
import java.nio.ShortBuffer
|
||||||
@ -365,4 +366,34 @@ inline fun byteToNormalizedFloat(value: Byte): Float {
|
|||||||
/** Turns a [0..1] float into a signed byte. */
|
/** Turns a [0..1] float into a signed byte. */
|
||||||
inline fun normalizedFloatToByte(value: Float): Byte {
|
inline fun normalizedFloatToByte(value: Float): Byte {
|
||||||
return (255f * value - 128f).roundToInt().toByte()
|
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.
|
||||||
|
}
|
||||||
}
|
}
|
Loading…
x
Reference in New Issue
Block a user