Add notification profile and chat folder backupv2 proto support.

This commit is contained in:
Cody Henthorne
2024-12-09 11:04:32 -05:00
committed by Greyson Parrelli
parent c91123e8e8
commit d1bfa6ee9e
34 changed files with 469 additions and 37 deletions

View File

@@ -156,6 +156,14 @@ object ArchiveUploadProgress {
updatePhase(ArchiveUploadProgressState.BackupPhase.Sticker)
}
override fun onNotificationProfile() {
updatePhase(ArchiveUploadProgressState.BackupPhase.NotificationProfile)
}
override fun onChatFolder() {
updatePhase(ArchiveUploadProgressState.BackupPhase.ChatFolder)
}
override fun onMessage(currentProgress: Long, approximateCount: Long) {
updatePhase(ArchiveUploadProgressState.BackupPhase.Message, currentProgress, approximateCount)
}

View File

@@ -46,7 +46,9 @@ import org.thoughtcrime.securesms.backup.v2.importer.ChatItemArchiveImporter
import org.thoughtcrime.securesms.backup.v2.processor.AccountDataArchiveProcessor
import org.thoughtcrime.securesms.backup.v2.processor.AdHocCallArchiveProcessor
import org.thoughtcrime.securesms.backup.v2.processor.ChatArchiveProcessor
import org.thoughtcrime.securesms.backup.v2.processor.ChatFolderProcessor
import org.thoughtcrime.securesms.backup.v2.processor.ChatItemArchiveProcessor
import org.thoughtcrime.securesms.backup.v2.processor.NotificationProfileProcessor
import org.thoughtcrime.securesms.backup.v2.processor.RecipientArchiveProcessor
import org.thoughtcrime.securesms.backup.v2.processor.StickerArchiveProcessor
import org.thoughtcrime.securesms.backup.v2.proto.BackupInfo
@@ -576,6 +578,28 @@ object BackupRepository {
return@export
}
progressEmitter?.onNotificationProfile()
NotificationProfileProcessor.export(dbSnapshot, exportState) { frame ->
writer.write(frame)
eventTimer.emit("notification-profile")
frameCount++
}
if (cancellationSignal()) {
Log.w(TAG, "[export] Cancelled! Stopping")
return@export
}
progressEmitter?.onChatFolder()
ChatFolderProcessor.export(dbSnapshot, exportState) { frame ->
writer.write(frame)
eventTimer.emit("chat-folder")
frameCount++
}
if (cancellationSignal()) {
Log.w(TAG, "[export] Cancelled! Stopping")
return@export
}
val approximateMessageCount = dbSnapshot.messageTable.getApproximateExportableMessageCount(exportState.threadIds)
val frameCountStart = frameCount
progressEmitter?.onMessage(0, approximateMessageCount)
@@ -727,9 +751,6 @@ object BackupRepository {
SignalDatabase.recipients.setProfileKey(selfId, selfData.profileKey)
SignalDatabase.recipients.setProfileSharing(selfId, true)
// Add back default All Chats chat folder after clearing data
SignalDatabase.chatFolders.insertAllChatFolder()
val importState = ImportState(messageBackupKey, mediaRootBackupKey)
val chatItemInserter: ChatItemArchiveImporter = ChatItemArchiveProcessor.beginImport(importState)
@@ -768,6 +789,18 @@ object BackupRepository {
frameCount++
}
frame.notificationProfile != null -> {
NotificationProfileProcessor.import(frame.notificationProfile, importState)
eventTimer.emit("notification-profile")
frameCount++
}
frame.chatFolder != null -> {
ChatFolderProcessor.import(frame.chatFolder, importState)
eventTimer.emit("chat-folder")
frameCount++
}
frame.chatItem != null -> {
chatItemInserter.import(frame.chatItem)
eventTimer.emit("chatItem")
@@ -791,6 +824,11 @@ object BackupRepository {
eventTimer.emit("chatItem")
}
if (!importState.importedChatFolders) {
// Add back default All Chats chat folder after clearing data if missing
SignalDatabase.chatFolders.insertAllChatFolder()
}
stopwatch.split("frames")
Log.d(TAG, "[import] Rebuilding FTS index...")
@@ -1449,6 +1487,8 @@ object BackupRepository {
fun onThread()
fun onCall()
fun onSticker()
fun onNotificationProfile()
fun onChatFolder()
fun onMessage(currentProgress: Long, approximateCount: Long)
fun onAttachment(currentProgress: Long, totalCount: Long)
}
@@ -1477,10 +1517,20 @@ class ImportState(val messageBackupKey: MessageBackupKey, val mediaRootBackupKey
val chatIdToLocalRecipientId: MutableMap<Long, RecipientId> = hashMapOf()
val chatIdToBackupRecipientId: MutableMap<Long, Long> = hashMapOf()
val remoteToLocalColorId: MutableMap<Long, Long> = hashMapOf()
val recipientIdToLocalThreadId: MutableMap<RecipientId, Long> = hashMapOf()
val recipientIdToIsGroup: MutableMap<RecipientId, Boolean> = hashMapOf()
private var chatFolderPosition: Int = 0
val importedChatFolders: Boolean
get() = chatFolderPosition > 0
fun requireLocalRecipientId(remoteId: Long): RecipientId {
return remoteToLocalRecipientId[remoteId] ?: throw IllegalArgumentException("There is no local recipientId for remote recipientId $remoteId!")
}
fun getNextChatFolderPosition(): Int {
return chatFolderPosition++
}
}
class BackupMetadata(

View File

@@ -12,6 +12,8 @@ class LocalBackupV2Event(val type: Type, val count: Long = 0, val estimatedTotal
PROGRESS_THREAD,
PROGRESS_CALL,
PROGRESS_STICKER,
NOTIFICATION_PROFILE,
CHAT_FOLDER,
PROGRESS_MESSAGE,
PROGRESS_ATTACHMENT,
PROGRESS_VERIFYING,

View File

@@ -169,6 +169,14 @@ object LocalArchiver {
EventBus.getDefault().post(LocalBackupV2Event(LocalBackupV2Event.Type.PROGRESS_STICKER))
}
override fun onNotificationProfile() {
EventBus.getDefault().post(LocalBackupV2Event(LocalBackupV2Event.Type.NOTIFICATION_PROFILE))
}
override fun onChatFolder() {
EventBus.getDefault().post(LocalBackupV2Event(LocalBackupV2Event.Type.CHAT_FOLDER))
}
override fun onMessage(currentProgress: Long, approximateCount: Long) {
if (currentProgress == 0L) {
EventBus.getDefault().post(LocalBackupV2Event(LocalBackupV2Event.Type.PROGRESS_MESSAGE))

View File

@@ -46,5 +46,6 @@ object ChatArchiveProcessor {
importState.chatIdToLocalRecipientId[chat.id] = recipientId
importState.chatIdToLocalThreadId[chat.id] = threadId
importState.chatIdToBackupRecipientId[chat.id] = chat.recipientId
importState.recipientIdToLocalThreadId[recipientId] = threadId
}
}

View File

@@ -0,0 +1,144 @@
/*
* Copyright 2024 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.thoughtcrime.securesms.backup.v2.processor
import androidx.core.content.contentValuesOf
import org.signal.core.util.SqlUtil
import org.signal.core.util.insertInto
import org.signal.core.util.logging.Log
import org.thoughtcrime.securesms.backup.v2.ExportState
import org.thoughtcrime.securesms.backup.v2.ImportState
import org.thoughtcrime.securesms.backup.v2.proto.ChatFolder
import org.thoughtcrime.securesms.backup.v2.proto.Frame
import org.thoughtcrime.securesms.backup.v2.stream.BackupFrameEmitter
import org.thoughtcrime.securesms.components.settings.app.chats.folders.ChatFolderRecord
import org.thoughtcrime.securesms.database.ChatFolderTables.ChatFolderMembershipTable
import org.thoughtcrime.securesms.database.ChatFolderTables.ChatFolderTable
import org.thoughtcrime.securesms.database.ChatFolderTables.MembershipType
import org.thoughtcrime.securesms.database.SignalDatabase
import org.thoughtcrime.securesms.backup.v2.proto.ChatFolder as ChatFolderProto
/**
* Handles exporting and importing [ChatFolderRecord]s.
*/
object ChatFolderProcessor {
private val TAG = Log.tag(ChatFolderProcessor::class)
fun export(db: SignalDatabase, exportState: ExportState, emitter: BackupFrameEmitter) {
val folders = db
.chatFoldersTable
.getChatFolders()
.sortedBy { it.position }
if (folders.isEmpty()) {
Log.d(TAG, "No chat folders, nothing to export")
return
}
if (folders.size == 1 && folders[0].folderType == ChatFolderRecord.FolderType.ALL) {
Log.d(TAG, "Only ALL chat folder present, skipping chat folder export")
return
}
if (folders.none { it.folderType == ChatFolderRecord.FolderType.ALL }) {
Log.w(TAG, "Missing ALL chat folder, exporting as first position")
emitter.emit(ChatFolderRecord.getAllChatsFolderForBackup().toBackupFrame(emptyList(), emptyList()))
}
folders.forEach { folder ->
val includedRecipientIds = folder
.includedChats
.map { db.threadTable.getRecipientIdForThreadId(it)!!.toLong() }
.filter { exportState.recipientIds.contains(it) }
val excludedRecipientIds = folder
.excludedChats
.map { db.threadTable.getRecipientIdForThreadId(it)!!.toLong() }
.filter { exportState.recipientIds.contains(it) }
val frame = folder.toBackupFrame(includedRecipientIds, excludedRecipientIds)
emitter.emit(frame)
}
}
fun import(chatFolder: ChatFolderProto, importState: ImportState) {
val chatFolderId = SignalDatabase
.writableDatabase
.insertInto(ChatFolderTable.TABLE_NAME)
.values(
ChatFolderTable.NAME to chatFolder.name,
ChatFolderTable.POSITION to importState.getNextChatFolderPosition(),
ChatFolderTable.SHOW_UNREAD to chatFolder.showOnlyUnread,
ChatFolderTable.SHOW_MUTED to chatFolder.showMutedChats,
ChatFolderTable.SHOW_INDIVIDUAL to chatFolder.includeAllIndividualChats,
ChatFolderTable.SHOW_GROUPS to chatFolder.includeAllGroupChats,
ChatFolderTable.FOLDER_TYPE to chatFolder.folderType.toLocal().value
)
.run()
if (chatFolderId < 0) {
Log.w(TAG, "Chat folder already exists")
return
}
val includedChatsQueries = chatFolder.includedRecipientIds.toMembershipInsertQueries(chatFolderId, importState, MembershipType.INCLUDED)
includedChatsQueries.forEach {
SignalDatabase.writableDatabase.execSQL(it.where, it.whereArgs)
}
val excludedChatsQueries = chatFolder.excludedRecipientIds.toMembershipInsertQueries(chatFolderId, importState, MembershipType.EXCLUDED)
excludedChatsQueries.forEach {
SignalDatabase.writableDatabase.execSQL(it.where, it.whereArgs)
}
}
}
private fun ChatFolderRecord.toBackupFrame(includedRecipientIds: List<Long>, excludedRecipientIds: List<Long>): Frame {
val chatFolder = ChatFolderProto(
name = this.name,
showOnlyUnread = this.showUnread,
showMutedChats = this.showMutedChats,
includeAllIndividualChats = this.showIndividualChats,
includeAllGroupChats = this.showGroupChats,
folderType = when (this.folderType) {
ChatFolderRecord.FolderType.ALL -> ChatFolderProto.FolderType.ALL
ChatFolderRecord.FolderType.CUSTOM -> ChatFolderProto.FolderType.CUSTOM
else -> throw IllegalStateException("Only ALL or CUSTOM should be in the db")
},
includedRecipientIds = includedRecipientIds,
excludedRecipientIds = excludedRecipientIds
)
return Frame(chatFolder = chatFolder)
}
private fun ChatFolderProto.FolderType.toLocal(): ChatFolderRecord.FolderType {
return when (this) {
ChatFolder.FolderType.UNKNOWN -> throw IllegalStateException()
ChatFolder.FolderType.ALL -> ChatFolderRecord.FolderType.ALL
ChatFolder.FolderType.CUSTOM -> ChatFolderRecord.FolderType.CUSTOM
}
}
private fun List<Long>.toMembershipInsertQueries(chatFolderId: Long, importState: ImportState, membershipType: MembershipType): List<SqlUtil.Query> {
val values = this
.mapNotNull { importState.remoteToLocalRecipientId[it] }
.map { recipientId -> importState.recipientIdToLocalThreadId[recipientId] ?: SignalDatabase.threads.getOrCreateThreadIdFor(recipientId, importState.recipientIdToIsGroup[recipientId] == true) }
.map { threadId ->
contentValuesOf(
ChatFolderMembershipTable.CHAT_FOLDER_ID to chatFolderId,
ChatFolderMembershipTable.THREAD_ID to threadId,
ChatFolderMembershipTable.MEMBERSHIP_TYPE to membershipType.value
)
}
return SqlUtil.buildBulkInsert(
ChatFolderMembershipTable.TABLE_NAME,
arrayOf(ChatFolderMembershipTable.CHAT_FOLDER_ID, ChatFolderMembershipTable.THREAD_ID, ChatFolderMembershipTable.MEMBERSHIP_TYPE),
values
)
}

View File

@@ -0,0 +1,131 @@
/*
* Copyright 2024 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.thoughtcrime.securesms.backup.v2.processor
import org.signal.core.util.insertInto
import org.signal.core.util.logging.Log
import org.signal.core.util.toInt
import org.thoughtcrime.securesms.backup.v2.ExportState
import org.thoughtcrime.securesms.backup.v2.ImportState
import org.thoughtcrime.securesms.backup.v2.proto.Frame
import org.thoughtcrime.securesms.backup.v2.stream.BackupFrameEmitter
import org.thoughtcrime.securesms.conversation.colors.AvatarColor
import org.thoughtcrime.securesms.database.NotificationProfileTables.NotificationProfileAllowedMembersTable
import org.thoughtcrime.securesms.database.NotificationProfileTables.NotificationProfileScheduleTable
import org.thoughtcrime.securesms.database.NotificationProfileTables.NotificationProfileTable
import org.thoughtcrime.securesms.database.SignalDatabase
import org.thoughtcrime.securesms.database.serialize
import org.thoughtcrime.securesms.notifications.profiles.NotificationProfile
import org.thoughtcrime.securesms.recipients.RecipientId
import java.lang.IllegalStateException
import java.time.DayOfWeek
import org.thoughtcrime.securesms.backup.v2.proto.NotificationProfile as NotificationProfileProto
/**
* Handles exporting and importing [NotificationProfile] models.
*/
object NotificationProfileProcessor {
private val TAG = Log.tag(NotificationProfileProcessor::class)
fun export(db: SignalDatabase, exportState: ExportState, emitter: BackupFrameEmitter) {
db.notificationProfileTables
.getProfiles()
.forEach { profile ->
val frame = profile.toBackupFrame(includeRecipient = { id -> exportState.recipientIds.contains(id.toLong()) })
emitter.emit(frame)
}
}
fun import(profile: NotificationProfileProto, importState: ImportState) {
val profileId = SignalDatabase
.writableDatabase
.insertInto(NotificationProfileTable.TABLE_NAME)
.values(
NotificationProfileTable.NAME to profile.name,
NotificationProfileTable.EMOJI to (profile.emoji ?: ""),
NotificationProfileTable.COLOR to (AvatarColor.fromColor(profile.color) ?: AvatarColor.random()).serialize(),
NotificationProfileTable.CREATED_AT to profile.createdAtMs,
NotificationProfileTable.ALLOW_ALL_CALLS to profile.allowAllCalls.toInt(),
NotificationProfileTable.ALLOW_ALL_MENTIONS to profile.allowAllMentions.toInt()
)
.run()
if (profileId < 0) {
Log.w(TAG, "Notification profile name already exists")
return
}
SignalDatabase
.writableDatabase
.insertInto(NotificationProfileScheduleTable.TABLE_NAME)
.values(
NotificationProfileScheduleTable.NOTIFICATION_PROFILE_ID to profileId,
NotificationProfileScheduleTable.ENABLED to profile.scheduleEnabled.toInt(),
NotificationProfileScheduleTable.START to profile.scheduleStartTime,
NotificationProfileScheduleTable.END to profile.scheduleEndTime,
NotificationProfileScheduleTable.DAYS_ENABLED to profile.scheduleDaysEnabled.map { it.toLocal() }.toSet().serialize()
)
.run()
profile
.allowedMembers
.mapNotNull { importState.remoteToLocalRecipientId[it] }
.forEach { recipientId ->
SignalDatabase
.writableDatabase
.insertInto(NotificationProfileAllowedMembersTable.TABLE_NAME)
.values(
NotificationProfileAllowedMembersTable.NOTIFICATION_PROFILE_ID to profileId,
NotificationProfileAllowedMembersTable.RECIPIENT_ID to recipientId.serialize()
)
.run()
}
}
}
private fun NotificationProfile.toBackupFrame(includeRecipient: (RecipientId) -> Boolean): Frame {
val profile = NotificationProfileProto(
name = this.name,
emoji = this.emoji.takeIf { it.isNotBlank() },
color = this.color.colorInt(),
createdAtMs = this.createdAt,
allowAllCalls = this.allowAllCalls,
allowAllMentions = this.allowAllMentions,
allowedMembers = this.allowedMembers.filter { includeRecipient(it) }.map { it.toLong() },
scheduleEnabled = this.schedule.enabled,
scheduleStartTime = this.schedule.start,
scheduleEndTime = this.schedule.end,
scheduleDaysEnabled = this.schedule.daysEnabled.map { it.toBackupProto() }
)
return Frame(notificationProfile = profile)
}
private fun DayOfWeek.toBackupProto(): NotificationProfileProto.DayOfWeek {
return when (this) {
DayOfWeek.MONDAY -> NotificationProfileProto.DayOfWeek.MONDAY
DayOfWeek.TUESDAY -> NotificationProfileProto.DayOfWeek.TUESDAY
DayOfWeek.WEDNESDAY -> NotificationProfileProto.DayOfWeek.WEDNESDAY
DayOfWeek.THURSDAY -> NotificationProfileProto.DayOfWeek.THURSDAY
DayOfWeek.FRIDAY -> NotificationProfileProto.DayOfWeek.FRIDAY
DayOfWeek.SATURDAY -> NotificationProfileProto.DayOfWeek.SATURDAY
DayOfWeek.SUNDAY -> NotificationProfileProto.DayOfWeek.SUNDAY
}
}
private fun NotificationProfileProto.DayOfWeek.toLocal(): DayOfWeek {
return when (this) {
NotificationProfileProto.DayOfWeek.UNKNOWN -> throw IllegalStateException()
NotificationProfileProto.DayOfWeek.MONDAY -> DayOfWeek.MONDAY
NotificationProfileProto.DayOfWeek.TUESDAY -> DayOfWeek.TUESDAY
NotificationProfileProto.DayOfWeek.WEDNESDAY -> DayOfWeek.WEDNESDAY
NotificationProfileProto.DayOfWeek.THURSDAY -> DayOfWeek.THURSDAY
NotificationProfileProto.DayOfWeek.FRIDAY -> DayOfWeek.FRIDAY
NotificationProfileProto.DayOfWeek.SATURDAY -> DayOfWeek.SATURDAY
NotificationProfileProto.DayOfWeek.SUNDAY -> DayOfWeek.SUNDAY
}
}

View File

@@ -49,4 +49,15 @@ data class ChatFolderRecord(
}
}
}
companion object {
fun getAllChatsFolderForBackup(): ChatFolderRecord {
return ChatFolderRecord(
folderType = ChatFolderRecord.FolderType.ALL,
showIndividualChats = true,
showGroupChats = true,
showMutedChats = true
)
}
}
}

View File

@@ -4,7 +4,7 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
import io.reactivex.rxjava3.core.Single
import org.thoughtcrime.securesms.database.NotificationProfileDatabase
import org.thoughtcrime.securesms.database.NotificationProfileTables
import org.thoughtcrime.securesms.notifications.profiles.NotificationProfile
class EditNotificationProfileViewModel(private val profileId: Long, private val repository: NotificationProfilesRepository) : ViewModel() {
@@ -34,8 +34,8 @@ class EditNotificationProfileViewModel(private val profileId: Long, private val
return save.map { r ->
when (r) {
is NotificationProfileDatabase.NotificationProfileChangeResult.Success -> SaveNotificationProfileResult.Success(r.notificationProfile, createMode)
NotificationProfileDatabase.NotificationProfileChangeResult.DuplicateName -> SaveNotificationProfileResult.DuplicateNameFailure
is NotificationProfileTables.NotificationProfileChangeResult.Success -> SaveNotificationProfileResult.Success(r.notificationProfile, createMode)
NotificationProfileTables.NotificationProfileChangeResult.DuplicateName -> SaveNotificationProfileResult.DuplicateNameFailure
}
}.observeOn(AndroidSchedulers.mainThread())
}

View File

@@ -8,7 +8,7 @@ import io.reactivex.rxjava3.core.Single
import io.reactivex.rxjava3.schedulers.Schedulers
import org.thoughtcrime.securesms.conversation.colors.AvatarColor
import org.thoughtcrime.securesms.database.DatabaseObserver
import org.thoughtcrime.securesms.database.NotificationProfileDatabase
import org.thoughtcrime.securesms.database.NotificationProfileTables
import org.thoughtcrime.securesms.database.RxDatabaseObserver
import org.thoughtcrime.securesms.database.SignalDatabase
import org.thoughtcrime.securesms.dependencies.AppDependencies
@@ -24,7 +24,7 @@ import org.thoughtcrime.securesms.util.toMillis
* One stop shop for all your Notification Profile data needs.
*/
class NotificationProfilesRepository {
private val database: NotificationProfileDatabase = SignalDatabase.notificationProfiles
private val database: NotificationProfileTables = SignalDatabase.notificationProfiles
fun getProfiles(): Flowable<List<NotificationProfile>> {
return RxDatabaseObserver
@@ -54,17 +54,17 @@ class NotificationProfilesRepository {
}.subscribeOn(Schedulers.io())
}
fun createProfile(name: String, selectedEmoji: String): Single<NotificationProfileDatabase.NotificationProfileChangeResult> {
fun createProfile(name: String, selectedEmoji: String): Single<NotificationProfileTables.NotificationProfileChangeResult> {
return Single.fromCallable { database.createProfile(name = name, emoji = selectedEmoji, color = AvatarColor.random(), createdAt = System.currentTimeMillis()) }
.subscribeOn(Schedulers.io())
}
fun updateProfile(profileId: Long, name: String, selectedEmoji: String): Single<NotificationProfileDatabase.NotificationProfileChangeResult> {
fun updateProfile(profileId: Long, name: String, selectedEmoji: String): Single<NotificationProfileTables.NotificationProfileChangeResult> {
return Single.fromCallable { database.updateProfile(profileId = profileId, name = name, emoji = selectedEmoji) }
.subscribeOn(Schedulers.io())
}
fun updateProfile(profile: NotificationProfile): Single<NotificationProfileDatabase.NotificationProfileChangeResult> {
fun updateProfile(profile: NotificationProfile): Single<NotificationProfileTables.NotificationProfileChangeResult> {
return Single.fromCallable { database.updateProfile(profile) }
.subscribeOn(Schedulers.io())
}
@@ -99,7 +99,7 @@ class NotificationProfilesRepository {
.take(1)
.singleOrError()
.flatMap { updateProfile(it.copy(allowAllMentions = !it.allowAllMentions)) }
.map { (it as NotificationProfileDatabase.NotificationProfileChangeResult.Success).notificationProfile }
.map { (it as NotificationProfileTables.NotificationProfileChangeResult.Success).notificationProfile }
}
fun toggleAllowAllCalls(profileId: Long): Single<NotificationProfile> {
@@ -107,7 +107,7 @@ class NotificationProfilesRepository {
.take(1)
.singleOrError()
.flatMap { updateProfile(it.copy(allowAllCalls = !it.allowAllCalls)) }
.map { (it as NotificationProfileDatabase.NotificationProfileChangeResult.Success).notificationProfile }
.map { (it as NotificationProfileTables.NotificationProfileChangeResult.Success).notificationProfile }
}
fun manuallyToggleProfile(profile: NotificationProfile, now: Long = System.currentTimeMillis()): Completable {

View File

@@ -4,9 +4,11 @@ import androidx.annotation.ColorInt;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
/**
* A serializable set of color constants that can be used for avatars.
@@ -27,8 +29,11 @@ public enum AvatarColor {
UNKNOWN("UNKNOWN", 0x00000000),
ON_SURFACE_VARIANT("ON_SURFACE_VARIANT", 0x00000000);
/** Fast map of name to enum, while also giving us a location to map old colors to new ones. */
/**
* Fast map of name to enum, while also giving us a location to map old colors to new ones.
*/
private static final Map<String, AvatarColor> NAME_MAP = new HashMap<>();
static {
for (AvatarColor color : AvatarColor.values()) {
NAME_MAP.put(color.serialize(), color);
@@ -97,7 +102,9 @@ public enum AvatarColor {
NAME_MAP.put("grey", A210);
}
/** Colors that can be assigned via {@link #random()}. */
/**
* Colors that can be assigned via {@link #random()}.
*/
static final AvatarColor[] RANDOM_OPTIONS = new AvatarColor[] {
A100,
A110,
@@ -137,4 +144,11 @@ public enum AvatarColor {
public static @NonNull AvatarColor deserialize(@Nullable String name) {
return Objects.requireNonNull(NAME_MAP.getOrDefault(name, A210));
}
public static @Nullable AvatarColor fromColor(@ColorInt int color) {
return Arrays.stream(values())
.filter(c -> c.color == color)
.findFirst()
.orElse(null);
}
}

View File

@@ -22,7 +22,7 @@ import java.time.DayOfWeek
/**
* Database for maintaining Notification Profiles, Notification Profile Schedules, and Notification Profile allowed memebers.
*/
class NotificationProfileDatabase(context: Context, databaseHelper: SignalDatabase) : DatabaseTable(context, databaseHelper), RecipientIdDatabaseReference {
class NotificationProfileTables(context: Context, databaseHelper: SignalDatabase) : DatabaseTable(context, databaseHelper), RecipientIdDatabaseReference {
companion object {
@JvmField
@@ -32,7 +32,7 @@ class NotificationProfileDatabase(context: Context, databaseHelper: SignalDataba
val CREATE_INDEXES: Array<String> = arrayOf(NotificationProfileScheduleTable.CREATE_INDEX, NotificationProfileAllowedMembersTable.CREATE_INDEX)
}
private object NotificationProfileTable {
object NotificationProfileTable {
const val TABLE_NAME = "notification_profile"
const val ID = "_id"
@@ -56,7 +56,7 @@ class NotificationProfileDatabase(context: Context, databaseHelper: SignalDataba
"""
}
private object NotificationProfileScheduleTable {
object NotificationProfileScheduleTable {
const val TABLE_NAME = "notification_profile_schedule"
const val ID = "_id"
@@ -82,7 +82,7 @@ class NotificationProfileDatabase(context: Context, databaseHelper: SignalDataba
const val CREATE_INDEX = "CREATE INDEX notification_profile_schedule_profile_index ON $TABLE_NAME ($NOTIFICATION_PROFILE_ID)"
}
private object NotificationProfileAllowedMembersTable {
object NotificationProfileAllowedMembersTable {
const val TABLE_NAME = "notification_profile_allowed_members"
const val ID = "_id"
@@ -367,7 +367,7 @@ class NotificationProfileDatabase(context: Context, databaseHelper: SignalDataba
}
}
private fun Iterable<DayOfWeek>.serialize(): String {
fun Iterable<DayOfWeek>.serialize(): String {
return joinToString(separator = ",", transform = { it.serialize() })
}

View File

@@ -63,7 +63,7 @@ open class SignalDatabase(private val context: Application, databaseSecret: Data
val messageSendLogTables: MessageSendLogTables = MessageSendLogTables(context, this)
val avatarPickerDatabase: AvatarPickerDatabase = AvatarPickerDatabase(context, this)
val reactionTable: ReactionTable = ReactionTable(context, this)
val notificationProfileDatabase: NotificationProfileDatabase = NotificationProfileDatabase(context, this)
val notificationProfileTables: NotificationProfileTables = NotificationProfileTables(context, this)
val donationReceiptTable: DonationReceiptTable = DonationReceiptTable(context, this)
val distributionListTables: DistributionListTables = DistributionListTables(context, this)
val storySendTable: StorySendTable = StorySendTable(context, this)
@@ -120,7 +120,7 @@ open class SignalDatabase(private val context: Application, databaseSecret: Data
executeStatements(db, SearchTable.CREATE_TABLE)
executeStatements(db, RemappedRecordTables.CREATE_TABLE)
executeStatements(db, MessageSendLogTables.CREATE_TABLE)
executeStatements(db, NotificationProfileDatabase.CREATE_TABLE)
executeStatements(db, NotificationProfileTables.CREATE_TABLE)
executeStatements(db, DistributionListTables.CREATE_TABLE)
executeStatements(db, ChatFolderTables.CREATE_TABLE)
db.execSQL(BackupMediaSnapshotTable.CREATE_TABLE)
@@ -137,7 +137,7 @@ open class SignalDatabase(private val context: Application, databaseSecret: Data
executeStatements(db, MentionTable.CREATE_INDEXES)
executeStatements(db, PaymentTable.CREATE_INDEXES)
executeStatements(db, MessageSendLogTables.CREATE_INDEXES)
executeStatements(db, NotificationProfileDatabase.CREATE_INDEXES)
executeStatements(db, NotificationProfileTables.CREATE_INDEXES)
executeStatements(db, DonationReceiptTable.CREATE_INDEXS)
executeStatements(db, StorySendTable.CREATE_INDEXS)
executeStatements(db, DistributionListTables.CREATE_INDEXES)
@@ -456,8 +456,8 @@ open class SignalDatabase(private val context: Application, databaseSecret: Data
@get:JvmStatic
@get:JvmName("notificationProfiles")
val notificationProfiles: NotificationProfileDatabase
get() = instance!!.notificationProfileDatabase
val notificationProfiles: NotificationProfileTables
get() = instance!!.notificationProfileTables
@get:JvmStatic
@get:JvmName("payments")