kopia lustrzana https://github.com/ryukoposting/Signal-Android
209 wiersze
8.5 KiB
Kotlin
209 wiersze
8.5 KiB
Kotlin
package org.thoughtcrime.securesms.mediasend.v2
|
|
|
|
import android.content.Context
|
|
import android.net.Uri
|
|
import androidx.annotation.WorkerThread
|
|
import io.reactivex.rxjava3.core.Maybe
|
|
import io.reactivex.rxjava3.core.Observable
|
|
import io.reactivex.rxjava3.core.Single
|
|
import io.reactivex.rxjava3.schedulers.Schedulers
|
|
import org.signal.core.util.ThreadUtil
|
|
import org.signal.core.util.logging.Log
|
|
import org.signal.imageeditor.core.model.EditorModel
|
|
import org.thoughtcrime.securesms.TransportOption
|
|
import org.thoughtcrime.securesms.database.AttachmentDatabase.TransformProperties
|
|
import org.thoughtcrime.securesms.database.ThreadDatabase
|
|
import org.thoughtcrime.securesms.database.model.Mention
|
|
import org.thoughtcrime.securesms.mediasend.CompositeMediaTransform
|
|
import org.thoughtcrime.securesms.mediasend.ImageEditorModelRenderMediaTransform
|
|
import org.thoughtcrime.securesms.mediasend.Media
|
|
import org.thoughtcrime.securesms.mediasend.MediaRepository
|
|
import org.thoughtcrime.securesms.mediasend.MediaSendActivityResult
|
|
import org.thoughtcrime.securesms.mediasend.MediaTransform
|
|
import org.thoughtcrime.securesms.mediasend.MediaUploadRepository
|
|
import org.thoughtcrime.securesms.mediasend.SentMediaQualityTransform
|
|
import org.thoughtcrime.securesms.mediasend.VideoEditorFragment
|
|
import org.thoughtcrime.securesms.mediasend.VideoTrimTransform
|
|
import org.thoughtcrime.securesms.mms.MediaConstraints
|
|
import org.thoughtcrime.securesms.mms.OutgoingMediaMessage
|
|
import org.thoughtcrime.securesms.mms.OutgoingSecureMediaMessage
|
|
import org.thoughtcrime.securesms.mms.SentMediaQuality
|
|
import org.thoughtcrime.securesms.mms.Slide
|
|
import org.thoughtcrime.securesms.providers.BlobProvider
|
|
import org.thoughtcrime.securesms.recipients.Recipient
|
|
import org.thoughtcrime.securesms.recipients.RecipientId
|
|
import org.thoughtcrime.securesms.scribbles.ImageEditorFragment
|
|
import org.thoughtcrime.securesms.sms.MessageSender
|
|
import org.thoughtcrime.securesms.sms.MessageSender.PreUploadResult
|
|
import org.thoughtcrime.securesms.util.MessageUtil
|
|
import java.util.ArrayList
|
|
import java.util.concurrent.TimeUnit
|
|
|
|
private val TAG = Log.tag(MediaSelectionRepository::class.java)
|
|
|
|
class MediaSelectionRepository(context: Context) {
|
|
|
|
private val context: Context = context.applicationContext
|
|
|
|
private val mediaRepository = MediaRepository()
|
|
|
|
val uploadRepository = MediaUploadRepository(context)
|
|
val isMetered: Observable<Boolean> = MeteredConnectivity.isMetered(context)
|
|
|
|
fun populateAndFilterMedia(media: List<Media>, mediaConstraints: MediaConstraints, maxSelection: Int): Single<MediaValidator.FilterResult> {
|
|
return Single.fromCallable {
|
|
val populatedMedia = mediaRepository.getPopulatedMedia(context, media)
|
|
|
|
MediaValidator.filterMedia(context, populatedMedia, mediaConstraints, maxSelection)
|
|
}.subscribeOn(Schedulers.io())
|
|
}
|
|
|
|
/**
|
|
* Tries to send the selected media, performing proper transformations for edited images and videos.
|
|
*/
|
|
fun send(
|
|
selectedMedia: List<Media>,
|
|
stateMap: Map<Uri, Any>,
|
|
quality: SentMediaQuality,
|
|
message: CharSequence?,
|
|
isSms: Boolean,
|
|
isViewOnce: Boolean,
|
|
singleRecipientId: RecipientId?,
|
|
recipientIds: List<RecipientId>,
|
|
mentions: List<Mention>,
|
|
transport: TransportOption
|
|
): Maybe<MediaSendActivityResult> {
|
|
if (isSms && recipientIds.isNotEmpty()) {
|
|
throw IllegalStateException("Provided recipients to send to, but this is SMS!")
|
|
}
|
|
|
|
return Maybe.create<MediaSendActivityResult> { emitter ->
|
|
val trimmedBody: String = if (isViewOnce) "" else message?.toString()?.trim() ?: ""
|
|
val trimmedMentions: List<Mention> = if (isViewOnce) emptyList() else mentions
|
|
val modelsToTransform: Map<Media, MediaTransform> = buildModelsToTransform(selectedMedia, stateMap, quality)
|
|
val oldToNewMediaMap: Map<Media, Media> = MediaRepository.transformMediaSync(context, selectedMedia, modelsToTransform)
|
|
val updatedMedia = oldToNewMediaMap.values.toList()
|
|
|
|
for (media in updatedMedia) {
|
|
Log.w(TAG, media.uri.toString() + " : " + media.transformProperties.transform { t: TransformProperties -> "" + t.isVideoTrim }.or("null"))
|
|
}
|
|
|
|
val singleRecipient = singleRecipientId?.let { Recipient.resolved(it) }
|
|
if (isSms || MessageSender.isLocalSelfSend(context, singleRecipient, isSms)) {
|
|
Log.i(TAG, "SMS or local self-send. Skipping pre-upload.")
|
|
emitter.onSuccess(MediaSendActivityResult.forTraditionalSend(requireNotNull(singleRecipient).id, updatedMedia, trimmedBody, transport, isViewOnce, trimmedMentions))
|
|
} else {
|
|
val splitMessage = MessageUtil.getSplitMessage(context, trimmedBody, transport.calculateCharacters(trimmedBody).maxPrimaryMessageSize)
|
|
val splitBody = splitMessage.body
|
|
|
|
if (splitMessage.textSlide.isPresent) {
|
|
val slide: Slide = splitMessage.textSlide.get()
|
|
uploadRepository.startUpload(
|
|
MediaBuilder.buildMedia(
|
|
uri = requireNotNull(slide.uri),
|
|
mimeType = slide.contentType,
|
|
date = System.currentTimeMillis(),
|
|
size = slide.fileSize,
|
|
borderless = slide.isBorderless,
|
|
videoGif = slide.isVideoGif
|
|
),
|
|
singleRecipient
|
|
)
|
|
}
|
|
|
|
uploadRepository.applyMediaUpdates(oldToNewMediaMap, singleRecipient)
|
|
uploadRepository.updateCaptions(updatedMedia)
|
|
uploadRepository.updateDisplayOrder(updatedMedia)
|
|
uploadRepository.getPreUploadResults { uploadResults ->
|
|
if (recipientIds.isNotEmpty()) {
|
|
val recipients = recipientIds.map { Recipient.resolved(it) }
|
|
sendMessages(recipients, splitBody, uploadResults, trimmedMentions, isViewOnce)
|
|
uploadRepository.deleteAbandonedAttachments()
|
|
emitter.onComplete()
|
|
} else {
|
|
emitter.onSuccess(MediaSendActivityResult.forPreUpload(requireNotNull(singleRecipient).id, uploadResults, splitBody, transport, isViewOnce, trimmedMentions))
|
|
}
|
|
}
|
|
}
|
|
}.subscribeOn(Schedulers.io()).cast(MediaSendActivityResult::class.java)
|
|
}
|
|
|
|
fun deleteBlobs(media: List<Media>) {
|
|
media
|
|
.map(Media::getUri)
|
|
.filter(BlobProvider::isAuthority)
|
|
.forEach { BlobProvider.getInstance().delete(context, it) }
|
|
}
|
|
|
|
fun cleanUp(selectedMedia: List<Media>) {
|
|
deleteBlobs(selectedMedia)
|
|
uploadRepository.cancelAllUploads()
|
|
uploadRepository.deleteAbandonedAttachments()
|
|
}
|
|
|
|
fun isLocalSelfSend(recipient: Recipient?, isSms: Boolean): Boolean {
|
|
return MessageSender.isLocalSelfSend(context, recipient, isSms)
|
|
}
|
|
|
|
@WorkerThread
|
|
private fun buildModelsToTransform(
|
|
selectedMedia: List<Media>,
|
|
stateMap: Map<Uri, Any>,
|
|
quality: SentMediaQuality
|
|
): Map<Media, MediaTransform> {
|
|
val modelsToRender: MutableMap<Media, MediaTransform> = mutableMapOf()
|
|
|
|
selectedMedia.forEach {
|
|
val state = stateMap[it.uri]
|
|
if (state is ImageEditorFragment.Data) {
|
|
val model: EditorModel? = state.readModel()
|
|
if (model != null && model.isChanged) {
|
|
modelsToRender[it] = ImageEditorModelRenderMediaTransform(model)
|
|
}
|
|
}
|
|
|
|
if (state is VideoEditorFragment.Data && state.isDurationEdited) {
|
|
modelsToRender[it] = VideoTrimTransform(state)
|
|
}
|
|
|
|
if (quality == SentMediaQuality.HIGH) {
|
|
val existingTransform: MediaTransform? = modelsToRender[it]
|
|
|
|
modelsToRender[it] = if (existingTransform == null) {
|
|
SentMediaQualityTransform(quality)
|
|
} else {
|
|
CompositeMediaTransform(existingTransform, SentMediaQualityTransform(quality))
|
|
}
|
|
}
|
|
}
|
|
|
|
return modelsToRender
|
|
}
|
|
|
|
@WorkerThread
|
|
private fun sendMessages(recipients: List<Recipient>, body: String, preUploadResults: Collection<PreUploadResult>, mentions: List<Mention>, isViewOnce: Boolean) {
|
|
val messages: MutableList<OutgoingSecureMediaMessage> = ArrayList(recipients.size)
|
|
|
|
for (recipient in recipients) {
|
|
val message = OutgoingMediaMessage(
|
|
recipient,
|
|
body, emptyList(),
|
|
System.currentTimeMillis(),
|
|
-1,
|
|
TimeUnit.SECONDS.toMillis(recipient.expiresInSeconds.toLong()),
|
|
isViewOnce,
|
|
ThreadDatabase.DistributionTypes.DEFAULT,
|
|
null, emptyList(), emptyList(),
|
|
mentions, emptyList(), emptyList()
|
|
)
|
|
messages.add(OutgoingSecureMediaMessage(message))
|
|
|
|
// XXX We must do this to avoid sending out messages to the same recipient with the same
|
|
// sentTimestamp. If we do this, they'll be considered dupes by the receiver.
|
|
ThreadUtil.sleep(5)
|
|
}
|
|
|
|
MessageSender.sendMediaBroadcast(context, messages, preUploadResults)
|
|
}
|
|
}
|