mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-02-15 07:28:30 +00:00
Add support for backing up wallpapers.
This commit is contained in:
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Copyright 2024 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.securesms.attachments
|
||||
|
||||
import org.thoughtcrime.securesms.database.AttachmentTable
|
||||
import org.thoughtcrime.securesms.database.AttachmentTable.TransformProperties
|
||||
import org.thoughtcrime.securesms.util.MediaUtil
|
||||
|
||||
/**
|
||||
* A basically-empty [Attachment] that is solely used for inserting an attachment into the [AttachmentTable].
|
||||
*/
|
||||
class WallpaperAttachment() : Attachment(
|
||||
contentType = MediaUtil.IMAGE_WEBP,
|
||||
transferState = AttachmentTable.TRANSFER_PROGRESS_DONE,
|
||||
size = 0,
|
||||
fileName = null,
|
||||
cdn = Cdn.CDN_0,
|
||||
remoteLocation = null,
|
||||
remoteKey = null,
|
||||
remoteIv = null,
|
||||
remoteDigest = null,
|
||||
incrementalDigest = null,
|
||||
fastPreflightId = null,
|
||||
voiceNote = false,
|
||||
borderless = false,
|
||||
videoGif = false,
|
||||
width = 0,
|
||||
height = 0,
|
||||
incrementalMacChunkSize = 0,
|
||||
quote = false,
|
||||
uploadTimestamp = 0,
|
||||
caption = null,
|
||||
stickerLocator = null,
|
||||
blurHash = null,
|
||||
audioHash = null,
|
||||
transformProperties = TransformProperties.empty(),
|
||||
uuid = null
|
||||
) {
|
||||
override val uri = null
|
||||
override val publicUri = null
|
||||
override val thumbnailUri = null
|
||||
}
|
||||
@@ -6,8 +6,14 @@
|
||||
package org.thoughtcrime.securesms.backup.v2.database
|
||||
|
||||
import org.signal.core.util.deleteAll
|
||||
import org.thoughtcrime.securesms.attachments.Attachment
|
||||
import org.thoughtcrime.securesms.attachments.AttachmentId
|
||||
import org.thoughtcrime.securesms.database.AttachmentTable
|
||||
|
||||
fun AttachmentTable.clearAllDataForBackupRestore() {
|
||||
writableDatabase.deleteAll(AttachmentTable.TABLE_NAME)
|
||||
}
|
||||
|
||||
fun AttachmentTable.restoreWallpaperAttachment(attachment: Attachment): AttachmentId? {
|
||||
return insertAttachmentsForMessage(AttachmentTable.WALLPAPER_MESSAGE_ID, listOf(attachment), emptyList()).values.firstOrNull()
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ package org.thoughtcrime.securesms.backup.v2.database
|
||||
|
||||
import android.content.ContentValues
|
||||
import androidx.core.content.contentValuesOf
|
||||
import okio.ByteString
|
||||
import org.signal.core.util.Base64
|
||||
import org.signal.core.util.Hex
|
||||
import org.signal.core.util.SqlUtil
|
||||
@@ -17,17 +16,13 @@ import org.signal.core.util.orNull
|
||||
import org.signal.core.util.requireLong
|
||||
import org.signal.core.util.toInt
|
||||
import org.signal.core.util.update
|
||||
import org.thoughtcrime.securesms.attachments.ArchivedAttachment
|
||||
import org.thoughtcrime.securesms.attachments.Attachment
|
||||
import org.thoughtcrime.securesms.attachments.Cdn
|
||||
import org.thoughtcrime.securesms.attachments.PointerAttachment
|
||||
import org.thoughtcrime.securesms.attachments.TombstoneAttachment
|
||||
import org.thoughtcrime.securesms.backup.v2.ImportState
|
||||
import org.thoughtcrime.securesms.backup.v2.proto.BodyRange
|
||||
import org.thoughtcrime.securesms.backup.v2.proto.ChatItem
|
||||
import org.thoughtcrime.securesms.backup.v2.proto.ChatUpdateMessage
|
||||
import org.thoughtcrime.securesms.backup.v2.proto.ContactAttachment
|
||||
import org.thoughtcrime.securesms.backup.v2.proto.FilePointer
|
||||
import org.thoughtcrime.securesms.backup.v2.proto.GroupCall
|
||||
import org.thoughtcrime.securesms.backup.v2.proto.IndividualCall
|
||||
import org.thoughtcrime.securesms.backup.v2.proto.LinkPreview
|
||||
@@ -39,8 +34,8 @@ import org.thoughtcrime.securesms.backup.v2.proto.SendStatus
|
||||
import org.thoughtcrime.securesms.backup.v2.proto.SimpleChatUpdate
|
||||
import org.thoughtcrime.securesms.backup.v2.proto.StandardMessage
|
||||
import org.thoughtcrime.securesms.backup.v2.proto.Sticker
|
||||
import org.thoughtcrime.securesms.backup.v2.util.toLocalAttachment
|
||||
import org.thoughtcrime.securesms.contactshare.Contact
|
||||
import org.thoughtcrime.securesms.database.AttachmentTable
|
||||
import org.thoughtcrime.securesms.database.CallTable
|
||||
import org.thoughtcrime.securesms.database.GroupReceiptTable
|
||||
import org.thoughtcrime.securesms.database.MessageTable
|
||||
@@ -74,9 +69,6 @@ import org.thoughtcrime.securesms.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId
|
||||
import org.thoughtcrime.securesms.stickers.StickerLocator
|
||||
import org.thoughtcrime.securesms.util.JsonUtils
|
||||
import org.whispersystems.signalservice.api.backup.MediaName
|
||||
import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentPointer
|
||||
import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentRemoteId
|
||||
import org.whispersystems.signalservice.api.payments.Money
|
||||
import org.whispersystems.signalservice.api.push.ServiceId
|
||||
import org.whispersystems.signalservice.api.util.UuidUtil
|
||||
@@ -342,7 +334,7 @@ class ChatItemImportInserter(
|
||||
address.country
|
||||
)
|
||||
},
|
||||
Contact.Avatar(null, backupContact.avatar.toLocalAttachment(), true)
|
||||
Contact.Avatar(null, backupContact.avatar.toLocalAttachment(importState = importState, voiceNote = false, borderless = false, gif = false, wasDownloaded = true), true)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -917,29 +909,6 @@ class ChatItemImportInserter(
|
||||
?: false
|
||||
}
|
||||
|
||||
private fun LinkPreview.toLocalLinkPreview(): org.thoughtcrime.securesms.linkpreview.LinkPreview {
|
||||
return org.thoughtcrime.securesms.linkpreview.LinkPreview(
|
||||
this.url,
|
||||
this.title ?: "",
|
||||
this.description ?: "",
|
||||
this.date ?: 0,
|
||||
Optional.ofNullable(this.image?.toLocalAttachment())
|
||||
)
|
||||
}
|
||||
|
||||
private fun MessageAttachment.toLocalAttachment(contentType: String? = this.pointer?.contentType, fileName: String? = this.pointer?.fileName): Attachment? {
|
||||
return this.pointer?.toLocalAttachment(
|
||||
voiceNote = this.flag == MessageAttachment.Flag.VOICE_MESSAGE,
|
||||
borderless = this.flag == MessageAttachment.Flag.BORDERLESS,
|
||||
gif = this.flag == MessageAttachment.Flag.GIF,
|
||||
wasDownloaded = this.wasDownloaded,
|
||||
stickerLocator = null,
|
||||
contentType = contentType,
|
||||
fileName = fileName,
|
||||
uuid = this.clientUuid
|
||||
)
|
||||
}
|
||||
|
||||
private fun Quote.QuotedAttachment.toLocalAttachment(): Attachment? {
|
||||
val thumbnail = this.thumbnail?.toLocalAttachment(this.contentType, this.fileName)
|
||||
|
||||
@@ -962,6 +931,11 @@ class ChatItemImportInserter(
|
||||
if (this == null) return null
|
||||
|
||||
return data_.toLocalAttachment(
|
||||
importState = importState,
|
||||
voiceNote = false,
|
||||
gif = false,
|
||||
borderless = false,
|
||||
wasDownloaded = true,
|
||||
stickerLocator = StickerLocator(
|
||||
packId = Hex.toStringCondensed(packId.toByteArray()),
|
||||
packKey = Hex.toStringCondensed(packKey.toByteArray()),
|
||||
@@ -971,90 +945,38 @@ class ChatItemImportInserter(
|
||||
)
|
||||
}
|
||||
|
||||
private fun FilePointer?.toLocalAttachment(
|
||||
borderless: Boolean = false,
|
||||
gif: Boolean = false,
|
||||
voiceNote: Boolean = false,
|
||||
wasDownloaded: Boolean = true,
|
||||
stickerLocator: StickerLocator? = null,
|
||||
contentType: String? = this?.contentType,
|
||||
fileName: String? = this?.fileName,
|
||||
uuid: ByteString? = null
|
||||
): Attachment? {
|
||||
return if (this == null) {
|
||||
null
|
||||
} else if (this.attachmentLocator != null) {
|
||||
val signalAttachmentPointer = SignalServiceAttachmentPointer(
|
||||
cdnNumber = this.attachmentLocator.cdnNumber,
|
||||
remoteId = SignalServiceAttachmentRemoteId.from(this.attachmentLocator.cdnKey),
|
||||
contentType = contentType,
|
||||
key = this.attachmentLocator.key.toByteArray(),
|
||||
size = Optional.ofNullable(this.attachmentLocator.size),
|
||||
preview = Optional.empty(),
|
||||
width = this.width ?: 0,
|
||||
height = this.height ?: 0,
|
||||
digest = Optional.ofNullable(this.attachmentLocator.digest.toByteArray()),
|
||||
incrementalDigest = Optional.ofNullable(this.incrementalMac?.toByteArray()),
|
||||
incrementalMacChunkSize = this.incrementalMacChunkSize ?: 0,
|
||||
fileName = Optional.ofNullable(fileName),
|
||||
voiceNote = voiceNote,
|
||||
isBorderless = borderless,
|
||||
isGif = gif,
|
||||
caption = Optional.ofNullable(this.caption),
|
||||
blurHash = Optional.ofNullable(this.blurHash),
|
||||
uploadTimestamp = this.attachmentLocator.uploadTimestamp,
|
||||
uuid = UuidUtil.fromByteStringOrNull(uuid)
|
||||
)
|
||||
PointerAttachment.forPointer(
|
||||
pointer = Optional.of(signalAttachmentPointer),
|
||||
stickerLocator = stickerLocator,
|
||||
transferState = if (wasDownloaded) AttachmentTable.TRANSFER_NEEDS_RESTORE else AttachmentTable.TRANSFER_PROGRESS_PENDING
|
||||
).orNull()
|
||||
} else if (this.invalidAttachmentLocator != null) {
|
||||
TombstoneAttachment(
|
||||
contentType = contentType,
|
||||
incrementalMac = this.incrementalMac?.toByteArray(),
|
||||
incrementalMacChunkSize = this.incrementalMacChunkSize,
|
||||
width = this.width,
|
||||
height = this.height,
|
||||
caption = this.caption,
|
||||
blurHash = this.blurHash,
|
||||
voiceNote = voiceNote,
|
||||
borderless = borderless,
|
||||
gif = gif,
|
||||
quote = false,
|
||||
uuid = UuidUtil.fromByteStringOrNull(uuid)
|
||||
)
|
||||
} else if (this.backupLocator != null) {
|
||||
ArchivedAttachment(
|
||||
contentType = contentType,
|
||||
size = this.backupLocator.size.toLong(),
|
||||
cdn = this.backupLocator.transitCdnNumber ?: Cdn.CDN_0.cdnNumber,
|
||||
key = this.backupLocator.key.toByteArray(),
|
||||
iv = null,
|
||||
cdnKey = this.backupLocator.transitCdnKey,
|
||||
archiveCdn = this.backupLocator.cdnNumber,
|
||||
archiveMediaName = this.backupLocator.mediaName,
|
||||
archiveMediaId = importState.backupKey.deriveMediaId(MediaName(this.backupLocator.mediaName)).encode(),
|
||||
archiveThumbnailMediaId = importState.backupKey.deriveMediaId(MediaName.forThumbnailFromMediaName(this.backupLocator.mediaName)).encode(),
|
||||
digest = this.backupLocator.digest.toByteArray(),
|
||||
incrementalMac = this.incrementalMac?.toByteArray(),
|
||||
incrementalMacChunkSize = this.incrementalMacChunkSize,
|
||||
width = this.width,
|
||||
height = this.height,
|
||||
caption = this.caption,
|
||||
blurHash = this.blurHash,
|
||||
voiceNote = voiceNote,
|
||||
borderless = borderless,
|
||||
gif = gif,
|
||||
quote = false,
|
||||
stickerLocator = stickerLocator,
|
||||
uuid = UuidUtil.fromByteStringOrNull(uuid),
|
||||
fileName = fileName
|
||||
)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
private fun LinkPreview.toLocalLinkPreview(): org.thoughtcrime.securesms.linkpreview.LinkPreview {
|
||||
return org.thoughtcrime.securesms.linkpreview.LinkPreview(
|
||||
this.url,
|
||||
this.title ?: "",
|
||||
this.description ?: "",
|
||||
this.date ?: 0,
|
||||
Optional.ofNullable(this.image?.toLocalAttachment(importState = importState, voiceNote = false, borderless = false, gif = false, wasDownloaded = true))
|
||||
)
|
||||
}
|
||||
|
||||
private fun MessageAttachment.toLocalAttachment(): Attachment? {
|
||||
return pointer?.toLocalAttachment(
|
||||
importState = importState,
|
||||
voiceNote = flag == MessageAttachment.Flag.VOICE_MESSAGE,
|
||||
gif = flag == MessageAttachment.Flag.GIF,
|
||||
borderless = flag == MessageAttachment.Flag.BORDERLESS,
|
||||
wasDownloaded = wasDownloaded,
|
||||
uuid = clientUuid
|
||||
)
|
||||
}
|
||||
|
||||
private fun MessageAttachment.toLocalAttachment(contentType: String?, fileName: String?): Attachment? {
|
||||
return pointer?.toLocalAttachment(
|
||||
importState = importState,
|
||||
voiceNote = flag == MessageAttachment.Flag.VOICE_MESSAGE,
|
||||
gif = flag == MessageAttachment.Flag.GIF,
|
||||
borderless = flag == MessageAttachment.Flag.BORDERLESS,
|
||||
wasDownloaded = wasDownloaded,
|
||||
contentType = contentType,
|
||||
fileName = fileName,
|
||||
uuid = clientUuid
|
||||
)
|
||||
}
|
||||
|
||||
private fun ContactAttachment.Name?.toLocal(): Contact.Name {
|
||||
|
||||
@@ -7,7 +7,6 @@ package org.thoughtcrime.securesms.backup.v2.database
|
||||
|
||||
import android.database.Cursor
|
||||
import androidx.core.content.contentValuesOf
|
||||
import com.google.protobuf.InvalidProtocolBufferException
|
||||
import org.signal.core.util.SqlUtil
|
||||
import org.signal.core.util.insertInto
|
||||
import org.signal.core.util.logging.Log
|
||||
@@ -16,15 +15,26 @@ import org.signal.core.util.requireBoolean
|
||||
import org.signal.core.util.requireInt
|
||||
import org.signal.core.util.requireLong
|
||||
import org.signal.core.util.toInt
|
||||
import org.thoughtcrime.securesms.attachments.AttachmentId
|
||||
import org.thoughtcrime.securesms.backup.v2.ImportState
|
||||
import org.thoughtcrime.securesms.backup.v2.proto.Chat
|
||||
import org.thoughtcrime.securesms.backup.v2.util.BackupConverters
|
||||
import org.thoughtcrime.securesms.backup.v2.proto.ChatStyle
|
||||
import org.thoughtcrime.securesms.backup.v2.util.ChatStyleConverter
|
||||
import org.thoughtcrime.securesms.backup.v2.util.toLocal
|
||||
import org.thoughtcrime.securesms.backup.v2.util.toLocalAttachment
|
||||
import org.thoughtcrime.securesms.conversation.colors.ChatColors
|
||||
import org.thoughtcrime.securesms.database.RecipientTable
|
||||
import org.thoughtcrime.securesms.database.SQLiteDatabase
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase
|
||||
import org.thoughtcrime.securesms.database.ThreadTable
|
||||
import org.thoughtcrime.securesms.database.model.databaseprotos.ChatColor
|
||||
import org.thoughtcrime.securesms.database.model.databaseprotos.Wallpaper
|
||||
import org.thoughtcrime.securesms.mms.PartAuthority
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId
|
||||
import org.thoughtcrime.securesms.util.decodeOrNull
|
||||
import org.thoughtcrime.securesms.wallpaper.ChatWallpaper
|
||||
import org.thoughtcrime.securesms.wallpaper.ChatWallpaperFactory
|
||||
import org.thoughtcrime.securesms.wallpaper.UriChatWallpaper
|
||||
import java.io.Closeable
|
||||
|
||||
private val TAG = Log.tag(ThreadTable::class.java)
|
||||
@@ -43,14 +53,15 @@ fun ThreadTable.getThreadsForBackup(): ChatExportIterator {
|
||||
${RecipientTable.TABLE_NAME}.${RecipientTable.MUTE_UNTIL},
|
||||
${RecipientTable.TABLE_NAME}.${RecipientTable.MENTION_SETTING},
|
||||
${RecipientTable.TABLE_NAME}.${RecipientTable.CHAT_COLORS},
|
||||
${RecipientTable.TABLE_NAME}.${RecipientTable.CUSTOM_CHAT_COLORS_ID}
|
||||
${RecipientTable.TABLE_NAME}.${RecipientTable.CUSTOM_CHAT_COLORS_ID},
|
||||
${RecipientTable.TABLE_NAME}.${RecipientTable.WALLPAPER}
|
||||
FROM ${ThreadTable.TABLE_NAME}
|
||||
LEFT OUTER JOIN ${RecipientTable.TABLE_NAME} ON ${ThreadTable.TABLE_NAME}.${ThreadTable.RECIPIENT_ID} = ${RecipientTable.TABLE_NAME}.${RecipientTable.ID}
|
||||
WHERE ${ThreadTable.ACTIVE} = 1
|
||||
"""
|
||||
val cursor = readableDatabase.query(query)
|
||||
|
||||
return ChatExportIterator(cursor)
|
||||
return ChatExportIterator(cursor, readableDatabase)
|
||||
}
|
||||
|
||||
fun ThreadTable.clearAllDataForBackupRestore() {
|
||||
@@ -59,10 +70,29 @@ fun ThreadTable.clearAllDataForBackupRestore() {
|
||||
clearCache()
|
||||
}
|
||||
|
||||
fun ThreadTable.restoreFromBackup(chat: Chat, recipientId: RecipientId, importState: ImportState): Long? {
|
||||
fun ThreadTable.restoreFromBackup(chat: Chat, recipientId: RecipientId, importState: ImportState): Long {
|
||||
val chatColor = chat.style?.toLocal(importState)
|
||||
val chatColorWithId = if (chatColor != null && chatColor.id is ChatColors.Id.NotSet) {
|
||||
val savedColors = SignalDatabase.chatColors.getSavedChatColors()
|
||||
val match = savedColors.find { it.matchesWithoutId(chatColor) }
|
||||
match ?: SignalDatabase.chatColors.saveChatColors(chatColor)
|
||||
} else {
|
||||
chatColor
|
||||
}
|
||||
|
||||
// TODO [backup] Wallpaper
|
||||
val wallpaperAttachmentId: AttachmentId? = chat.style?.wallpaperPhoto?.let { filePointer ->
|
||||
filePointer.toLocalAttachment(importState)?.let {
|
||||
SignalDatabase.attachments.restoreWallpaperAttachment(it)
|
||||
}
|
||||
}
|
||||
|
||||
val chatWallpaper = chat.style?.parseChatWallpaper(wallpaperAttachmentId)?.let {
|
||||
if (chat.style.dimWallpaperInDarkMode) {
|
||||
ChatWallpaperFactory.updateWithDimming(it, ChatWallpaper.FIXED_DIM_LEVEL_FOR_DARK_THEME)
|
||||
} else {
|
||||
it
|
||||
}
|
||||
}
|
||||
|
||||
val threadId = writableDatabase
|
||||
.insertInto(ThreadTable.TABLE_NAME)
|
||||
@@ -84,7 +114,9 @@ fun ThreadTable.restoreFromBackup(chat: Chat, recipientId: RecipientId, importSt
|
||||
RecipientTable.MESSAGE_EXPIRATION_TIME to chat.expirationTimerMs,
|
||||
RecipientTable.MESSAGE_EXPIRATION_TIME_VERSION to chat.expireTimerVersion,
|
||||
RecipientTable.CHAT_COLORS to chatColor?.serialize()?.encode(),
|
||||
RecipientTable.CUSTOM_CHAT_COLORS_ID to (chatColor?.id ?: ChatColors.Id.NotSet).longValue
|
||||
RecipientTable.CUSTOM_CHAT_COLORS_ID to (chatColor?.id ?: ChatColors.Id.NotSet).longValue,
|
||||
RecipientTable.WALLPAPER_URI to if (chatWallpaper is UriChatWallpaper) chatWallpaper.uri.toString() else null,
|
||||
RecipientTable.WALLPAPER to chatWallpaper?.serialize()?.encode()
|
||||
),
|
||||
"${RecipientTable.ID} = ?",
|
||||
SqlUtil.buildArgs(recipientId.toLong())
|
||||
@@ -93,7 +125,7 @@ fun ThreadTable.restoreFromBackup(chat: Chat, recipientId: RecipientId, importSt
|
||||
return threadId
|
||||
}
|
||||
|
||||
class ChatExportIterator(private val cursor: Cursor) : Iterator<Chat>, Closeable {
|
||||
class ChatExportIterator(private val cursor: Cursor, private val readableDatabase: SQLiteDatabase) : Iterator<Chat>, Closeable {
|
||||
override fun hasNext(): Boolean {
|
||||
return cursor.count > 0 && !cursor.isLast
|
||||
}
|
||||
@@ -103,14 +135,15 @@ class ChatExportIterator(private val cursor: Cursor) : Iterator<Chat>, Closeable
|
||||
throw NoSuchElementException()
|
||||
}
|
||||
|
||||
val serializedChatColors = cursor.requireBlob(RecipientTable.CHAT_COLORS)
|
||||
val chatColorId = ChatColors.Id.forLongValue(cursor.requireLong(RecipientTable.CUSTOM_CHAT_COLORS_ID))
|
||||
val chatColors: ChatColors? = serializedChatColors?.let { serialized ->
|
||||
try {
|
||||
ChatColors.forChatColor(chatColorId, ChatColor.ADAPTER.decode(serialized))
|
||||
} catch (e: InvalidProtocolBufferException) {
|
||||
null
|
||||
}
|
||||
val customChatColorsId = ChatColors.Id.forLongValue(cursor.requireLong(RecipientTable.CUSTOM_CHAT_COLORS_ID))
|
||||
|
||||
val chatColors: ChatColors? = cursor.requireBlob(RecipientTable.CHAT_COLORS)?.let { serializedChatColors ->
|
||||
val chatColor = ChatColor.ADAPTER.decodeOrNull(serializedChatColors)
|
||||
chatColor?.let { ChatColors.forChatColor(customChatColorsId, it) }
|
||||
}
|
||||
|
||||
val chatWallpaper: Wallpaper? = cursor.requireBlob(RecipientTable.WALLPAPER)?.let { serializedWallpaper ->
|
||||
Wallpaper.ADAPTER.decodeOrNull(serializedWallpaper)
|
||||
}
|
||||
|
||||
return Chat(
|
||||
@@ -123,7 +156,12 @@ class ChatExportIterator(private val cursor: Cursor) : Iterator<Chat>, Closeable
|
||||
muteUntilMs = cursor.requireLong(RecipientTable.MUTE_UNTIL),
|
||||
markedUnread = ThreadTable.ReadStatus.deserialize(cursor.requireInt(ThreadTable.READ)) == ThreadTable.ReadStatus.FORCED_UNREAD,
|
||||
dontNotifyForMentionsIfMuted = RecipientTable.MentionSetting.DO_NOT_NOTIFY.id == cursor.requireInt(RecipientTable.MENTION_SETTING),
|
||||
style = BackupConverters.constructRemoteChatStyle(chatColors, chatColorId)
|
||||
style = ChatStyleConverter.constructRemoteChatStyle(
|
||||
readableDatabase = readableDatabase,
|
||||
chatColors = chatColors,
|
||||
chatColorId = customChatColorsId,
|
||||
chatWallpaper = chatWallpaper
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -131,3 +169,15 @@ class ChatExportIterator(private val cursor: Cursor) : Iterator<Chat>, Closeable
|
||||
cursor.close()
|
||||
}
|
||||
}
|
||||
|
||||
private fun ChatStyle.parseChatWallpaper(wallpaperAttachmentId: AttachmentId?): ChatWallpaper? {
|
||||
if (this.wallpaperPreset != null) {
|
||||
return this.wallpaperPreset.toLocal()
|
||||
}
|
||||
|
||||
if (wallpaperAttachmentId != null) {
|
||||
return UriChatWallpaper(PartAuthority.getAttachmentDataUri(wallpaperAttachmentId), 0f)
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ import org.thoughtcrime.securesms.backup.v2.proto.AccountData
|
||||
import org.thoughtcrime.securesms.backup.v2.proto.ChatStyle
|
||||
import org.thoughtcrime.securesms.backup.v2.proto.Frame
|
||||
import org.thoughtcrime.securesms.backup.v2.stream.BackupFrameEmitter
|
||||
import org.thoughtcrime.securesms.backup.v2.util.BackupConverters
|
||||
import org.thoughtcrime.securesms.backup.v2.util.ChatStyleConverter
|
||||
import org.thoughtcrime.securesms.backup.v2.util.toLocal
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.InAppPaymentsRepository
|
||||
import org.thoughtcrime.securesms.components.settings.app.usernamelinks.UsernameQrCodeColorScheme
|
||||
@@ -50,6 +50,7 @@ object AccountDataProcessor {
|
||||
val donationSubscriber = db.inAppPaymentSubscriberTable.getByCurrencyCode(donationCurrency.currencyCode, InAppPaymentSubscriberRecord.Type.DONATION)
|
||||
|
||||
val chatColors = SignalStore.chatColors.chatColors
|
||||
val chatWallpaper = SignalStore.wallpaper.currentRawWallpaper
|
||||
|
||||
emitter.emit(
|
||||
Frame(
|
||||
@@ -87,12 +88,12 @@ object AccountDataProcessor {
|
||||
hasSeenGroupStoryEducationSheet = signalStore.storyValues.userHasSeenGroupStoryEducationSheet,
|
||||
hasCompletedUsernameOnboarding = signalStore.uiHintValues.hasCompletedUsernameOnboarding(),
|
||||
customChatColors = db.chatColorsTable.getSavedChatColors().toRemoteChatColors(),
|
||||
defaultChatStyle = BackupConverters.constructRemoteChatStyle(chatColors, chatColors?.id ?: ChatColors.Id.NotSet)?.also {
|
||||
it.newBuilder().apply {
|
||||
// TODO [backup] We should do this elsewhere once we handle wallpaper better
|
||||
dimWallpaperInDarkMode = (SignalStore.wallpaper.wallpaper?.dimLevelForDarkTheme ?: 0f) > 0f
|
||||
}.build()
|
||||
}
|
||||
defaultChatStyle = ChatStyleConverter.constructRemoteChatStyle(
|
||||
readableDatabase = db.signalReadableDatabase,
|
||||
chatColors = chatColors,
|
||||
chatColorId = chatColors?.id ?: ChatColors.Id.NotSet,
|
||||
chatWallpaper = chatWallpaper
|
||||
)
|
||||
),
|
||||
donationSubscriberData = donationSubscriber?.toSubscriberData(signalStore.inAppPaymentValues.isDonationSubscriptionManuallyCancelled())
|
||||
)
|
||||
|
||||
@@ -39,7 +39,7 @@ object ChatBackupProcessor {
|
||||
return
|
||||
}
|
||||
|
||||
SignalDatabase.threads.restoreFromBackup(chat, recipientId, importState)?.let { threadId ->
|
||||
SignalDatabase.threads.restoreFromBackup(chat, recipientId, importState).let { threadId ->
|
||||
importState.chatIdToLocalRecipientId[chat.id] = recipientId
|
||||
importState.chatIdToLocalThreadId[chat.id] = threadId
|
||||
importState.chatIdToBackupRecipientId[chat.id] = chat.recipientId
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
/*
|
||||
* Copyright 2024 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.securesms.backup.v2.util
|
||||
|
||||
import okio.ByteString
|
||||
import org.signal.core.util.orNull
|
||||
import org.thoughtcrime.securesms.attachments.ArchivedAttachment
|
||||
import org.thoughtcrime.securesms.attachments.Attachment
|
||||
import org.thoughtcrime.securesms.attachments.Cdn
|
||||
import org.thoughtcrime.securesms.attachments.PointerAttachment
|
||||
import org.thoughtcrime.securesms.attachments.TombstoneAttachment
|
||||
import org.thoughtcrime.securesms.backup.v2.ImportState
|
||||
import org.thoughtcrime.securesms.backup.v2.proto.FilePointer
|
||||
import org.thoughtcrime.securesms.database.AttachmentTable
|
||||
import org.thoughtcrime.securesms.stickers.StickerLocator
|
||||
import org.whispersystems.signalservice.api.backup.MediaName
|
||||
import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentPointer
|
||||
import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentRemoteId
|
||||
import org.whispersystems.signalservice.api.util.UuidUtil
|
||||
import java.util.Optional
|
||||
|
||||
/**
|
||||
* Converts a [FilePointer] to a local [Attachment] object for inserting into the database.
|
||||
*/
|
||||
fun FilePointer?.toLocalAttachment(
|
||||
importState: ImportState,
|
||||
voiceNote: Boolean = false,
|
||||
borderless: Boolean = false,
|
||||
gif: Boolean = false,
|
||||
wasDownloaded: Boolean = false,
|
||||
stickerLocator: StickerLocator? = null,
|
||||
contentType: String? = this?.contentType,
|
||||
fileName: String? = this?.fileName,
|
||||
uuid: ByteString? = null
|
||||
): Attachment? {
|
||||
if (this == null) return null
|
||||
|
||||
if (this.attachmentLocator != null) {
|
||||
val signalAttachmentPointer = SignalServiceAttachmentPointer(
|
||||
cdnNumber = this.attachmentLocator.cdnNumber,
|
||||
remoteId = SignalServiceAttachmentRemoteId.from(attachmentLocator.cdnKey),
|
||||
contentType = contentType,
|
||||
key = this.attachmentLocator.key.toByteArray(),
|
||||
size = Optional.ofNullable(attachmentLocator.size),
|
||||
preview = Optional.empty(),
|
||||
width = this.width ?: 0,
|
||||
height = this.height ?: 0,
|
||||
digest = Optional.ofNullable(this.attachmentLocator.digest.toByteArray()),
|
||||
incrementalDigest = Optional.ofNullable(this.incrementalMac?.toByteArray()),
|
||||
incrementalMacChunkSize = this.incrementalMacChunkSize ?: 0,
|
||||
fileName = Optional.ofNullable(fileName),
|
||||
voiceNote = voiceNote,
|
||||
isBorderless = borderless,
|
||||
isGif = gif,
|
||||
caption = Optional.ofNullable(this.caption),
|
||||
blurHash = Optional.ofNullable(this.blurHash),
|
||||
uploadTimestamp = this.attachmentLocator.uploadTimestamp,
|
||||
uuid = UuidUtil.fromByteStringOrNull(uuid)
|
||||
)
|
||||
return PointerAttachment.forPointer(
|
||||
pointer = Optional.of(signalAttachmentPointer),
|
||||
stickerLocator = stickerLocator,
|
||||
transferState = if (wasDownloaded) AttachmentTable.TRANSFER_NEEDS_RESTORE else AttachmentTable.TRANSFER_PROGRESS_PENDING
|
||||
).orNull()
|
||||
} else if (this.invalidAttachmentLocator != null) {
|
||||
return TombstoneAttachment(
|
||||
contentType = contentType,
|
||||
incrementalMac = this.incrementalMac?.toByteArray(),
|
||||
incrementalMacChunkSize = this.incrementalMacChunkSize,
|
||||
width = this.width,
|
||||
height = this.height,
|
||||
caption = this.caption,
|
||||
blurHash = this.blurHash,
|
||||
voiceNote = voiceNote,
|
||||
borderless = borderless,
|
||||
gif = gif,
|
||||
quote = false,
|
||||
uuid = UuidUtil.fromByteStringOrNull(uuid)
|
||||
)
|
||||
} else if (this.backupLocator != null) {
|
||||
return ArchivedAttachment(
|
||||
contentType = contentType,
|
||||
size = this.backupLocator.size.toLong(),
|
||||
cdn = this.backupLocator.transitCdnNumber ?: Cdn.CDN_0.cdnNumber,
|
||||
key = this.backupLocator.key.toByteArray(),
|
||||
iv = null,
|
||||
cdnKey = this.backupLocator.transitCdnKey,
|
||||
archiveCdn = this.backupLocator.cdnNumber,
|
||||
archiveMediaName = this.backupLocator.mediaName,
|
||||
archiveMediaId = importState.backupKey.deriveMediaId(MediaName(this.backupLocator.mediaName)).encode(),
|
||||
archiveThumbnailMediaId = importState.backupKey.deriveMediaId(MediaName.forThumbnailFromMediaName(this.backupLocator.mediaName)).encode(),
|
||||
digest = this.backupLocator.digest.toByteArray(),
|
||||
incrementalMac = this.incrementalMac?.toByteArray(),
|
||||
incrementalMacChunkSize = this.incrementalMacChunkSize,
|
||||
width = this.width,
|
||||
height = this.height,
|
||||
caption = this.caption,
|
||||
blurHash = this.blurHash,
|
||||
voiceNote = voiceNote,
|
||||
borderless = borderless,
|
||||
gif = gif,
|
||||
quote = false,
|
||||
stickerLocator = stickerLocator,
|
||||
uuid = UuidUtil.fromByteStringOrNull(uuid),
|
||||
fileName = fileName
|
||||
)
|
||||
}
|
||||
return null
|
||||
}
|
||||
@@ -0,0 +1,279 @@
|
||||
/*
|
||||
* Copyright 2024 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.securesms.backup.v2.util
|
||||
|
||||
import android.database.Cursor
|
||||
import okio.ByteString
|
||||
import okio.ByteString.Companion.toByteString
|
||||
import org.signal.core.util.Base64
|
||||
import org.signal.core.util.readToSingleObject
|
||||
import org.signal.core.util.requireBlob
|
||||
import org.signal.core.util.requireInt
|
||||
import org.signal.core.util.requireLong
|
||||
import org.signal.core.util.requireString
|
||||
import org.signal.core.util.select
|
||||
import org.thoughtcrime.securesms.attachments.AttachmentId
|
||||
import org.thoughtcrime.securesms.backup.v2.ImportState
|
||||
import org.thoughtcrime.securesms.backup.v2.proto.ChatStyle
|
||||
import org.thoughtcrime.securesms.backup.v2.proto.FilePointer
|
||||
import org.thoughtcrime.securesms.conversation.colors.ChatColors
|
||||
import org.thoughtcrime.securesms.conversation.colors.ChatColorsPalette
|
||||
import org.thoughtcrime.securesms.database.AttachmentTable
|
||||
import org.thoughtcrime.securesms.database.SQLiteDatabase
|
||||
import org.thoughtcrime.securesms.database.model.databaseprotos.Wallpaper
|
||||
import org.thoughtcrime.securesms.mms.PartUriParser
|
||||
import org.thoughtcrime.securesms.util.UriUtil
|
||||
import org.thoughtcrime.securesms.wallpaper.ChatWallpaper
|
||||
import org.thoughtcrime.securesms.wallpaper.GradientChatWallpaper
|
||||
import org.thoughtcrime.securesms.wallpaper.SingleColorChatWallpaper
|
||||
|
||||
/**
|
||||
* Contains a collection of methods to chat styles to and from their archive format.
|
||||
* These are in a file of their own just because they're rather long (with all of the various constants to map between) and used in multiple places.
|
||||
*/
|
||||
object ChatStyleConverter {
|
||||
fun constructRemoteChatStyle(
|
||||
readableDatabase: SQLiteDatabase,
|
||||
chatColors: ChatColors?,
|
||||
chatColorId: ChatColors.Id,
|
||||
chatWallpaper: Wallpaper?
|
||||
): ChatStyle? {
|
||||
if (chatColors == null && chatWallpaper == null) {
|
||||
return null
|
||||
}
|
||||
|
||||
val chatStyleBuilder = ChatStyle.Builder()
|
||||
|
||||
if (chatColors != null) {
|
||||
when (chatColorId) {
|
||||
ChatColors.Id.NotSet -> {}
|
||||
ChatColors.Id.Auto -> {
|
||||
chatStyleBuilder.autoBubbleColor = ChatStyle.AutomaticBubbleColor()
|
||||
}
|
||||
|
||||
ChatColors.Id.BuiltIn -> {
|
||||
chatStyleBuilder.bubbleColorPreset = chatColors.toRemote()
|
||||
}
|
||||
|
||||
is ChatColors.Id.Custom -> {
|
||||
chatStyleBuilder.customColorId = chatColorId.longValue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (chatWallpaper != null) {
|
||||
when {
|
||||
chatWallpaper.singleColor != null -> {
|
||||
chatStyleBuilder.wallpaperPreset = chatWallpaper.singleColor.color.toRemoteWallpaperPreset()
|
||||
}
|
||||
chatWallpaper.linearGradient != null -> {
|
||||
chatStyleBuilder.wallpaperPreset = chatWallpaper.linearGradient.toRemoteWallpaperPreset()
|
||||
}
|
||||
chatWallpaper.file_ != null -> {
|
||||
chatStyleBuilder.wallpaperPhoto = chatWallpaper.file_.toFilePointer(readableDatabase)
|
||||
}
|
||||
}
|
||||
|
||||
chatStyleBuilder.dimWallpaperInDarkMode = chatWallpaper.dimLevelInDarkTheme > 0
|
||||
}
|
||||
|
||||
return chatStyleBuilder.build()
|
||||
}
|
||||
}
|
||||
|
||||
fun ChatStyle.toLocal(importState: ImportState): ChatColors? {
|
||||
if (this.bubbleColorPreset != null) {
|
||||
return when (this.bubbleColorPreset) {
|
||||
// Solids
|
||||
ChatStyle.BubbleColorPreset.SOLID_CRIMSON -> ChatColorsPalette.Bubbles.CRIMSON
|
||||
ChatStyle.BubbleColorPreset.SOLID_VERMILION -> ChatColorsPalette.Bubbles.VERMILION
|
||||
ChatStyle.BubbleColorPreset.SOLID_BURLAP -> ChatColorsPalette.Bubbles.BURLAP
|
||||
ChatStyle.BubbleColorPreset.SOLID_FOREST -> ChatColorsPalette.Bubbles.FOREST
|
||||
ChatStyle.BubbleColorPreset.SOLID_WINTERGREEN -> ChatColorsPalette.Bubbles.WINTERGREEN
|
||||
ChatStyle.BubbleColorPreset.SOLID_TEAL -> ChatColorsPalette.Bubbles.TEAL
|
||||
ChatStyle.BubbleColorPreset.SOLID_BLUE -> ChatColorsPalette.Bubbles.BLUE
|
||||
ChatStyle.BubbleColorPreset.SOLID_INDIGO -> ChatColorsPalette.Bubbles.INDIGO
|
||||
ChatStyle.BubbleColorPreset.SOLID_VIOLET -> ChatColorsPalette.Bubbles.VIOLET
|
||||
ChatStyle.BubbleColorPreset.SOLID_PLUM -> ChatColorsPalette.Bubbles.PLUM
|
||||
ChatStyle.BubbleColorPreset.SOLID_TAUPE -> ChatColorsPalette.Bubbles.TAUPE
|
||||
ChatStyle.BubbleColorPreset.SOLID_STEEL -> ChatColorsPalette.Bubbles.STEEL
|
||||
// Gradients
|
||||
ChatStyle.BubbleColorPreset.GRADIENT_EMBER -> ChatColorsPalette.Bubbles.EMBER
|
||||
ChatStyle.BubbleColorPreset.GRADIENT_MIDNIGHT -> ChatColorsPalette.Bubbles.MIDNIGHT
|
||||
ChatStyle.BubbleColorPreset.GRADIENT_INFRARED -> ChatColorsPalette.Bubbles.INFRARED
|
||||
ChatStyle.BubbleColorPreset.GRADIENT_LAGOON -> ChatColorsPalette.Bubbles.LAGOON
|
||||
ChatStyle.BubbleColorPreset.GRADIENT_FLUORESCENT -> ChatColorsPalette.Bubbles.FLUORESCENT
|
||||
ChatStyle.BubbleColorPreset.GRADIENT_BASIL -> ChatColorsPalette.Bubbles.BASIL
|
||||
ChatStyle.BubbleColorPreset.GRADIENT_SUBLIME -> ChatColorsPalette.Bubbles.SUBLIME
|
||||
ChatStyle.BubbleColorPreset.GRADIENT_SEA -> ChatColorsPalette.Bubbles.SEA
|
||||
ChatStyle.BubbleColorPreset.GRADIENT_TANGERINE -> ChatColorsPalette.Bubbles.TANGERINE
|
||||
ChatStyle.BubbleColorPreset.UNKNOWN_BUBBLE_COLOR_PRESET, ChatStyle.BubbleColorPreset.SOLID_ULTRAMARINE -> ChatColorsPalette.Bubbles.ULTRAMARINE
|
||||
}
|
||||
}
|
||||
|
||||
if (this.autoBubbleColor != null) {
|
||||
return ChatColorsPalette.Bubbles.default.withId(ChatColors.Id.Auto)
|
||||
}
|
||||
|
||||
if (this.customColorId != null) {
|
||||
return importState.remoteToLocalColorId[this.customColorId]?.let { localId ->
|
||||
val colorId = ChatColors.Id.forLongValue(localId)
|
||||
ChatColorsPalette.Bubbles.default.withId(colorId)
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
fun ChatColors.toRemote(): ChatStyle.BubbleColorPreset? {
|
||||
when (this) {
|
||||
// Solids
|
||||
ChatColorsPalette.Bubbles.CRIMSON -> return ChatStyle.BubbleColorPreset.SOLID_CRIMSON
|
||||
ChatColorsPalette.Bubbles.VERMILION -> return ChatStyle.BubbleColorPreset.SOLID_VERMILION
|
||||
ChatColorsPalette.Bubbles.BURLAP -> return ChatStyle.BubbleColorPreset.SOLID_BURLAP
|
||||
ChatColorsPalette.Bubbles.FOREST -> return ChatStyle.BubbleColorPreset.SOLID_FOREST
|
||||
ChatColorsPalette.Bubbles.WINTERGREEN -> return ChatStyle.BubbleColorPreset.SOLID_WINTERGREEN
|
||||
ChatColorsPalette.Bubbles.TEAL -> return ChatStyle.BubbleColorPreset.SOLID_TEAL
|
||||
ChatColorsPalette.Bubbles.BLUE -> return ChatStyle.BubbleColorPreset.SOLID_BLUE
|
||||
ChatColorsPalette.Bubbles.INDIGO -> return ChatStyle.BubbleColorPreset.SOLID_INDIGO
|
||||
ChatColorsPalette.Bubbles.VIOLET -> return ChatStyle.BubbleColorPreset.SOLID_VIOLET
|
||||
ChatColorsPalette.Bubbles.PLUM -> return ChatStyle.BubbleColorPreset.SOLID_PLUM
|
||||
ChatColorsPalette.Bubbles.TAUPE -> return ChatStyle.BubbleColorPreset.SOLID_TAUPE
|
||||
ChatColorsPalette.Bubbles.STEEL -> return ChatStyle.BubbleColorPreset.SOLID_STEEL
|
||||
ChatColorsPalette.Bubbles.ULTRAMARINE -> return ChatStyle.BubbleColorPreset.SOLID_ULTRAMARINE
|
||||
// Gradients
|
||||
ChatColorsPalette.Bubbles.EMBER -> return ChatStyle.BubbleColorPreset.GRADIENT_EMBER
|
||||
ChatColorsPalette.Bubbles.MIDNIGHT -> return ChatStyle.BubbleColorPreset.GRADIENT_MIDNIGHT
|
||||
ChatColorsPalette.Bubbles.INFRARED -> return ChatStyle.BubbleColorPreset.GRADIENT_INFRARED
|
||||
ChatColorsPalette.Bubbles.LAGOON -> return ChatStyle.BubbleColorPreset.GRADIENT_LAGOON
|
||||
ChatColorsPalette.Bubbles.FLUORESCENT -> return ChatStyle.BubbleColorPreset.GRADIENT_FLUORESCENT
|
||||
ChatColorsPalette.Bubbles.BASIL -> return ChatStyle.BubbleColorPreset.GRADIENT_BASIL
|
||||
ChatColorsPalette.Bubbles.SUBLIME -> return ChatStyle.BubbleColorPreset.GRADIENT_SUBLIME
|
||||
ChatColorsPalette.Bubbles.SEA -> return ChatStyle.BubbleColorPreset.GRADIENT_SEA
|
||||
ChatColorsPalette.Bubbles.TANGERINE -> return ChatStyle.BubbleColorPreset.GRADIENT_TANGERINE
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
fun ChatStyle.WallpaperPreset.toLocal(): ChatWallpaper? {
|
||||
return when (this) {
|
||||
ChatStyle.WallpaperPreset.SOLID_BLUSH -> SingleColorChatWallpaper.BLUSH
|
||||
ChatStyle.WallpaperPreset.SOLID_COPPER -> SingleColorChatWallpaper.COPPER
|
||||
ChatStyle.WallpaperPreset.SOLID_DUST -> SingleColorChatWallpaper.DUST
|
||||
ChatStyle.WallpaperPreset.SOLID_CELADON -> SingleColorChatWallpaper.CELADON
|
||||
ChatStyle.WallpaperPreset.SOLID_RAINFOREST -> SingleColorChatWallpaper.RAINFOREST
|
||||
ChatStyle.WallpaperPreset.SOLID_PACIFIC -> SingleColorChatWallpaper.PACIFIC
|
||||
ChatStyle.WallpaperPreset.SOLID_FROST -> SingleColorChatWallpaper.FROST
|
||||
ChatStyle.WallpaperPreset.SOLID_NAVY -> SingleColorChatWallpaper.NAVY
|
||||
ChatStyle.WallpaperPreset.SOLID_LILAC -> SingleColorChatWallpaper.LILAC
|
||||
ChatStyle.WallpaperPreset.SOLID_PINK -> SingleColorChatWallpaper.PINK
|
||||
ChatStyle.WallpaperPreset.SOLID_EGGPLANT -> SingleColorChatWallpaper.EGGPLANT
|
||||
ChatStyle.WallpaperPreset.SOLID_SILVER -> SingleColorChatWallpaper.SILVER
|
||||
ChatStyle.WallpaperPreset.GRADIENT_SUNSET -> GradientChatWallpaper.SUNSET
|
||||
ChatStyle.WallpaperPreset.GRADIENT_NOIR -> GradientChatWallpaper.NOIR
|
||||
ChatStyle.WallpaperPreset.GRADIENT_HEATMAP -> GradientChatWallpaper.HEATMAP
|
||||
ChatStyle.WallpaperPreset.GRADIENT_AQUA -> GradientChatWallpaper.AQUA
|
||||
ChatStyle.WallpaperPreset.GRADIENT_IRIDESCENT -> GradientChatWallpaper.IRIDESCENT
|
||||
ChatStyle.WallpaperPreset.GRADIENT_MONSTERA -> GradientChatWallpaper.MONSTERA
|
||||
ChatStyle.WallpaperPreset.GRADIENT_BLISS -> GradientChatWallpaper.BLISS
|
||||
ChatStyle.WallpaperPreset.GRADIENT_SKY -> GradientChatWallpaper.SKY
|
||||
ChatStyle.WallpaperPreset.GRADIENT_PEACH -> GradientChatWallpaper.PEACH
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
private fun Int.toRemoteWallpaperPreset(): ChatStyle.WallpaperPreset {
|
||||
return when (this) {
|
||||
SingleColorChatWallpaper.BLUSH.color -> ChatStyle.WallpaperPreset.SOLID_BLUSH
|
||||
SingleColorChatWallpaper.COPPER.color -> ChatStyle.WallpaperPreset.SOLID_COPPER
|
||||
SingleColorChatWallpaper.DUST.color -> ChatStyle.WallpaperPreset.SOLID_DUST
|
||||
SingleColorChatWallpaper.CELADON.color -> ChatStyle.WallpaperPreset.SOLID_CELADON
|
||||
SingleColorChatWallpaper.RAINFOREST.color -> ChatStyle.WallpaperPreset.SOLID_RAINFOREST
|
||||
SingleColorChatWallpaper.PACIFIC.color -> ChatStyle.WallpaperPreset.SOLID_PACIFIC
|
||||
SingleColorChatWallpaper.FROST.color -> ChatStyle.WallpaperPreset.SOLID_FROST
|
||||
SingleColorChatWallpaper.NAVY.color -> ChatStyle.WallpaperPreset.SOLID_NAVY
|
||||
SingleColorChatWallpaper.LILAC.color -> ChatStyle.WallpaperPreset.SOLID_LILAC
|
||||
SingleColorChatWallpaper.PINK.color -> ChatStyle.WallpaperPreset.SOLID_PINK
|
||||
SingleColorChatWallpaper.EGGPLANT.color -> ChatStyle.WallpaperPreset.SOLID_EGGPLANT
|
||||
SingleColorChatWallpaper.SILVER.color -> ChatStyle.WallpaperPreset.SOLID_SILVER
|
||||
else -> ChatStyle.WallpaperPreset.UNKNOWN_WALLPAPER_PRESET
|
||||
}
|
||||
}
|
||||
|
||||
private fun Wallpaper.LinearGradient.toRemoteWallpaperPreset(): ChatStyle.WallpaperPreset {
|
||||
val colorArray = colors.toIntArray()
|
||||
return when {
|
||||
colorArray contentEquals GradientChatWallpaper.SUNSET.colors -> ChatStyle.WallpaperPreset.GRADIENT_SUNSET
|
||||
colorArray contentEquals GradientChatWallpaper.NOIR.colors -> ChatStyle.WallpaperPreset.GRADIENT_NOIR
|
||||
colorArray contentEquals GradientChatWallpaper.HEATMAP.colors -> ChatStyle.WallpaperPreset.GRADIENT_HEATMAP
|
||||
colorArray contentEquals GradientChatWallpaper.AQUA.colors -> ChatStyle.WallpaperPreset.GRADIENT_AQUA
|
||||
colorArray contentEquals GradientChatWallpaper.IRIDESCENT.colors -> ChatStyle.WallpaperPreset.GRADIENT_IRIDESCENT
|
||||
colorArray contentEquals GradientChatWallpaper.MONSTERA.colors -> ChatStyle.WallpaperPreset.GRADIENT_MONSTERA
|
||||
colorArray contentEquals GradientChatWallpaper.BLISS.colors -> ChatStyle.WallpaperPreset.GRADIENT_BLISS
|
||||
colorArray contentEquals GradientChatWallpaper.SKY.colors -> ChatStyle.WallpaperPreset.GRADIENT_SKY
|
||||
colorArray contentEquals GradientChatWallpaper.PEACH.colors -> ChatStyle.WallpaperPreset.GRADIENT_PEACH
|
||||
else -> ChatStyle.WallpaperPreset.UNKNOWN_WALLPAPER_PRESET
|
||||
}
|
||||
}
|
||||
|
||||
private fun Wallpaper.File.toFilePointer(readableDatabase: SQLiteDatabase): FilePointer? {
|
||||
val attachmentId: AttachmentId = UriUtil.parseOrNull(this.uri)?.let { PartUriParser(it).partId } ?: return null
|
||||
|
||||
val wallpaperAttachment: ArchiveAttachmentData? = readableDatabase
|
||||
.select(
|
||||
AttachmentTable.ARCHIVE_MEDIA_NAME,
|
||||
AttachmentTable.ARCHIVE_CDN,
|
||||
AttachmentTable.REMOTE_KEY,
|
||||
AttachmentTable.REMOTE_DIGEST,
|
||||
AttachmentTable.DATA_SIZE,
|
||||
AttachmentTable.CONTENT_TYPE,
|
||||
AttachmentTable.WIDTH,
|
||||
AttachmentTable.HEIGHT
|
||||
)
|
||||
.from(AttachmentTable.TABLE_NAME)
|
||||
.where("${AttachmentTable.ID} = ?", attachmentId.id)
|
||||
.run()
|
||||
.readToSingleObject { cursor -> cursor.toArchiveAttachmentData() }
|
||||
|
||||
return wallpaperAttachment?.let { attachment ->
|
||||
FilePointer(
|
||||
backupLocator = FilePointer.BackupLocator(
|
||||
mediaName = attachment.archiveMediaName ?: "",
|
||||
cdnNumber = attachment.archiveCdn,
|
||||
key = attachment.remoteKey?.toByteString() ?: ByteString.EMPTY,
|
||||
size = attachment.size.toInt(),
|
||||
digest = attachment.remoteDigest?.toByteString() ?: ByteString.EMPTY
|
||||
),
|
||||
contentType = attachment.contentType,
|
||||
width = attachment.width,
|
||||
height = attachment.height
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun Cursor.toArchiveAttachmentData(): ArchiveAttachmentData {
|
||||
return ArchiveAttachmentData(
|
||||
archiveMediaName = this.requireString(AttachmentTable.ARCHIVE_MEDIA_NAME),
|
||||
archiveCdn = this.requireInt(AttachmentTable.ARCHIVE_CDN),
|
||||
remoteKey = this.requireString(AttachmentTable.REMOTE_KEY)?.let { Base64.decodeOrNull(it) },
|
||||
remoteDigest = this.requireBlob(AttachmentTable.REMOTE_DIGEST),
|
||||
size = this.requireLong(AttachmentTable.DATA_SIZE),
|
||||
contentType = this.requireString(AttachmentTable.CONTENT_TYPE),
|
||||
width = this.requireInt(AttachmentTable.WIDTH),
|
||||
height = this.requireInt(AttachmentTable.HEIGHT)
|
||||
)
|
||||
}
|
||||
|
||||
private class ArchiveAttachmentData(
|
||||
val archiveMediaName: String?,
|
||||
val archiveCdn: Int,
|
||||
val remoteKey: ByteArray?,
|
||||
val remoteDigest: ByteArray?,
|
||||
val size: Long,
|
||||
val contentType: String?,
|
||||
val width: Int,
|
||||
val height: Int
|
||||
)
|
||||
@@ -1,112 +0,0 @@
|
||||
/*
|
||||
* Copyright 2024 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.securesms.backup.v2.util
|
||||
|
||||
import org.thoughtcrime.securesms.backup.v2.ImportState
|
||||
import org.thoughtcrime.securesms.backup.v2.proto.ChatStyle
|
||||
import org.thoughtcrime.securesms.conversation.colors.ChatColors
|
||||
import org.thoughtcrime.securesms.conversation.colors.ChatColorsPalette
|
||||
|
||||
// TODO [backup] Passing in chatColorId probably unnecessary. Only stored as separate column in recipient table for querying, I believe.
|
||||
object BackupConverters {
|
||||
fun constructRemoteChatStyle(chatColors: ChatColors?, chatColorId: ChatColors.Id): ChatStyle? {
|
||||
var chatStyleBuilder: ChatStyle.Builder? = null
|
||||
|
||||
if (chatColors != null) {
|
||||
chatStyleBuilder = ChatStyle.Builder()
|
||||
when (chatColorId) {
|
||||
ChatColors.Id.NotSet -> {}
|
||||
ChatColors.Id.Auto -> {
|
||||
chatStyleBuilder.autoBubbleColor = ChatStyle.AutomaticBubbleColor()
|
||||
}
|
||||
|
||||
ChatColors.Id.BuiltIn -> {
|
||||
chatStyleBuilder.bubbleColorPreset = chatColors.toRemote()
|
||||
}
|
||||
|
||||
is ChatColors.Id.Custom -> {
|
||||
chatStyleBuilder.customColorId = chatColorId.longValue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO [backup] wallpaper
|
||||
|
||||
return chatStyleBuilder?.build()
|
||||
}
|
||||
}
|
||||
|
||||
fun ChatStyle.toLocal(importState: ImportState): ChatColors? {
|
||||
if (this.bubbleColorPreset != null) {
|
||||
return when (this.bubbleColorPreset) {
|
||||
ChatStyle.BubbleColorPreset.SOLID_CRIMSON -> ChatColorsPalette.Bubbles.CRIMSON
|
||||
ChatStyle.BubbleColorPreset.SOLID_VERMILION -> ChatColorsPalette.Bubbles.VERMILION
|
||||
ChatStyle.BubbleColorPreset.SOLID_BURLAP -> ChatColorsPalette.Bubbles.BURLAP
|
||||
ChatStyle.BubbleColorPreset.SOLID_FOREST -> ChatColorsPalette.Bubbles.FOREST
|
||||
ChatStyle.BubbleColorPreset.SOLID_WINTERGREEN -> ChatColorsPalette.Bubbles.WINTERGREEN
|
||||
ChatStyle.BubbleColorPreset.SOLID_TEAL -> ChatColorsPalette.Bubbles.TEAL
|
||||
ChatStyle.BubbleColorPreset.SOLID_BLUE -> ChatColorsPalette.Bubbles.BLUE
|
||||
ChatStyle.BubbleColorPreset.SOLID_INDIGO -> ChatColorsPalette.Bubbles.INDIGO
|
||||
ChatStyle.BubbleColorPreset.SOLID_VIOLET -> ChatColorsPalette.Bubbles.VIOLET
|
||||
ChatStyle.BubbleColorPreset.SOLID_PLUM -> ChatColorsPalette.Bubbles.PLUM
|
||||
ChatStyle.BubbleColorPreset.SOLID_TAUPE -> ChatColorsPalette.Bubbles.TAUPE
|
||||
ChatStyle.BubbleColorPreset.SOLID_STEEL -> ChatColorsPalette.Bubbles.STEEL
|
||||
ChatStyle.BubbleColorPreset.GRADIENT_EMBER -> ChatColorsPalette.Bubbles.EMBER
|
||||
ChatStyle.BubbleColorPreset.GRADIENT_MIDNIGHT -> ChatColorsPalette.Bubbles.MIDNIGHT
|
||||
ChatStyle.BubbleColorPreset.GRADIENT_INFRARED -> ChatColorsPalette.Bubbles.INFRARED
|
||||
ChatStyle.BubbleColorPreset.GRADIENT_LAGOON -> ChatColorsPalette.Bubbles.LAGOON
|
||||
ChatStyle.BubbleColorPreset.GRADIENT_FLUORESCENT -> ChatColorsPalette.Bubbles.FLUORESCENT
|
||||
ChatStyle.BubbleColorPreset.GRADIENT_BASIL -> ChatColorsPalette.Bubbles.BASIL
|
||||
ChatStyle.BubbleColorPreset.GRADIENT_SUBLIME -> ChatColorsPalette.Bubbles.SUBLIME
|
||||
ChatStyle.BubbleColorPreset.GRADIENT_SEA -> ChatColorsPalette.Bubbles.SEA
|
||||
ChatStyle.BubbleColorPreset.GRADIENT_TANGERINE -> ChatColorsPalette.Bubbles.TANGERINE
|
||||
ChatStyle.BubbleColorPreset.UNKNOWN_BUBBLE_COLOR_PRESET, ChatStyle.BubbleColorPreset.SOLID_ULTRAMARINE -> ChatColorsPalette.Bubbles.ULTRAMARINE
|
||||
}
|
||||
}
|
||||
|
||||
if (this.autoBubbleColor != null) {
|
||||
return ChatColorsPalette.Bubbles.default.withId(ChatColors.Id.Auto)
|
||||
}
|
||||
|
||||
if (this.customColorId != null) {
|
||||
return importState.remoteToLocalColorId[this.customColorId]?.let { localId ->
|
||||
val colorId = ChatColors.Id.forLongValue(localId)
|
||||
ChatColorsPalette.Bubbles.default.withId(colorId)
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
fun ChatColors.toRemote(): ChatStyle.BubbleColorPreset? {
|
||||
when (this) {
|
||||
// Solids
|
||||
ChatColorsPalette.Bubbles.CRIMSON -> return ChatStyle.BubbleColorPreset.SOLID_CRIMSON
|
||||
ChatColorsPalette.Bubbles.VERMILION -> return ChatStyle.BubbleColorPreset.SOLID_VERMILION
|
||||
ChatColorsPalette.Bubbles.BURLAP -> return ChatStyle.BubbleColorPreset.SOLID_BURLAP
|
||||
ChatColorsPalette.Bubbles.FOREST -> return ChatStyle.BubbleColorPreset.SOLID_FOREST
|
||||
ChatColorsPalette.Bubbles.WINTERGREEN -> return ChatStyle.BubbleColorPreset.SOLID_WINTERGREEN
|
||||
ChatColorsPalette.Bubbles.TEAL -> return ChatStyle.BubbleColorPreset.SOLID_TEAL
|
||||
ChatColorsPalette.Bubbles.BLUE -> return ChatStyle.BubbleColorPreset.SOLID_BLUE
|
||||
ChatColorsPalette.Bubbles.INDIGO -> return ChatStyle.BubbleColorPreset.SOLID_INDIGO
|
||||
ChatColorsPalette.Bubbles.VIOLET -> return ChatStyle.BubbleColorPreset.SOLID_VIOLET
|
||||
ChatColorsPalette.Bubbles.PLUM -> return ChatStyle.BubbleColorPreset.SOLID_PLUM
|
||||
ChatColorsPalette.Bubbles.TAUPE -> return ChatStyle.BubbleColorPreset.SOLID_TAUPE
|
||||
ChatColorsPalette.Bubbles.STEEL -> return ChatStyle.BubbleColorPreset.SOLID_STEEL
|
||||
ChatColorsPalette.Bubbles.ULTRAMARINE -> return ChatStyle.BubbleColorPreset.SOLID_ULTRAMARINE
|
||||
// Gradients
|
||||
ChatColorsPalette.Bubbles.EMBER -> return ChatStyle.BubbleColorPreset.GRADIENT_EMBER
|
||||
ChatColorsPalette.Bubbles.MIDNIGHT -> return ChatStyle.BubbleColorPreset.GRADIENT_MIDNIGHT
|
||||
ChatColorsPalette.Bubbles.INFRARED -> return ChatStyle.BubbleColorPreset.GRADIENT_INFRARED
|
||||
ChatColorsPalette.Bubbles.LAGOON -> return ChatStyle.BubbleColorPreset.GRADIENT_LAGOON
|
||||
ChatColorsPalette.Bubbles.FLUORESCENT -> return ChatStyle.BubbleColorPreset.GRADIENT_FLUORESCENT
|
||||
ChatColorsPalette.Bubbles.BASIL -> return ChatStyle.BubbleColorPreset.GRADIENT_BASIL
|
||||
ChatColorsPalette.Bubbles.SUBLIME -> return ChatStyle.BubbleColorPreset.GRADIENT_SUBLIME
|
||||
ChatColorsPalette.Bubbles.SEA -> return ChatStyle.BubbleColorPreset.GRADIENT_SEA
|
||||
ChatColorsPalette.Bubbles.TANGERINE -> return ChatStyle.BubbleColorPreset.GRADIENT_TANGERINE
|
||||
}
|
||||
return null
|
||||
}
|
||||
@@ -33,6 +33,7 @@ class ChatColors(
|
||||
) : Parcelable {
|
||||
|
||||
fun isGradient(): Boolean = linearGradient != null
|
||||
fun isSolid(): Boolean = singleColor != null
|
||||
|
||||
/**
|
||||
* Returns the Drawable to render the linear gradient, or null if this ChatColors is a single color.
|
||||
|
||||
@@ -70,6 +70,7 @@ import org.thoughtcrime.securesms.attachments.Attachment
|
||||
import org.thoughtcrime.securesms.attachments.AttachmentId
|
||||
import org.thoughtcrime.securesms.attachments.Cdn
|
||||
import org.thoughtcrime.securesms.attachments.DatabaseAttachment
|
||||
import org.thoughtcrime.securesms.attachments.WallpaperAttachment
|
||||
import org.thoughtcrime.securesms.audio.AudioHash
|
||||
import org.thoughtcrime.securesms.blurhash.BlurHash
|
||||
import org.thoughtcrime.securesms.crypto.AttachmentSecret
|
||||
@@ -182,6 +183,7 @@ class AttachmentTable(
|
||||
const val TRANSFER_RESTORE_IN_PROGRESS = 6
|
||||
const val TRANSFER_RESTORE_OFFLOADED = 7
|
||||
const val PREUPLOAD_MESSAGE_ID: Long = -8675309
|
||||
const val WALLPAPER_MESSAGE_ID: Long = -8675308
|
||||
|
||||
private val PROJECTION = arrayOf(
|
||||
ID,
|
||||
@@ -816,7 +818,7 @@ class AttachmentTable(
|
||||
fun trimAllAbandonedAttachments() {
|
||||
val deleteCount = writableDatabase
|
||||
.delete(TABLE_NAME)
|
||||
.where("$MESSAGE_ID != $PREUPLOAD_MESSAGE_ID AND $MESSAGE_ID NOT IN (SELECT ${MessageTable.ID} FROM ${MessageTable.TABLE_NAME})")
|
||||
.where("$MESSAGE_ID != $PREUPLOAD_MESSAGE_ID AND $MESSAGE_ID != $WALLPAPER_MESSAGE_ID AND $MESSAGE_ID NOT IN (SELECT ${MessageTable.ID} FROM ${MessageTable.TABLE_NAME})")
|
||||
.run()
|
||||
|
||||
if (deleteCount > 0) {
|
||||
@@ -2184,6 +2186,16 @@ class AttachmentTable(
|
||||
throw MmsException(e)
|
||||
}
|
||||
|
||||
return insertAttachmentWithData(messageId, dataStream, attachment, quote)
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts an attachment with existing data. This is likely an outgoing attachment that we're in the process of sending.
|
||||
*
|
||||
* @param dataStream The stream to read the data from. This stream will be closed by this method.
|
||||
*/
|
||||
@Throws(MmsException::class)
|
||||
private fun insertAttachmentWithData(messageId: Long, dataStream: InputStream, attachment: Attachment, quote: Boolean): AttachmentId {
|
||||
// To avoid performing long-running operations in a transaction, we write the data to an independent file first in a way that doesn't rely on db state.
|
||||
val fileWriteResult: DataFileWriteResult = writeToDataFile(newDataFile(context), dataStream, attachment.transformProperties ?: TransformProperties.empty())
|
||||
Log.d(TAG, "[insertAttachmentWithData] Wrote data to file: ${fileWriteResult.file.absolutePath} (MessageId: $messageId, ${attachment.uri})")
|
||||
@@ -2212,12 +2224,16 @@ class AttachmentTable(
|
||||
}
|
||||
|
||||
if (hashMatch != null) {
|
||||
if (fileWriteResult.hash == hashMatch.hashStart) {
|
||||
Log.i(TAG, "[insertAttachmentWithData] Found that the new attachment hash matches the DATA_HASH_START of ${hashMatch.id}. Using all of it's fields. (MessageId: $messageId, ${attachment.uri})")
|
||||
} else if (fileWriteResult.hash == hashMatch.hashEnd) {
|
||||
Log.i(TAG, "[insertAttachmentWithData] Found that the new attachment hash matches the DATA_HASH_END of ${hashMatch.id}. Using all of it's fields. (MessageId: $messageId, ${attachment.uri})")
|
||||
} else {
|
||||
throw IllegalStateException("Should not be possible based on query.")
|
||||
when (fileWriteResult.hash) {
|
||||
hashMatch.hashStart -> {
|
||||
Log.i(TAG, "[insertAttachmentWithData] Found that the new attachment hash matches the DATA_HASH_START of ${hashMatch.id}. Using all of it's fields. (MessageId: $messageId, ${attachment.uri})")
|
||||
}
|
||||
hashMatch.hashEnd -> {
|
||||
Log.i(TAG, "[insertAttachmentWithData] Found that the new attachment hash matches the DATA_HASH_END of ${hashMatch.id}. Using all of it's fields. (MessageId: $messageId, ${attachment.uri})")
|
||||
}
|
||||
else -> {
|
||||
throw IllegalStateException("Should not be possible based on query.")
|
||||
}
|
||||
}
|
||||
|
||||
contentValues.put(DATA_FILE, hashMatch.file.absolutePath)
|
||||
@@ -2309,6 +2325,21 @@ class AttachmentTable(
|
||||
return attachmentId
|
||||
}
|
||||
|
||||
fun insertWallpaper(dataStream: InputStream): AttachmentId {
|
||||
return insertAttachmentWithData(WALLPAPER_MESSAGE_ID, dataStream, WallpaperAttachment(), quote = false).also { id ->
|
||||
createKeyIvIfNecessary(id)
|
||||
}
|
||||
}
|
||||
|
||||
fun getAllWallpapers(): List<AttachmentId> {
|
||||
return readableDatabase
|
||||
.select(ID)
|
||||
.from(TABLE_NAME)
|
||||
.where("$MESSAGE_ID = $WALLPAPER_MESSAGE_ID")
|
||||
.run()
|
||||
.readToList { AttachmentId(it.requireLong(ID)) }
|
||||
}
|
||||
|
||||
private fun getTransferFile(db: SQLiteDatabase, attachmentId: AttachmentId): File? {
|
||||
return db
|
||||
.select(TRANSFER_FILE)
|
||||
|
||||
@@ -98,6 +98,7 @@ import org.thoughtcrime.securesms.util.ProfileUtil
|
||||
import org.thoughtcrime.securesms.util.RemoteConfig
|
||||
import org.thoughtcrime.securesms.util.Util
|
||||
import org.thoughtcrime.securesms.wallpaper.ChatWallpaper
|
||||
import org.thoughtcrime.securesms.wallpaper.ChatWallpaperFactory
|
||||
import org.thoughtcrime.securesms.wallpaper.WallpaperStorage
|
||||
import org.whispersystems.signalservice.api.profiles.SignalServiceProfile
|
||||
import org.whispersystems.signalservice.api.push.ServiceId
|
||||
@@ -1968,7 +1969,7 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
|
||||
for (pair in idWithWallpaper) {
|
||||
AppDependencies.databaseObserver.notifyRecipientChanged(pair.first)
|
||||
if (pair.second != null) {
|
||||
WallpaperStorage.onWallpaperDeselected(context, Uri.parse(pair.second))
|
||||
WallpaperStorage.onWallpaperDeselected(Uri.parse(pair.second))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -1980,11 +1981,11 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
|
||||
}
|
||||
}
|
||||
|
||||
fun setWallpaper(id: RecipientId, chatWallpaper: ChatWallpaper?) {
|
||||
setWallpaper(id, chatWallpaper?.serialize())
|
||||
fun setWallpaper(id: RecipientId, chatWallpaper: ChatWallpaper?, notifyDeselected: Boolean) {
|
||||
setWallpaper(id, chatWallpaper?.serialize(), notifyDeselected)
|
||||
}
|
||||
|
||||
private fun setWallpaper(id: RecipientId, wallpaper: Wallpaper?) {
|
||||
private fun setWallpaper(id: RecipientId, wallpaper: Wallpaper?, notifyDeselected: Boolean) {
|
||||
val existingWallpaperUri = getWallpaperUri(id)
|
||||
val values = ContentValues().apply {
|
||||
put(WALLPAPER, wallpaper?.encode())
|
||||
@@ -1999,8 +2000,8 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
|
||||
AppDependencies.databaseObserver.notifyRecipientChanged(id)
|
||||
}
|
||||
|
||||
if (existingWallpaperUri != null) {
|
||||
WallpaperStorage.onWallpaperDeselected(context, existingWallpaperUri)
|
||||
if (notifyDeselected && existingWallpaperUri != null) {
|
||||
WallpaperStorage.onWallpaperDeselected(existingWallpaperUri)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2010,7 +2011,7 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
|
||||
.dimLevelInDarkTheme(if (enabled) ChatWallpaper.FIXED_DIM_LEVEL_FOR_DARK_THEME else 0f)
|
||||
.build()
|
||||
|
||||
setWallpaper(id, updated)
|
||||
setWallpaper(id, updated, false)
|
||||
}
|
||||
|
||||
private fun getWallpaper(id: RecipientId): Wallpaper? {
|
||||
@@ -2055,6 +2056,23 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
|
||||
return 0
|
||||
}
|
||||
|
||||
/**
|
||||
* Migrates all recipients using [legacyUri] for their wallpaper to [newUri].
|
||||
* Needed for an app migration.
|
||||
*/
|
||||
fun migrateWallpaperUri(legacyUri: Uri, newUri: Uri): Int {
|
||||
val newWallpaper = ChatWallpaperFactory.create(newUri)
|
||||
|
||||
return writableDatabase
|
||||
.update(TABLE_NAME)
|
||||
.values(
|
||||
WALLPAPER to newWallpaper.serialize().encode(),
|
||||
WALLPAPER_URI to newUri.toString()
|
||||
)
|
||||
.where("$WALLPAPER_URI = ?", legacyUri)
|
||||
.run()
|
||||
}
|
||||
|
||||
fun getPhoneNumberDiscoverability(id: RecipientId): PhoneNumberDiscoverableState? {
|
||||
return readableDatabase
|
||||
.select(PHONE_NUMBER_DISCOVERABLE)
|
||||
|
||||
@@ -66,9 +66,11 @@ class BackupRestoreMediaJob private constructor(parameters: Parameters) : BaseJo
|
||||
val messageMap = SignalDatabase.messages.getMessages(messageIds).associate { it.id to (it as MmsMessageRecord) }
|
||||
|
||||
for (attachment in attachmentBatch) {
|
||||
val isWallpaper = attachment.mmsId == AttachmentTable.WALLPAPER_MESSAGE_ID
|
||||
|
||||
val message = messageMap[attachment.mmsId]
|
||||
if (message == null) {
|
||||
Log.w(TAG, "Unable to find message for ${attachment.attachmentId}")
|
||||
if (message == null && !isWallpaper) {
|
||||
Log.w(TAG, "Unable to find message for ${attachment.attachmentId}, mmsId: ${attachment.mmsId}")
|
||||
notRestorable += attachment
|
||||
continue
|
||||
}
|
||||
@@ -79,7 +81,7 @@ class BackupRestoreMediaJob private constructor(parameters: Parameters) : BaseJo
|
||||
highPriority = false
|
||||
)
|
||||
|
||||
if (shouldRestoreFullSize(message, restoreTime, SignalStore.backup.optimizeStorage)) {
|
||||
if (isWallpaper || shouldRestoreFullSize(message!!, restoreTime, SignalStore.backup.optimizeStorage)) {
|
||||
restoreFullAttachmentJobs += attachment to RestoreAttachmentJob(
|
||||
messageId = attachment.mmsId,
|
||||
attachmentId = attachment.attachmentId
|
||||
|
||||
@@ -94,6 +94,7 @@ import org.thoughtcrime.securesms.migrations.TrimByLengthSettingsMigrationJob;
|
||||
import org.thoughtcrime.securesms.migrations.UpdateSmsJobsMigrationJob;
|
||||
import org.thoughtcrime.securesms.migrations.UserNotificationMigrationJob;
|
||||
import org.thoughtcrime.securesms.migrations.UuidMigrationJob;
|
||||
import org.thoughtcrime.securesms.migrations.WallpaperStorageMigrationJob;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
@@ -309,6 +310,7 @@ public final class JobManagerFactories {
|
||||
put(UpdateSmsJobsMigrationJob.KEY, new UpdateSmsJobsMigrationJob.Factory());
|
||||
put(UserNotificationMigrationJob.KEY, new UserNotificationMigrationJob.Factory());
|
||||
put(UuidMigrationJob.KEY, new UuidMigrationJob.Factory());
|
||||
put(WallpaperStorageMigrationJob.KEY, new WallpaperStorageMigrationJob.Factory());
|
||||
|
||||
// Dead jobs
|
||||
put(FailingJob.KEY, new FailingJob.Factory());
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package org.thoughtcrime.securesms.keyvalue;
|
||||
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
@@ -35,8 +34,8 @@ public final class WallpaperValues extends SignalStoreValues {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
public void setWallpaper(@NonNull Context context, @Nullable ChatWallpaper wallpaper) {
|
||||
Wallpaper currentWallpaper = getCurrentWallpaper();
|
||||
public void setWallpaper(@Nullable ChatWallpaper wallpaper) {
|
||||
Wallpaper currentWallpaper = getCurrentRawWallpaper();
|
||||
Uri currentUri = null;
|
||||
|
||||
if (currentWallpaper != null && currentWallpaper.file_ != null) {
|
||||
@@ -50,12 +49,12 @@ public final class WallpaperValues extends SignalStoreValues {
|
||||
}
|
||||
|
||||
if (currentUri != null) {
|
||||
WallpaperStorage.onWallpaperDeselected(context, currentUri);
|
||||
WallpaperStorage.onWallpaperDeselected(currentUri);
|
||||
}
|
||||
}
|
||||
|
||||
public @Nullable ChatWallpaper getWallpaper() {
|
||||
Wallpaper currentWallpaper = getCurrentWallpaper();
|
||||
Wallpaper currentWallpaper = getCurrentRawWallpaper();
|
||||
|
||||
if (currentWallpaper != null) {
|
||||
return ChatWallpaperFactory.create(currentWallpaper);
|
||||
@@ -69,7 +68,7 @@ public final class WallpaperValues extends SignalStoreValues {
|
||||
}
|
||||
|
||||
public void setDimInDarkTheme(boolean enabled) {
|
||||
Wallpaper currentWallpaper = getCurrentWallpaper();
|
||||
Wallpaper currentWallpaper = getCurrentRawWallpaper();
|
||||
|
||||
if (currentWallpaper != null) {
|
||||
putBlob(KEY_WALLPAPER,
|
||||
@@ -88,7 +87,7 @@ public final class WallpaperValues extends SignalStoreValues {
|
||||
* wallpaper is both set *and* it's an image.
|
||||
*/
|
||||
public @Nullable Uri getWallpaperUri() {
|
||||
Wallpaper currentWallpaper = getCurrentWallpaper();
|
||||
Wallpaper currentWallpaper = getCurrentRawWallpaper();
|
||||
|
||||
if (currentWallpaper != null && currentWallpaper.file_ != null) {
|
||||
return Uri.parse(currentWallpaper.file_.uri);
|
||||
@@ -97,7 +96,10 @@ public final class WallpaperValues extends SignalStoreValues {
|
||||
}
|
||||
}
|
||||
|
||||
private @Nullable Wallpaper getCurrentWallpaper() {
|
||||
/**
|
||||
* Allows for retrieval of the raw, serialized wallpaper proto. Clients should prefer {@link #getWallpaper()} instead.
|
||||
*/
|
||||
public @Nullable Wallpaper getCurrentRawWallpaper() {
|
||||
byte[] serialized = getBlob(KEY_WALLPAPER, null);
|
||||
|
||||
if (serialized != null) {
|
||||
@@ -111,4 +113,12 @@ public final class WallpaperValues extends SignalStoreValues {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* For a migration, we need to update the current wallpaper _without_ triggering the onDeselectedEvents and such.
|
||||
* For normal usage, use {@link #setWallpaper(ChatWallpaper)}
|
||||
*/
|
||||
public void setRawWallpaperForMigration(@NonNull Wallpaper wallpaper) {
|
||||
putBlob(KEY_WALLPAPER, wallpaper.encode());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -156,9 +156,10 @@ public class ApplicationMigrations {
|
||||
// static final int BACKFILL_DIGESTS = 112;
|
||||
static final int BACKFILL_DIGESTS_V2 = 113;
|
||||
static final int CALL_LINK_STORAGE_SYNC = 114;
|
||||
static final int WALLPAPER_MIGRATION = 115;
|
||||
}
|
||||
|
||||
public static final int CURRENT_VERSION = 114;
|
||||
public static final int CURRENT_VERSION = 115;
|
||||
|
||||
/**
|
||||
* This *must* be called after the {@link JobManager} has been instantiated, but *before* the call
|
||||
@@ -713,6 +714,10 @@ public class ApplicationMigrations {
|
||||
jobs.put(Version.CALL_LINK_STORAGE_SYNC, new SyncCallLinksMigrationJob());
|
||||
}
|
||||
|
||||
if (lastSeenVersion < Version.WALLPAPER_MIGRATION) {
|
||||
jobs.put(Version.WALLPAPER_MIGRATION, new WallpaperStorageMigrationJob());
|
||||
}
|
||||
|
||||
return jobs;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
* Copyright 2024 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.securesms.migrations
|
||||
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.thoughtcrime.securesms.BuildConfig
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase
|
||||
import org.thoughtcrime.securesms.database.model.databaseprotos.Wallpaper
|
||||
import org.thoughtcrime.securesms.jobmanager.Job
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
import org.thoughtcrime.securesms.mms.PartAuthority
|
||||
import org.thoughtcrime.securesms.util.storage.FileStorage
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
|
||||
/**
|
||||
* We need to move the wallpapers to be stored in the attachment table as part of backups V2.
|
||||
*/
|
||||
internal class WallpaperStorageMigrationJob(parameters: Parameters = Parameters.Builder().build()) : MigrationJob(parameters) {
|
||||
companion object {
|
||||
private val TAG = Log.tag(WallpaperStorageMigrationJob::class.java)
|
||||
const val KEY = "WallpaperStorageMigrationJob"
|
||||
|
||||
private const val DIRECTORY = "wallpapers"
|
||||
private const val FILENAME_BASE = "wallpaper"
|
||||
|
||||
private val CONTENT_URI = Uri.parse("content://${BuildConfig.APPLICATION_ID}/wallpaper")
|
||||
}
|
||||
|
||||
override fun getFactoryKey(): String = KEY
|
||||
|
||||
override fun isUiBlocking(): Boolean = true
|
||||
|
||||
override fun performMigration() {
|
||||
val wallpaperFileNames = FileStorage.getAll(context, DIRECTORY, FILENAME_BASE)
|
||||
|
||||
if (wallpaperFileNames.isEmpty()) {
|
||||
Log.i(TAG, "No wallpapers to migrate. Done.")
|
||||
return
|
||||
}
|
||||
|
||||
Log.i(TAG, "There are ${wallpaperFileNames.size} wallpapers to migrate.")
|
||||
|
||||
val currentDefaultWallpaperUri = SignalStore.wallpaper.currentRawWallpaper?.file_?.uri
|
||||
|
||||
for (filename in wallpaperFileNames) {
|
||||
val inputStream = FileStorage.read(context, DIRECTORY, filename)
|
||||
val wallpaperAttachmentId = SignalDatabase.attachments.insertWallpaper(inputStream)
|
||||
|
||||
val directory = context.getDir(DIRECTORY, Context.MODE_PRIVATE)
|
||||
val file = File(directory, filename)
|
||||
|
||||
val legacyUri = Uri.withAppendedPath(CONTENT_URI, filename)
|
||||
val newUri = PartAuthority.getAttachmentDataUri(wallpaperAttachmentId)
|
||||
|
||||
val updatedUserCount = SignalDatabase.recipients.migrateWallpaperUri(
|
||||
legacyUri = legacyUri,
|
||||
newUri = newUri
|
||||
)
|
||||
Log.d(TAG, "Wallpaper with name '$filename' was in use by $updatedUserCount recipients.")
|
||||
|
||||
if (currentDefaultWallpaperUri == legacyUri.toString()) {
|
||||
Log.d(TAG, "Wallpaper with name '$filename' was set as the default wallpaper. Updating.")
|
||||
SignalStore.wallpaper.setRawWallpaperForMigration(Wallpaper(file_ = Wallpaper.File(uri = newUri.toString())))
|
||||
}
|
||||
|
||||
val deleted = file.delete()
|
||||
if (!deleted) {
|
||||
Log.w(TAG, "Failed to delete wallpaper file: $file")
|
||||
}
|
||||
}
|
||||
|
||||
Log.i(TAG, "Successfully migrated ${wallpaperFileNames.size} wallpapers.")
|
||||
}
|
||||
|
||||
override fun shouldRetry(e: Exception): Boolean = e is IOException
|
||||
|
||||
class Factory : Job.Factory<WallpaperStorageMigrationJob> {
|
||||
override fun create(parameters: Parameters, serializedData: ByteArray?): WallpaperStorageMigrationJob {
|
||||
return WallpaperStorageMigrationJob(parameters)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -34,13 +34,11 @@ public class PartAuthority {
|
||||
private static final String PART_URI_STRING = "content://" + AUTHORITY + "/part";
|
||||
private static final String PART_THUMBNAIL_STRING = "content://" + AUTHORITY + "/thumbnail";
|
||||
private static final String STICKER_URI_STRING = "content://" + AUTHORITY + "/sticker";
|
||||
private static final String WALLPAPER_URI_STRING = "content://" + AUTHORITY + "/wallpaper";
|
||||
private static final String EMOJI_URI_STRING = "content://" + AUTHORITY + "/emoji";
|
||||
private static final String AVATAR_PICKER_URI_STRING = "content://" + AUTHORITY + "/avatar_picker";
|
||||
private static final Uri PART_CONTENT_URI = Uri.parse(PART_URI_STRING);
|
||||
private static final Uri PART_THUMBNAIL_URI = Uri.parse(PART_THUMBNAIL_STRING);
|
||||
private static final Uri STICKER_CONTENT_URI = Uri.parse(STICKER_URI_STRING);
|
||||
private static final Uri WALLPAPER_CONTENT_URI = Uri.parse(WALLPAPER_URI_STRING);
|
||||
private static final Uri EMOJI_CONTENT_URI = Uri.parse(EMOJI_URI_STRING);
|
||||
private static final Uri AVATAR_PICKER_CONTENT_URI = Uri.parse(AVATAR_PICKER_URI_STRING);
|
||||
|
||||
@@ -84,7 +82,6 @@ public class PartAuthority {
|
||||
case STICKER_ROW: return SignalDatabase.stickers().getStickerStream(ContentUris.parseId(uri));
|
||||
case PERSISTENT_ROW: return DeprecatedPersistentBlobProvider.getInstance(context).getStream(context, ContentUris.parseId(uri));
|
||||
case BLOB_ROW: return BlobProvider.getInstance().getStream(context, uri);
|
||||
case WALLPAPER_ROW: return WallpaperStorage.read(context, getWallpaperFilename(uri));
|
||||
case EMOJI_ROW: return EmojiFiles.openForReading(context, getEmojiFilename(uri));
|
||||
case AVATAR_PICKER_ROW: return AvatarPickerStorage.read(context, getAvatarPickerFilename(uri));
|
||||
case THUMBNAIL_ROW: return SignalDatabase.attachments().getAttachmentThumbnailStream(new PartUriParser(uri).getPartId(), 0);
|
||||
@@ -190,10 +187,6 @@ public class PartAuthority {
|
||||
return ContentUris.withAppendedId(STICKER_CONTENT_URI, id);
|
||||
}
|
||||
|
||||
public static Uri getWallpaperUri(String filename) {
|
||||
return Uri.withAppendedPath(WALLPAPER_CONTENT_URI, filename);
|
||||
}
|
||||
|
||||
public static Uri getAvatarPickerUri(String filename) {
|
||||
return Uri.withAppendedPath(AVATAR_PICKER_CONTENT_URI, filename);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* Copyright 2024 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.securesms.util
|
||||
|
||||
import com.google.protobuf.InvalidProtocolBufferException
|
||||
import com.squareup.wire.ProtoAdapter
|
||||
|
||||
/**
|
||||
* Performs the common pattern of attempting to decode a serialized proto and returning null if it fails to decode.
|
||||
*/
|
||||
fun <E> ProtoAdapter<E>.decodeOrNull(serialized: ByteArray): E? {
|
||||
return try {
|
||||
this.decode(serialized)
|
||||
} catch (e: InvalidProtocolBufferException) {
|
||||
null
|
||||
}
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
package org.thoughtcrime.securesms.util;
|
||||
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
public final class UriUtil {
|
||||
|
||||
/**
|
||||
* Ensures that an external URI is valid and doesn't contain any references to internal files or
|
||||
* any other trickiness.
|
||||
*/
|
||||
public static boolean isValidExternalUri(@NonNull Context context, @NonNull Uri uri) {
|
||||
if (ContentResolver.SCHEME_FILE.equals(uri.getScheme())) {
|
||||
try {
|
||||
File file = new File(uri.getPath());
|
||||
|
||||
return file.getCanonicalPath().equals(file.getPath()) &&
|
||||
!file.getCanonicalPath().startsWith("/data") &&
|
||||
!file.getCanonicalPath().contains(context.getPackageName());
|
||||
} catch (IOException e) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
47
app/src/main/java/org/thoughtcrime/securesms/util/UriUtil.kt
Normal file
47
app/src/main/java/org/thoughtcrime/securesms/util/UriUtil.kt
Normal file
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Copyright 2024 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.securesms.util
|
||||
|
||||
import android.content.ContentResolver
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
|
||||
object UriUtil {
|
||||
|
||||
/**
|
||||
* Ensures that an external URI is valid and doesn't contain any references to internal files or
|
||||
* any other trickiness.
|
||||
*/
|
||||
@JvmStatic
|
||||
fun isValidExternalUri(context: Context, uri: Uri): Boolean {
|
||||
if (ContentResolver.SCHEME_FILE == uri.scheme) {
|
||||
try {
|
||||
val file = File(uri.path)
|
||||
|
||||
return file.canonicalPath == file.path &&
|
||||
!file.canonicalPath.startsWith("/data") &&
|
||||
!file.canonicalPath.contains(context.packageName)
|
||||
} catch (e: IOException) {
|
||||
return false
|
||||
}
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a string to a URI if it's valid, otherwise null.
|
||||
*/
|
||||
fun parseOrNull(uri: String): Uri? {
|
||||
return try {
|
||||
Uri.parse(uri)
|
||||
} catch (e: Exception) {
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,6 @@ import org.signal.core.util.concurrent.SignalExecutors;
|
||||
import org.thoughtcrime.securesms.conversation.colors.ChatColors;
|
||||
import org.thoughtcrime.securesms.conversation.colors.ChatColorsPalette;
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase;
|
||||
import org.thoughtcrime.securesms.dependencies.AppDependencies;
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||
@@ -50,7 +49,7 @@ class ChatWallpaperRepository {
|
||||
EXECUTOR.execute(() -> {
|
||||
List<ChatWallpaper> wallpapers = new ArrayList<>(ChatWallpaper.BuiltIns.INSTANCE.getAllBuiltIns());
|
||||
|
||||
wallpapers.addAll(WallpaperStorage.getAll(AppDependencies.getApplication()));
|
||||
wallpapers.addAll(WallpaperStorage.getAll());
|
||||
consumer.accept(wallpapers);
|
||||
});
|
||||
}
|
||||
@@ -59,17 +58,17 @@ class ChatWallpaperRepository {
|
||||
if (recipientId != null) {
|
||||
//noinspection CodeBlock2Expr
|
||||
EXECUTOR.execute(() -> {
|
||||
SignalDatabase.recipients().setWallpaper(recipientId, chatWallpaper);
|
||||
SignalDatabase.recipients().setWallpaper(recipientId, chatWallpaper, true);
|
||||
onWallpaperSaved.run();
|
||||
});
|
||||
} else {
|
||||
SignalStore.wallpaper().setWallpaper(AppDependencies.getApplication(), chatWallpaper);
|
||||
SignalStore.wallpaper().setWallpaper(chatWallpaper);
|
||||
onWallpaperSaved.run();
|
||||
}
|
||||
}
|
||||
|
||||
void resetAllWallpaper(@NonNull Runnable onWallpaperReset) {
|
||||
SignalStore.wallpaper().setWallpaper(AppDependencies.getApplication(), null);
|
||||
SignalStore.wallpaper().setWallpaper(null);
|
||||
EXECUTOR.execute(() -> {
|
||||
SignalDatabase.recipients().resetAllWallpaper();
|
||||
onWallpaperReset.run();
|
||||
@@ -95,7 +94,8 @@ class ChatWallpaperRepository {
|
||||
.setWallpaper(recipientId,
|
||||
ChatWallpaperFactory.updateWithDimming(recipient.getWallpaper(),
|
||||
dimInDarkTheme ? ChatWallpaper.FIXED_DIM_LEVEL_FOR_DARK_THEME
|
||||
: 0f));
|
||||
: 0f),
|
||||
false);
|
||||
} else {
|
||||
throw new IllegalStateException("Unexpected call to setDimInDarkTheme, no wallpaper has been set on the given recipient or globally.");
|
||||
}
|
||||
|
||||
@@ -17,42 +17,42 @@ import java.util.Objects;
|
||||
|
||||
public final class GradientChatWallpaper implements ChatWallpaper, Parcelable {
|
||||
|
||||
public static final ChatWallpaper SUNSET = new GradientChatWallpaper(168f,
|
||||
new int[] { 0xFFF3DC47, 0xFFF3DA47, 0xFFF2D546, 0xFFF2CC46, 0xFFF1C146, 0xFFEFB445, 0xFFEEA544, 0xFFEC9644, 0xFFEB8743, 0xFFE97743, 0xFFE86942, 0xFFE65C41, 0xFFE55041, 0xFFE54841, 0xFFE44240, 0xFFE44040 },
|
||||
new float[] { 0.0f, 0.0807f, 0.1554f, 0.225f, 0.2904f, 0.3526f, 0.4125f, 0.471f, 0.529f, 0.5875f, 0.6474f, 0.7096f, 0.775f, 0.8446f, 0.9193f, 1f },
|
||||
0f);
|
||||
public static final ChatWallpaper NOIR = new GradientChatWallpaper(180f,
|
||||
new int[] { 0xFF16161D, 0xFF17171E, 0xFF1A1A22, 0xFF1F1F28, 0xFF26262F, 0xFF2D2D38, 0xFF353542, 0xFF3E3E4C, 0xFF474757, 0xFF4F4F61, 0xFF57576B, 0xFF5F5F74, 0xFF65657C, 0xFF6A6A82, 0xFF6D6D85, 0xFF6E6E87 },
|
||||
new float[] { 0.0000f, 0.0807f, 0.1554f, 0.2250f, 0.2904f, 0.3526f, 0.4125f, 0.4710f, 0.5290f, 0.5875f, 0.6474f, 0.7096f, 0.7750f, 0.8446f, 0.9193f, 1.0000f },
|
||||
0f);
|
||||
public static final ChatWallpaper HEATMAP = new GradientChatWallpaper(192f,
|
||||
new int[] { 0xFFF53844, 0xFFF33845, 0xFFEC3848, 0xFFE2384C, 0xFFD63851, 0xFFC73857, 0xFFB6385E, 0xFFA43866, 0xFF93376D, 0xFF813775, 0xFF70377C, 0xFF613782, 0xFF553787, 0xFF4B378B, 0xFF44378E, 0xFF42378F },
|
||||
new float[] { 0.0000f, 0.0075f, 0.0292f, 0.0637f, 0.1097f, 0.1659f, 0.2310f, 0.3037f, 0.3827f, 0.4666f, 0.5541f, 0.6439f, 0.7347f, 0.8252f, 0.9141f, 1.0000f },
|
||||
0f);
|
||||
public static final ChatWallpaper AQUA = new GradientChatWallpaper(180f,
|
||||
new int[] { 0xFF0093E9, 0xFF0294E9, 0xFF0696E7, 0xFF0D99E5, 0xFF169EE3, 0xFF21A3E0, 0xFF2DA8DD, 0xFF3AAEDA, 0xFF46B5D6, 0xFF53BBD3, 0xFF5FC0D0, 0xFF6AC5CD, 0xFF73CACB, 0xFF7ACDC9, 0xFF7ECFC7, 0xFF80D0C7 },
|
||||
new float[] { 0.0000f, 0.0807f, 0.1554f, 0.2250f, 0.2904f, 0.3526f, 0.4125f, 0.4710f, 0.5290f, 0.5875f, 0.6474f, 0.7096f, 0.7750f, 0.8446f, 0.9193f, 1.0000f },
|
||||
0f);
|
||||
public static final ChatWallpaper IRIDESCENT = new GradientChatWallpaper(192f,
|
||||
new int[] { 0xFFF04CE6, 0xFFEE4BE6, 0xFFE54AE5, 0xFFD949E5, 0xFFC946E4, 0xFFB644E3, 0xFFA141E3, 0xFF8B3FE2, 0xFF743CE1, 0xFF5E39E0, 0xFF4936DF, 0xFF3634DE, 0xFF2632DD, 0xFF1930DD, 0xFF112FDD, 0xFF0E2FDD },
|
||||
new float[] { 0.0000f, 0.0807f, 0.1554f, 0.2250f, 0.2904f, 0.3526f, 0.4125f, 0.4710f, 0.5290f, 0.5875f, 0.6474f, 0.7096f, 0.7750f, 0.8446f, 0.9193f, 1.0000f },
|
||||
0f);
|
||||
public static final ChatWallpaper MONSTERA = new GradientChatWallpaper(180f,
|
||||
new int[] { 0xFF65CDAC, 0xFF64CDAB, 0xFF60CBA8, 0xFF5BC8A3, 0xFF55C49D, 0xFF4DC096, 0xFF45BB8F, 0xFF3CB687, 0xFF33B17F, 0xFF2AAC76, 0xFF21A76F, 0xFF1AA268, 0xFF139F62, 0xFF0E9C5E, 0xFF0B9A5B, 0xFF0A995A },
|
||||
new float[] { 0.0000f, 0.0807f, 0.1554f, 0.2250f, 0.2904f, 0.3526f, 0.4125f, 0.4710f, 0.5290f, 0.5875f, 0.6474f, 0.7096f, 0.7750f, 0.8446f, 0.9193f, 1.0000f },
|
||||
0f);
|
||||
public static final ChatWallpaper BLISS = new GradientChatWallpaper(180f,
|
||||
new int[] { 0xFFD8E1FA, 0xFFD8E0F9, 0xFFD8DEF7, 0xFFD8DBF3, 0xFFD8D6EE, 0xFFD7D1E8, 0xFFD7CCE2, 0xFFD7C6DB, 0xFFD7BFD4, 0xFFD7B9CD, 0xFFD6B4C7, 0xFFD6AFC1, 0xFFD6AABC, 0xFFD6A7B8, 0xFFD6A5B6, 0xFFD6A4B5 },
|
||||
new float[] { 0.0000f, 0.0807f, 0.1554f, 0.2250f, 0.2904f, 0.3526f, 0.4125f, 0.4710f, 0.5290f, 0.5875f, 0.6474f, 0.7096f, 0.7750f, 0.8446f, 0.9193f, 1.0000f },
|
||||
0f);
|
||||
public static final ChatWallpaper SKY = new GradientChatWallpaper(180f,
|
||||
new int[] { 0xFFD8EBFD, 0xFFD7EAFD, 0xFFD5E9FD, 0xFFD2E7FD, 0xFFCDE5FD, 0xFFC8E3FD, 0xFFC3E0FD, 0xFFBDDDFC, 0xFFB7DAFC, 0xFFB2D7FC, 0xFFACD4FC, 0xFFA7D1FC, 0xFFA3CFFB, 0xFFA0CDFB, 0xFF9ECCFB, 0xFF9DCCFB },
|
||||
new float[] { 0.0000f, 0.0807f, 0.1554f, 0.2250f, 0.2904f, 0.3526f, 0.4125f, 0.4710f, 0.5290f, 0.5875f, 0.6474f, 0.7096f, 0.7750f, 0.8446f, 0.9193f, 1.0000f },
|
||||
0f);
|
||||
public static final ChatWallpaper PEACH = new GradientChatWallpaper(192f,
|
||||
new int[] { 0xFFFFE5C2, 0xFFFFE4C1, 0xFFFFE2BF, 0xFFFFDFBD, 0xFFFEDBB9, 0xFFFED6B5, 0xFFFED1B1, 0xFFFDCCAC, 0xFFFDC6A8, 0xFFFDC0A3, 0xFFFCBB9F, 0xFFFCB69B, 0xFFFCB297, 0xFFFCAF95, 0xFFFCAD93, 0xFFFCAC92 },
|
||||
new float[] { 0.0000f, 0.0807f, 0.1554f, 0.2250f, 0.2904f, 0.3526f, 0.4125f, 0.4710f, 0.5290f, 0.5875f, 0.6474f, 0.7096f, 0.7750f, 0.8446f, 0.9193f, 1.0000f },
|
||||
0f);
|
||||
public static final GradientChatWallpaper SUNSET = new GradientChatWallpaper(168f,
|
||||
new int[] { 0xFFF3DC47, 0xFFF3DA47, 0xFFF2D546, 0xFFF2CC46, 0xFFF1C146, 0xFFEFB445, 0xFFEEA544, 0xFFEC9644, 0xFFEB8743, 0xFFE97743, 0xFFE86942, 0xFFE65C41, 0xFFE55041, 0xFFE54841, 0xFFE44240, 0xFFE44040 },
|
||||
new float[] { 0.0f, 0.0807f, 0.1554f, 0.225f, 0.2904f, 0.3526f, 0.4125f, 0.471f, 0.529f, 0.5875f, 0.6474f, 0.7096f, 0.775f, 0.8446f, 0.9193f, 1f },
|
||||
0f);
|
||||
public static final GradientChatWallpaper NOIR = new GradientChatWallpaper(180f,
|
||||
new int[] { 0xFF16161D, 0xFF17171E, 0xFF1A1A22, 0xFF1F1F28, 0xFF26262F, 0xFF2D2D38, 0xFF353542, 0xFF3E3E4C, 0xFF474757, 0xFF4F4F61, 0xFF57576B, 0xFF5F5F74, 0xFF65657C, 0xFF6A6A82, 0xFF6D6D85, 0xFF6E6E87 },
|
||||
new float[] { 0.0000f, 0.0807f, 0.1554f, 0.2250f, 0.2904f, 0.3526f, 0.4125f, 0.4710f, 0.5290f, 0.5875f, 0.6474f, 0.7096f, 0.7750f, 0.8446f, 0.9193f, 1.0000f },
|
||||
0f);
|
||||
public static final GradientChatWallpaper HEATMAP = new GradientChatWallpaper(192f,
|
||||
new int[] { 0xFFF53844, 0xFFF33845, 0xFFEC3848, 0xFFE2384C, 0xFFD63851, 0xFFC73857, 0xFFB6385E, 0xFFA43866, 0xFF93376D, 0xFF813775, 0xFF70377C, 0xFF613782, 0xFF553787, 0xFF4B378B, 0xFF44378E, 0xFF42378F },
|
||||
new float[] { 0.0000f, 0.0075f, 0.0292f, 0.0637f, 0.1097f, 0.1659f, 0.2310f, 0.3037f, 0.3827f, 0.4666f, 0.5541f, 0.6439f, 0.7347f, 0.8252f, 0.9141f, 1.0000f },
|
||||
0f);
|
||||
public static final GradientChatWallpaper AQUA = new GradientChatWallpaper(180f,
|
||||
new int[] { 0xFF0093E9, 0xFF0294E9, 0xFF0696E7, 0xFF0D99E5, 0xFF169EE3, 0xFF21A3E0, 0xFF2DA8DD, 0xFF3AAEDA, 0xFF46B5D6, 0xFF53BBD3, 0xFF5FC0D0, 0xFF6AC5CD, 0xFF73CACB, 0xFF7ACDC9, 0xFF7ECFC7, 0xFF80D0C7 },
|
||||
new float[] { 0.0000f, 0.0807f, 0.1554f, 0.2250f, 0.2904f, 0.3526f, 0.4125f, 0.4710f, 0.5290f, 0.5875f, 0.6474f, 0.7096f, 0.7750f, 0.8446f, 0.9193f, 1.0000f },
|
||||
0f);
|
||||
public static final GradientChatWallpaper IRIDESCENT = new GradientChatWallpaper(192f,
|
||||
new int[] { 0xFFF04CE6, 0xFFEE4BE6, 0xFFE54AE5, 0xFFD949E5, 0xFFC946E4, 0xFFB644E3, 0xFFA141E3, 0xFF8B3FE2, 0xFF743CE1, 0xFF5E39E0, 0xFF4936DF, 0xFF3634DE, 0xFF2632DD, 0xFF1930DD, 0xFF112FDD, 0xFF0E2FDD },
|
||||
new float[] { 0.0000f, 0.0807f, 0.1554f, 0.2250f, 0.2904f, 0.3526f, 0.4125f, 0.4710f, 0.5290f, 0.5875f, 0.6474f, 0.7096f, 0.7750f, 0.8446f, 0.9193f, 1.0000f },
|
||||
0f);
|
||||
public static final GradientChatWallpaper MONSTERA = new GradientChatWallpaper(180f,
|
||||
new int[] { 0xFF65CDAC, 0xFF64CDAB, 0xFF60CBA8, 0xFF5BC8A3, 0xFF55C49D, 0xFF4DC096, 0xFF45BB8F, 0xFF3CB687, 0xFF33B17F, 0xFF2AAC76, 0xFF21A76F, 0xFF1AA268, 0xFF139F62, 0xFF0E9C5E, 0xFF0B9A5B, 0xFF0A995A },
|
||||
new float[] { 0.0000f, 0.0807f, 0.1554f, 0.2250f, 0.2904f, 0.3526f, 0.4125f, 0.4710f, 0.5290f, 0.5875f, 0.6474f, 0.7096f, 0.7750f, 0.8446f, 0.9193f, 1.0000f },
|
||||
0f);
|
||||
public static final GradientChatWallpaper BLISS = new GradientChatWallpaper(180f,
|
||||
new int[] { 0xFFD8E1FA, 0xFFD8E0F9, 0xFFD8DEF7, 0xFFD8DBF3, 0xFFD8D6EE, 0xFFD7D1E8, 0xFFD7CCE2, 0xFFD7C6DB, 0xFFD7BFD4, 0xFFD7B9CD, 0xFFD6B4C7, 0xFFD6AFC1, 0xFFD6AABC, 0xFFD6A7B8, 0xFFD6A5B6, 0xFFD6A4B5 },
|
||||
new float[] { 0.0000f, 0.0807f, 0.1554f, 0.2250f, 0.2904f, 0.3526f, 0.4125f, 0.4710f, 0.5290f, 0.5875f, 0.6474f, 0.7096f, 0.7750f, 0.8446f, 0.9193f, 1.0000f },
|
||||
0f);
|
||||
public static final GradientChatWallpaper SKY = new GradientChatWallpaper(180f,
|
||||
new int[] { 0xFFD8EBFD, 0xFFD7EAFD, 0xFFD5E9FD, 0xFFD2E7FD, 0xFFCDE5FD, 0xFFC8E3FD, 0xFFC3E0FD, 0xFFBDDDFC, 0xFFB7DAFC, 0xFFB2D7FC, 0xFFACD4FC, 0xFFA7D1FC, 0xFFA3CFFB, 0xFFA0CDFB, 0xFF9ECCFB, 0xFF9DCCFB },
|
||||
new float[] { 0.0000f, 0.0807f, 0.1554f, 0.2250f, 0.2904f, 0.3526f, 0.4125f, 0.4710f, 0.5290f, 0.5875f, 0.6474f, 0.7096f, 0.7750f, 0.8446f, 0.9193f, 1.0000f },
|
||||
0f);
|
||||
public static final GradientChatWallpaper PEACH = new GradientChatWallpaper(192f,
|
||||
new int[] { 0xFFFFE5C2, 0xFFFFE4C1, 0xFFFFE2BF, 0xFFFFDFBD, 0xFFFEDBB9, 0xFFFED6B5, 0xFFFED1B1, 0xFFFDCCAC, 0xFFFDC6A8, 0xFFFDC0A3, 0xFFFCBB9F, 0xFFFCB69B, 0xFFFCB297, 0xFFFCAF95, 0xFFFCAD93, 0xFFFCAC92 },
|
||||
new float[] { 0.0000f, 0.0807f, 0.1554f, 0.2250f, 0.2904f, 0.3526f, 0.4125f, 0.4710f, 0.5290f, 0.5875f, 0.6474f, 0.7096f, 0.7750f, 0.8446f, 0.9193f, 1.0000f },
|
||||
0f);
|
||||
|
||||
|
||||
private final float degrees;
|
||||
@@ -155,6 +155,10 @@ public final class GradientChatWallpaper implements ChatWallpaper, Parcelable {
|
||||
return result;
|
||||
}
|
||||
|
||||
public int[] getColors() {
|
||||
return colors;
|
||||
}
|
||||
|
||||
public static final Creator<GradientChatWallpaper> CREATOR = new Creator<GradientChatWallpaper>() {
|
||||
@Override
|
||||
public GradientChatWallpaper createFromParcel(Parcel in) {
|
||||
|
||||
@@ -14,18 +14,18 @@ import java.util.Objects;
|
||||
|
||||
public final class SingleColorChatWallpaper implements ChatWallpaper, Parcelable {
|
||||
|
||||
public static final ChatWallpaper BLUSH = new SingleColorChatWallpaper(0xFFE26983, 0f);
|
||||
public static final ChatWallpaper COPPER = new SingleColorChatWallpaper(0xFFDF9171, 0f);
|
||||
public static final ChatWallpaper DUST = new SingleColorChatWallpaper(0xFF9E9887, 0f);
|
||||
public static final ChatWallpaper CELADON = new SingleColorChatWallpaper(0xFF89AE8F, 0f);
|
||||
public static final ChatWallpaper RAINFOREST = new SingleColorChatWallpaper(0xFF146148, 0f);
|
||||
public static final ChatWallpaper PACIFIC = new SingleColorChatWallpaper(0xFF32C7E2, 0f);
|
||||
public static final ChatWallpaper FROST = new SingleColorChatWallpaper(0xFF7C99B6, 0f);
|
||||
public static final ChatWallpaper NAVY = new SingleColorChatWallpaper(0xFF403B91, 0f);
|
||||
public static final ChatWallpaper LILAC = new SingleColorChatWallpaper(0xFFC988E7, 0f);
|
||||
public static final ChatWallpaper PINK = new SingleColorChatWallpaper(0xFFE297C3, 0f);
|
||||
public static final ChatWallpaper EGGPLANT = new SingleColorChatWallpaper(0xFF624249, 0f);
|
||||
public static final ChatWallpaper SILVER = new SingleColorChatWallpaper(0xFFA2A2AA, 0f);
|
||||
public static final SingleColorChatWallpaper BLUSH = new SingleColorChatWallpaper(0xFFE26983, 0f);
|
||||
public static final SingleColorChatWallpaper COPPER = new SingleColorChatWallpaper(0xFFDF9171, 0f);
|
||||
public static final SingleColorChatWallpaper DUST = new SingleColorChatWallpaper(0xFF9E9887, 0f);
|
||||
public static final SingleColorChatWallpaper CELADON = new SingleColorChatWallpaper(0xFF89AE8F, 0f);
|
||||
public static final SingleColorChatWallpaper RAINFOREST = new SingleColorChatWallpaper(0xFF146148, 0f);
|
||||
public static final SingleColorChatWallpaper PACIFIC = new SingleColorChatWallpaper(0xFF32C7E2, 0f);
|
||||
public static final SingleColorChatWallpaper FROST = new SingleColorChatWallpaper(0xFF7C99B6, 0f);
|
||||
public static final SingleColorChatWallpaper NAVY = new SingleColorChatWallpaper(0xFF403B91, 0f);
|
||||
public static final SingleColorChatWallpaper LILAC = new SingleColorChatWallpaper(0xFFC988E7, 0f);
|
||||
public static final SingleColorChatWallpaper PINK = new SingleColorChatWallpaper(0xFFE297C3, 0f);
|
||||
public static final SingleColorChatWallpaper EGGPLANT = new SingleColorChatWallpaper(0xFF624249, 0f);
|
||||
public static final SingleColorChatWallpaper SILVER = new SingleColorChatWallpaper(0xFFA2A2AA, 0f);
|
||||
|
||||
private final @ColorInt int color;
|
||||
private final float dimLevelInDarkTheme;
|
||||
@@ -95,6 +95,10 @@ public final class SingleColorChatWallpaper implements ChatWallpaper, Parcelable
|
||||
return Objects.hash(color, dimLevelInDarkTheme);
|
||||
}
|
||||
|
||||
public int getColor() {
|
||||
return color;
|
||||
}
|
||||
|
||||
public static final Creator<SingleColorChatWallpaper> CREATOR = new Creator<SingleColorChatWallpaper>() {
|
||||
@Override
|
||||
public SingleColorChatWallpaper createFromParcel(Parcel in) {
|
||||
|
||||
@@ -27,7 +27,7 @@ import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
final class UriChatWallpaper implements ChatWallpaper, Parcelable {
|
||||
public final class UriChatWallpaper implements ChatWallpaper, Parcelable {
|
||||
|
||||
private static final LruCache<Uri, Bitmap> CACHE = new LruCache<Uri, Bitmap>((int) Runtime.getRuntime().maxMemory() / 8) {
|
||||
@Override
|
||||
@@ -118,6 +118,10 @@ final class UriChatWallpaper implements ChatWallpaper, Parcelable {
|
||||
return false;
|
||||
}
|
||||
|
||||
public @NonNull Uri getUri() {
|
||||
return uri;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull Wallpaper serialize() {
|
||||
return new Wallpaper.Builder()
|
||||
|
||||
@@ -1,18 +1,19 @@
|
||||
package org.thoughtcrime.securesms.wallpaper;
|
||||
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.WorkerThread;
|
||||
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.attachments.AttachmentId;
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase;
|
||||
import org.thoughtcrime.securesms.dependencies.AppDependencies;
|
||||
import org.thoughtcrime.securesms.jobs.UploadAttachmentToArchiveJob;
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||
import org.thoughtcrime.securesms.mms.PartAuthority;
|
||||
import org.thoughtcrime.securesms.util.storage.FileStorage;
|
||||
import org.thoughtcrime.securesms.mms.PartUriParser;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.List;
|
||||
@@ -26,31 +27,28 @@ public final class WallpaperStorage {
|
||||
|
||||
private static final String TAG = Log.tag(WallpaperStorage.class);
|
||||
|
||||
private static final String DIRECTORY = "wallpapers";
|
||||
private static final String FILENAME_BASE = "wallpaper";
|
||||
|
||||
/**
|
||||
* Saves the provided input stream as a new wallpaper file.
|
||||
*/
|
||||
@WorkerThread
|
||||
public static @NonNull ChatWallpaper save(@NonNull Context context, @NonNull InputStream wallpaperStream, @NonNull String extension) throws IOException {
|
||||
String name = FileStorage.save(context, wallpaperStream, DIRECTORY, FILENAME_BASE, extension);
|
||||
public static @NonNull ChatWallpaper save(@NonNull InputStream wallpaperStream) throws IOException {
|
||||
AttachmentId attachmentId = SignalDatabase.attachments().insertWallpaper(wallpaperStream);
|
||||
|
||||
return ChatWallpaperFactory.create(PartAuthority.getWallpaperUri(name));
|
||||
if (SignalStore.backup().backsUpMedia()) {
|
||||
AppDependencies.getJobManager().add(new UploadAttachmentToArchiveJob(attachmentId));
|
||||
}
|
||||
|
||||
return ChatWallpaperFactory.create(PartAuthority.getAttachmentDataUri(attachmentId));
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
public static @NonNull InputStream read(@NonNull Context context, String filename) throws IOException {
|
||||
return FileStorage.read(context, DIRECTORY, filename);
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
public static @NonNull List<ChatWallpaper> getAll(@NonNull Context context) {
|
||||
return FileStorage.getAll(context, DIRECTORY, FILENAME_BASE)
|
||||
.stream()
|
||||
.map(PartAuthority::getWallpaperUri)
|
||||
.map(ChatWallpaperFactory::create)
|
||||
.collect(Collectors.toList());
|
||||
public static @NonNull List<ChatWallpaper> getAll() {
|
||||
return SignalDatabase.attachments()
|
||||
.getAllWallpapers()
|
||||
.stream()
|
||||
.map(PartAuthority::getAttachmentDataUri)
|
||||
.map(ChatWallpaperFactory::create)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -58,7 +56,7 @@ public final class WallpaperStorage {
|
||||
* if we discover it's unused, we'll delete the file.
|
||||
*/
|
||||
@WorkerThread
|
||||
public static void onWallpaperDeselected(@NonNull Context context, @NonNull Uri uri) {
|
||||
public static void onWallpaperDeselected(@NonNull Uri uri) {
|
||||
Uri globalUri = SignalStore.wallpaper().getWallpaperUri();
|
||||
if (Objects.equals(uri, globalUri)) {
|
||||
return;
|
||||
@@ -69,12 +67,7 @@ public final class WallpaperStorage {
|
||||
return;
|
||||
}
|
||||
|
||||
String filename = PartAuthority.getWallpaperFilename(uri);
|
||||
File directory = context.getDir(DIRECTORY, Context.MODE_PRIVATE);
|
||||
File wallpaperFile = new File(directory, filename);
|
||||
|
||||
if (!wallpaperFile.delete()) {
|
||||
Log.w(TAG, "Failed to delete " + filename + "!");
|
||||
}
|
||||
AttachmentId attachmentId = new PartUriParser(uri).getPartId();
|
||||
SignalDatabase.attachments().deleteAttachment(attachmentId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,14 +33,14 @@ final class WallpaperCropRepository {
|
||||
@WorkerThread
|
||||
@NonNull ChatWallpaper setWallPaper(byte[] bytes) throws IOException {
|
||||
try (InputStream inputStream = new ByteArrayInputStream(bytes)) {
|
||||
ChatWallpaper wallpaper = WallpaperStorage.save(context, inputStream, "webp");
|
||||
ChatWallpaper wallpaper = WallpaperStorage.save(inputStream);
|
||||
|
||||
if (recipientId != null) {
|
||||
Log.i(TAG, "Setting image wallpaper for " + recipientId);
|
||||
SignalDatabase.recipients().setWallpaper(recipientId, wallpaper);
|
||||
SignalDatabase.recipients().setWallpaper(recipientId, wallpaper, true);
|
||||
} else {
|
||||
Log.i(TAG, "Setting image wallpaper for default");
|
||||
SignalStore.wallpaper().setWallpaper(context, wallpaper);
|
||||
SignalStore.wallpaper().setWallpaper(wallpaper);
|
||||
}
|
||||
|
||||
return wallpaper;
|
||||
|
||||
@@ -621,7 +621,7 @@ message FilePointer {
|
||||
|
||||
oneof locator {
|
||||
BackupLocator backupLocator = 1;
|
||||
AttachmentLocator attachmentLocator= 2;
|
||||
AttachmentLocator attachmentLocator = 2;
|
||||
InvalidAttachmentLocator invalidAttachmentLocator = 3;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
/*
|
||||
* Copyright 2024 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.securesms.util;
|
||||
|
||||
import android.app.Application;
|
||||
@@ -10,7 +15,6 @@ import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.robolectric.ParameterizedRobolectricTestRunner;
|
||||
import org.robolectric.annotation.Config;
|
||||
import org.thoughtcrime.securesms.BuildConfig;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
@@ -24,7 +28,7 @@ public class UriUtilTest_isValidExternalUri {
|
||||
private final String input;
|
||||
private final boolean output;
|
||||
|
||||
private static final String APPLICATION_ID = BuildConfig.APPLICATION_ID;
|
||||
private static final String APPLICATION_ID = "org.thoughtcrime.securesms";
|
||||
|
||||
@ParameterizedRobolectricTestRunner.Parameters
|
||||
public static Collection<Object[]> data() {
|
||||
|
||||
@@ -39,7 +39,8 @@ tasks.withType<KotlinCompile>().configureEach {
|
||||
afterEvaluate {
|
||||
listOf(
|
||||
"runKtlintCheckOverMainSourceSet",
|
||||
"runKtlintFormatOverMainSourceSet"
|
||||
"runKtlintFormatOverMainSourceSet",
|
||||
"sourcesJar"
|
||||
).forEach { taskName ->
|
||||
tasks.named(taskName) {
|
||||
mustRunAfter(tasks.named("generateMainProtos"))
|
||||
|
||||
Reference in New Issue
Block a user