Do not auto-download videos for thumbnails if traffic undesired

Too much network traffic is likely undesired in
- public rooms
- metered network connections

Change-Id: I7aba03098b15fc8a6f20bbc50d53dec842b4d7c9
This commit is contained in:
SpiritCroc 2022-08-02 20:51:30 +02:00
parent 82b072a081
commit d713a782b0
9 changed files with 64 additions and 12 deletions

View File

@ -0,0 +1,27 @@
package de.spiritcroc.util
import im.vector.app.features.home.room.detail.timeline.item.MessageInformationData
import org.matrix.android.sdk.api.extensions.orFalse
import javax.inject.Inject
class ThumbnailGenerationVideoDownloadDecider @Inject constructor() /*val context: Context, val vectorPreference: VectorPreference)*/ {
fun enableVideoDownloadForThumbnailGeneration(informationData: MessageInformationData? = null): Boolean {
// Disable automatic download for public rooms
if (informationData?.isPublic.orFalse()) {
return false
}
// Disable automatic download for metered connections
/* Since this may change later, better check in VectorGlideModelLoader directly
context.getSystemService<ConnectivityManager>()!!.apply {
if (isActiveNetworkMetered) {
return false
}
}
*/
// Else, enable automatic download
return true
}
}

View File

@ -17,6 +17,8 @@
package im.vector.app.core.glide
import android.content.Context
import android.net.ConnectivityManager
import androidx.core.content.getSystemService
import com.bumptech.glide.Priority
import com.bumptech.glide.load.DataSource
import com.bumptech.glide.load.Options
@ -62,7 +64,7 @@ class VectorGlideModelLoader(private val context: Context) :
}
class VectorGlideDataFetcher(
context: Context,
private val context: Context,
private val data: ImageContentRenderer.Data,
private val width: Int,
private val height: Int
@ -130,14 +132,26 @@ class VectorGlideDataFetcher(
)
}
if (result.isFailure && (data.fallbackUrl != null || data.fallbackElementToDecrypt != null)) {
result = runCatching {
fileService.downloadFile(
fileName = data.filename,
mimeType = data.mimeType,
url = data.fallbackUrl,
elementToDecrypt = data.fallbackElementToDecrypt)
// Extract thumbnail from video
val isInCache = fileService.isFileInCache(
fileName = data.filename,
mimeType = data.mimeType,
mxcUrl = data.fallbackUrl,
elementToDecrypt = data.fallbackElementToDecrypt)
if (data.downloadFallbackIfThumbnailMissing || isInCache) {
// Disable automatic download for metered connections
if (isInCache || !context.getSystemService<ConnectivityManager>()!!.isActiveNetworkMetered) {
result = runCatching {
fileService.downloadFile(
fileName = data.filename,
mimeType = data.mimeType,
url = data.fallbackUrl,
elementToDecrypt = data.fallbackElementToDecrypt
)
}
result = result.getOrNull()?.let { thumbnailExtractor.extractThumbnail(it) } ?: result
}
}
result = result.getOrNull()?.let { thumbnailExtractor.extractThumbnail(it) } ?: result
}
withContext(Dispatchers.Main) {
result.fold(

View File

@ -77,6 +77,7 @@ import de.spiritcroc.menu.toggleExec
import de.spiritcroc.recyclerview.StickyHeaderItemDecoration
import de.spiritcroc.recyclerview.widget.BetterLinearLayoutManager
import de.spiritcroc.recyclerview.widget.LinearLayoutManager
import de.spiritcroc.util.ThumbnailGenerationVideoDownloadDecider
import im.vector.app.BuildConfig
import im.vector.app.R
import im.vector.app.core.animations.play
@ -283,6 +284,7 @@ class TimelineFragment @Inject constructor(
private val notificationDrawerManager: NotificationDrawerManager,
private val eventHtmlRenderer: EventHtmlRenderer,
private val vectorPreferences: VectorPreferences,
private val generationVideoDownloadDecider: ThumbnailGenerationVideoDownloadDecider,
private val bubbleThemeUtils: BubbleThemeUtils,
private val threadsManager: ThreadsManager,
private val colorProvider: ColorProvider,
@ -1454,7 +1456,7 @@ class TimelineFragment @Inject constructor(
views.composerLayout.views.composerRelatedMessageContent.text = (formattedBody ?: nonFormattedBody)
// Image Event
val data = event.buildImageContentRendererData(dimensionConverter.dpToPx(66))
val data = event.buildImageContentRendererData(dimensionConverter.dpToPx(66), generationVideoDownloadDecider.enableVideoDownloadForThumbnailGeneration())
val isImageVisible = if (data != null) {
imageContentRenderer.render(data, ImageContentRenderer.Mode.THUMBNAIL, views.composerLayout.views.composerRelatedMessageImage)
true

View File

@ -17,6 +17,7 @@ package im.vector.app.features.home.room.detail.timeline.action
import com.airbnb.epoxy.TypedEpoxyController
import com.airbnb.mvrx.Success
import de.spiritcroc.util.ThumbnailGenerationVideoDownloadDecider
import im.vector.app.EmojiCompatFontProvider
import im.vector.app.R
import im.vector.app.core.date.DateFormatKind
@ -66,6 +67,7 @@ class MessageActionsEpoxyController @Inject constructor(
private val spanUtils: SpanUtils,
private val eventDetailsFormatter: EventDetailsFormatter,
private val vectorPreferences: VectorPreferences,
private val thumbnailGenerationVideoDownloadDecider: ThumbnailGenerationVideoDownloadDecider,
private val dateFormatter: VectorDateFormatter,
private val urlMapProvider: UrlMapProvider,
private val locationPinProvider: LocationPinProvider
@ -88,7 +90,7 @@ class MessageActionsEpoxyController @Inject constructor(
matrixItem(state.informationData.matrixItem)
movementMethod(createLinkMovementMethod(host.listener))
imageContentRenderer(host.imageContentRenderer)
data(state.timelineEvent()?.buildImageContentRendererData(host.dimensionConverter.dpToPx(66)))
data(state.timelineEvent()?.buildImageContentRendererData(host.dimensionConverter.dpToPx(66), host.thumbnailGenerationVideoDownloadDecider.enableVideoDownloadForThumbnailGeneration()))
userClicked { host.listener?.didSelectMenuAction(EventSharedAction.OpenUserProfile(state.informationData.senderId)) }
bindingOptions(bindingOptions)
body(body.toEpoxyCharSequence())

View File

@ -500,6 +500,7 @@ class MessageItemFactory @Inject constructor(
maxWidth = maxWidth,
allowNonMxcUrls = informationData.sendState.isSending(),
// Video fallback for generating thumbnails
downloadFallbackIfThumbnailMissing = attributes.generateMissingVideoThumbnails,
fallbackUrl = messageContent.getFileUrl(),
fallbackElementToDecrypt = messageContent.encryptedFileInfo?.toElementToDecrypt()
)

View File

@ -15,6 +15,7 @@
*/
package im.vector.app.features.home.room.detail.timeline.helper
import de.spiritcroc.util.ThumbnailGenerationVideoDownloadDecider
import im.vector.app.EmojiCompatFontProvider
import im.vector.app.R
import im.vector.app.core.resources.StringProvider
@ -36,6 +37,7 @@ class MessageItemAttributesFactory @Inject constructor(
private val stringProvider: StringProvider,
private val displayableEventFormatter: DisplayableEventFormatter,
private val preferencesProvider: UserPreferencesProvider,
private val thumbnailGenerationVideoDownloadDecider: ThumbnailGenerationVideoDownloadDecider,
private val emojiCompatFontProvider: EmojiCompatFontProvider
) {
@ -71,7 +73,8 @@ class MessageItemAttributesFactory @Inject constructor(
threadDetails = threadDetails,
reactionsSummaryEvents = reactionsSummaryEvents,
areThreadMessagesEnabled = preferencesProvider.areThreadMessagesEnabled(),
autoplayAnimatedImages = preferencesProvider.autoplayAnimatedImages()
autoplayAnimatedImages = preferencesProvider.autoplayAnimatedImages(),
generateMissingVideoThumbnails = thumbnailGenerationVideoDownloadDecider.enableVideoDownloadForThumbnailGeneration(informationData)
)
}
}

View File

@ -27,7 +27,7 @@ import org.matrix.android.sdk.api.session.room.model.message.getFileUrl
import org.matrix.android.sdk.api.session.room.model.message.getThumbnailUrl
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
fun TimelineEvent.buildImageContentRendererData(maxHeight: Int): ImageContentRenderer.Data? {
fun TimelineEvent.buildImageContentRendererData(maxHeight: Int, generateMissingVideoThumbnails: Boolean): ImageContentRenderer.Data? {
return when {
root.isImageMessage() -> root.getClearContent().toModel<MessageImageContent>()
?.let { messageImageContent ->
@ -59,6 +59,7 @@ fun TimelineEvent.buildImageContentRendererData(maxHeight: Int): ImageContentRen
maxWidth = maxHeight * 2,
allowNonMxcUrls = false,
// Video fallback for generating thumbnails
downloadFallbackIfThumbnailMissing = generateMissingVideoThumbnails,
fallbackUrl = messageVideoContent.getFileUrl(),
fallbackElementToDecrypt = messageVideoContent.encryptedFileInfo?.toElementToDecrypt()
)

View File

@ -207,6 +207,7 @@ abstract class AbsMessageItem<H : AbsMessageItem.Holder>(
val threadDetails: ThreadDetails? = null,
val areThreadMessagesEnabled: Boolean = false,
val autoplayAnimatedImages: Boolean = false,
val generateMissingVideoThumbnails: Boolean = false,
override val reactionsSummaryEvents: ReactionsSummaryEvents? = null,
) : AbsBaseMessageItem.Attributes {

View File

@ -85,6 +85,7 @@ class ImageContentRenderer @Inject constructor(
// If true will load non mxc url, be careful to set it only for images sent by you
override val allowNonMxcUrls: Boolean = false,
// Fallback for videos: generate preview from video
val downloadFallbackIfThumbnailMissing: Boolean = false,
val fallbackUrl: String? = null,
val fallbackElementToDecrypt: ElementToDecrypt? = null,
) : AttachmentData