From 7fccbd44c07995fefc900795eb18316aa2945526 Mon Sep 17 00:00:00 2001 From: Cody Henthorne Date: Tue, 18 Oct 2022 16:06:37 -0400 Subject: [PATCH] Fix infinite export loop and improve general error handling. --- .../database/AttachmentDatabase.java | 16 ++++++++-- .../securesms/database/MessageDatabase.java | 16 +++++++++- .../securesms/database/MmsDatabase.java | 8 +++-- .../securesms/database/MmsSmsDatabase.java | 12 ++++++- .../securesms/database/SmsDatabase.java | 8 +++-- .../helpers/SignalDatabaseMigrations.kt | 7 +++- .../V160_SmsMmsExportedIndexMigration.kt | 11 +++++++ .../database/model/MessageExportStatus.kt | 32 +++++++++++++++++++ .../exporter/SignalSmsExportService.kt | 9 ++++++ .../flow/ExportSmsCompleteFragment.kt | 7 ++-- .../flow/ExportYourSmsMessagesFragment.kt | 2 +- .../flow/ExportingSmsMessagesFragment.kt | 2 +- app/src/main/res/navigation/sms_export.xml | 15 +++++++-- .../core/util/SQLiteDatabaseExtensions.kt | 1 + .../java/org/signal/core/util/Serializer.kt | 2 ++ .../main/java/org/signal/core/util/SqlUtil.kt | 2 +- .../signal/smsexporter/SmsExportProgress.kt | 3 +- .../signal/smsexporter/SmsExportService.kt | 32 ++++++++++++++----- 18 files changed, 155 insertions(+), 30 deletions(-) create mode 100644 app/src/main/java/org/thoughtcrime/securesms/database/helpers/migration/V160_SmsMmsExportedIndexMigration.kt create mode 100644 app/src/main/java/org/thoughtcrime/securesms/database/model/MessageExportStatus.kt diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/AttachmentDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/AttachmentDatabase.java index 3dc93134d..2d965f407 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/AttachmentDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/AttachmentDatabase.java @@ -65,6 +65,7 @@ import org.thoughtcrime.securesms.video.EncryptedMediaDataSource; import org.whispersystems.signalservice.internal.util.JsonUtil; import java.io.File; +import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; @@ -204,7 +205,13 @@ public class AttachmentDatabase extends Database { public @NonNull InputStream getAttachmentStream(AttachmentId attachmentId, long offset) throws IOException { - InputStream dataStream = getDataStream(attachmentId, DATA, offset); + InputStream dataStream; + + try { + dataStream = getDataStream(attachmentId, DATA, offset); + } catch (FileNotFoundException e) { + throw new IOException("No stream for: " + attachmentId, e); + } if (dataStream == null) throw new IOException("No stream for: " + attachmentId); else return dataStream; @@ -1019,8 +1026,8 @@ public class AttachmentDatabase extends Database { } @SuppressWarnings("WeakerAccess") - @VisibleForTesting - protected @Nullable InputStream getDataStream(AttachmentId attachmentId, String dataType, long offset) + private @Nullable InputStream getDataStream(AttachmentId attachmentId, String dataType, long offset) + throws FileNotFoundException { DataInfo dataInfo = getAttachmentDataFileInfo(attachmentId, dataType); @@ -1042,6 +1049,9 @@ public class AttachmentDatabase extends Database { return stream; } + } catch (FileNotFoundException e) { + Log.w(TAG, e); + throw e; } catch (IOException e) { Log.w(TAG, e); return null; diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/MessageDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/MessageDatabase.java index 7a719e731..90c6be1c3 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/MessageDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/MessageDatabase.java @@ -22,6 +22,7 @@ import org.thoughtcrime.securesms.database.documents.Document; import org.thoughtcrime.securesms.database.documents.IdentityKeyMismatch; import org.thoughtcrime.securesms.database.documents.IdentityKeyMismatchSet; import org.thoughtcrime.securesms.database.documents.NetworkFailure; +import org.thoughtcrime.securesms.database.model.MessageExportStatus; import org.thoughtcrime.securesms.database.model.MessageId; import org.thoughtcrime.securesms.database.model.MessageRecord; import org.thoughtcrime.securesms.database.model.ParentStoryId; @@ -398,7 +399,7 @@ public abstract class MessageDatabase extends Database implements MmsSmsColumns, } public int getUnexportedInsecureMessagesCount(long threadId) { - try (Cursor cursor = getWritableDatabase().query(getTableName(), SqlUtil.COUNT, getInsecureMessageClause(threadId) + " AND NOT " + EXPORTED, null, null, null, null)) { + try (Cursor cursor = getWritableDatabase().query(getTableName(), SqlUtil.COUNT, getInsecureMessageClause(threadId) + " AND " + EXPORTED + " < ?", SqlUtil.buildArgs(MessageExportStatus.EXPORTED), null, null, null)) { if (cursor.moveToFirst()) { return cursor.getInt(0); } @@ -407,6 +408,19 @@ public abstract class MessageDatabase extends Database implements MmsSmsColumns, return 0; } + /** + * Reset the exported status (not state) to the default for clearing errors. + */ + public void clearInsecureMessageExportedErrorStatus() { + ContentValues values = new ContentValues(1); + values.put(EXPORTED, MessageExportStatus.UNEXPORTED.getCode()); + + SQLiteDatabaseExtensionsKt.update(getWritableDatabase(), getTableName()) + .values(values) + .where(EXPORTED + " < ?", MessageExportStatus.UNEXPORTED.getCode()) + .run(); + } + public void setReactionsSeen(long threadId, long sinceTimestamp) { SQLiteDatabase db = databaseHelper.getSignalWritableDatabase(); ContentValues values = new ContentValues(); diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.java index 03d2d2d48..8efc0726c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.java @@ -52,6 +52,7 @@ import org.thoughtcrime.securesms.database.documents.NetworkFailure; import org.thoughtcrime.securesms.database.documents.NetworkFailureSet; import org.thoughtcrime.securesms.database.model.MediaMmsMessageRecord; import org.thoughtcrime.securesms.database.model.Mention; +import org.thoughtcrime.securesms.database.model.MessageExportStatus; import org.thoughtcrime.securesms.database.model.MessageId; import org.thoughtcrime.securesms.database.model.MessageRecord; import org.thoughtcrime.securesms.database.model.NotificationMmsMessageRecord; @@ -205,7 +206,8 @@ public class MmsDatabase extends MessageDatabase { "CREATE INDEX IF NOT EXISTS mms_is_story_index ON " + TABLE_NAME + " (" + STORY_TYPE + ");", "CREATE INDEX IF NOT EXISTS mms_parent_story_id_index ON " + TABLE_NAME + " (" + PARENT_STORY_ID + ");", "CREATE INDEX IF NOT EXISTS mms_thread_story_parent_story_index ON " + TABLE_NAME + " (" + THREAD_ID + ", " + DATE_RECEIVED + "," + STORY_TYPE + "," + PARENT_STORY_ID + ");", - "CREATE INDEX IF NOT EXISTS mms_quote_id_quote_author_index ON " + TABLE_NAME + "(" + QUOTE_ID + ", " + QUOTE_AUTHOR + ");" + "CREATE INDEX IF NOT EXISTS mms_quote_id_quote_author_index ON " + TABLE_NAME + "(" + QUOTE_ID + ", " + QUOTE_AUTHOR + ");", + "CREATE INDEX IF NOT EXISTS mms_exported_index ON " + TABLE_NAME + " (" + EXPORTED + ");" }; private static final String[] MMS_PROJECTION = new String[] { @@ -2466,13 +2468,13 @@ public class MmsDatabase extends MessageDatabase { beginTransaction(); try { List threadsToUpdate = new LinkedList<>(); - try (Cursor cursor = getReadableDatabase().query(TABLE_NAME, THREAD_ID_PROJECTION, EXPORTED + " = ?", SqlUtil.buildArgs(1), THREAD_ID, null, null, null)) { + try (Cursor cursor = getReadableDatabase().query(TABLE_NAME, THREAD_ID_PROJECTION, EXPORTED + " = ?", SqlUtil.buildArgs(MessageExportStatus.EXPORTED), THREAD_ID, null, null, null)) { while (cursor.moveToNext()) { threadsToUpdate.add(CursorUtil.requireLong(cursor, THREAD_ID)); } } - getWritableDatabase().delete(TABLE_NAME, EXPORTED + " = ?", SqlUtil.buildArgs(1)); + getWritableDatabase().delete(TABLE_NAME, EXPORTED + " = ?", SqlUtil.buildArgs(MessageExportStatus.EXPORTED)); for (final long threadId : threadsToUpdate) { SignalDatabase.threads().update(threadId, false); diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsDatabase.java index 79331c80c..dd6761710 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsDatabase.java @@ -35,6 +35,7 @@ import org.signal.core.util.logging.Log; import org.signal.libsignal.protocol.util.Pair; import org.thoughtcrime.securesms.database.MessageDatabase.MessageUpdate; import org.thoughtcrime.securesms.database.MessageDatabase.SyncMessageId; +import org.thoughtcrime.securesms.database.model.MessageExportStatus; import org.thoughtcrime.securesms.database.model.MessageId; import org.thoughtcrime.securesms.database.model.MessageRecord; import org.thoughtcrime.securesms.database.model.databaseprotos.MessageExportState; @@ -671,7 +672,16 @@ public class MmsSmsDatabase extends Database { String table = messageId.isMms() ? MmsDatabase.TABLE_NAME : SmsDatabase.TABLE_NAME; ContentValues contentValues = new ContentValues(1); - contentValues.put(MmsSmsColumns.EXPORTED, 1); + contentValues.put(MmsSmsColumns.EXPORTED, MessageExportStatus.EXPORTED.getCode()); + + getWritableDatabase().update(table, contentValues, ID_WHERE, SqlUtil.buildArgs(messageId.getId())); + } + + public void markMessageExportFailed(@NonNull MessageId messageId) { + String table = messageId.isMms() ? MmsDatabase.TABLE_NAME : SmsDatabase.TABLE_NAME; + ContentValues contentValues = new ContentValues(1); + + contentValues.put(MmsSmsColumns.EXPORTED, MessageExportStatus.ERROR.getCode()); getWritableDatabase().update(table, contentValues, ID_WHERE, SqlUtil.buildArgs(messageId.getId())); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java index 6646a17ad..e5aaa2ab5 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java @@ -42,6 +42,7 @@ import org.thoughtcrime.securesms.database.documents.IdentityKeyMismatch; import org.thoughtcrime.securesms.database.documents.IdentityKeyMismatchSet; import org.thoughtcrime.securesms.database.documents.NetworkFailure; import org.thoughtcrime.securesms.database.model.GroupCallUpdateDetailsUtil; +import org.thoughtcrime.securesms.database.model.MessageExportStatus; import org.thoughtcrime.securesms.database.model.MessageId; import org.thoughtcrime.securesms.database.model.MessageRecord; import org.thoughtcrime.securesms.database.model.ParentStoryId; @@ -146,7 +147,8 @@ public class SmsDatabase extends MessageDatabase { "CREATE INDEX IF NOT EXISTS sms_date_sent_index ON " + TABLE_NAME + " (" + DATE_SENT + ", " + RECIPIENT_ID + ", " + THREAD_ID + ");", "CREATE INDEX IF NOT EXISTS sms_date_server_index ON " + TABLE_NAME + " (" + DATE_SERVER + ");", "CREATE INDEX IF NOT EXISTS sms_thread_date_index ON " + TABLE_NAME + " (" + THREAD_ID + ", " + DATE_RECEIVED + ");", - "CREATE INDEX IF NOT EXISTS sms_reactions_unread_index ON " + TABLE_NAME + " (" + REACTIONS_UNREAD + ");" + "CREATE INDEX IF NOT EXISTS sms_reactions_unread_index ON " + TABLE_NAME + " (" + REACTIONS_UNREAD + ");", + "CREATE INDEX IF NOT EXISTS sms_exported_index ON " + TABLE_NAME + " (" + EXPORTED + ");" }; private static final String[] MESSAGE_PROJECTION = new String[] { @@ -924,13 +926,13 @@ public class SmsDatabase extends MessageDatabase { beginTransaction(); try { List threadsToUpdate = new LinkedList<>(); - try (Cursor cursor = getReadableDatabase().query(TABLE_NAME, THREAD_ID_PROJECTION, EXPORTED + " = ?", SqlUtil.buildArgs(1), THREAD_ID, null, null, null)) { + try (Cursor cursor = getReadableDatabase().query(TABLE_NAME, THREAD_ID_PROJECTION, EXPORTED + " = ?", SqlUtil.buildArgs(MessageExportStatus.EXPORTED), THREAD_ID, null, null, null)) { while (cursor.moveToNext()) { threadsToUpdate.add(CursorUtil.requireLong(cursor, THREAD_ID)); } } - getWritableDatabase().delete(TABLE_NAME, EXPORTED + " = ?", SqlUtil.buildArgs(1)); + getWritableDatabase().delete(TABLE_NAME, EXPORTED + " = ?", SqlUtil.buildArgs(MessageExportStatus.EXPORTED)); for (final long threadId : threadsToUpdate) { SignalDatabase.threads().update(threadId, false); diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/helpers/SignalDatabaseMigrations.kt b/app/src/main/java/org/thoughtcrime/securesms/database/helpers/SignalDatabaseMigrations.kt index 284deaec8..940783443 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/helpers/SignalDatabaseMigrations.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/helpers/SignalDatabaseMigrations.kt @@ -14,13 +14,14 @@ import org.thoughtcrime.securesms.database.helpers.migration.V156_RecipientUnreg import org.thoughtcrime.securesms.database.helpers.migration.V157_RecipeintHiddenMigration import org.thoughtcrime.securesms.database.helpers.migration.V158_GroupsLastForceUpdateTimestampMigration import org.thoughtcrime.securesms.database.helpers.migration.V159_ThreadUnreadSelfMentionCount +import org.thoughtcrime.securesms.database.helpers.migration.V160_SmsMmsExportedIndexMigration /** * Contains all of the database migrations for [SignalDatabase]. Broken into a separate file for cleanliness. */ object SignalDatabaseMigrations { - const val DATABASE_VERSION = 159 + const val DATABASE_VERSION = 160 @JvmStatic fun migrate(context: Application, db: SQLiteDatabase, oldVersion: Int, newVersion: Int) { @@ -67,6 +68,10 @@ object SignalDatabaseMigrations { if (oldVersion < 159) { V159_ThreadUnreadSelfMentionCount.migrate(context, db, oldVersion, newVersion) } + + if (oldVersion < 160) { + V160_SmsMmsExportedIndexMigration.migrate(context, db, oldVersion, newVersion) + } } @JvmStatic diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/helpers/migration/V160_SmsMmsExportedIndexMigration.kt b/app/src/main/java/org/thoughtcrime/securesms/database/helpers/migration/V160_SmsMmsExportedIndexMigration.kt new file mode 100644 index 000000000..e93bc42c4 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/database/helpers/migration/V160_SmsMmsExportedIndexMigration.kt @@ -0,0 +1,11 @@ +package org.thoughtcrime.securesms.database.helpers.migration + +import android.app.Application +import net.zetetic.database.sqlcipher.SQLiteDatabase + +object V160_SmsMmsExportedIndexMigration : SignalDatabaseMigration { + override fun migrate(context: Application, db: SQLiteDatabase, oldVersion: Int, newVersion: Int) { + db.execSQL("CREATE INDEX IF NOT EXISTS sms_exported_index ON sms (exported)") + db.execSQL("CREATE INDEX IF NOT EXISTS mms_exported_index ON mms (exported)") + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/model/MessageExportStatus.kt b/app/src/main/java/org/thoughtcrime/securesms/database/model/MessageExportStatus.kt new file mode 100644 index 000000000..014277e69 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/database/model/MessageExportStatus.kt @@ -0,0 +1,32 @@ +package org.thoughtcrime.securesms.database.model + +import org.signal.core.util.DatabaseId +import org.signal.core.util.IntSerializer + +/** + * Export status for a message. + */ +enum class MessageExportStatus(val code: Int) : DatabaseId { + UNEXPORTED(0), + EXPORTED(1), + ERROR(-1); + + override fun serialize(): String { + return Serializer.serialize(this).toString() + } + + companion object Serializer : IntSerializer { + override fun serialize(data: MessageExportStatus): Int { + return data.code + } + + override fun deserialize(data: Int): MessageExportStatus { + return when (data) { + UNEXPORTED.code -> UNEXPORTED + EXPORTED.code -> EXPORTED + ERROR.code -> ERROR + else -> throw AssertionError("Unknown message export status: $data") + } + } + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/exporter/SignalSmsExportService.kt b/app/src/main/java/org/thoughtcrime/securesms/exporter/SignalSmsExportService.kt index 26c36a879..57e484352 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/exporter/SignalSmsExportService.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/exporter/SignalSmsExportService.kt @@ -19,6 +19,7 @@ import org.thoughtcrime.securesms.notifications.NotificationChannels import org.thoughtcrime.securesms.notifications.NotificationIds import org.thoughtcrime.securesms.notifications.v2.NotificationPendingIntentHelper import org.thoughtcrime.securesms.util.JsonUtils +import java.io.IOException import java.io.InputStream /** @@ -79,6 +80,11 @@ class SignalSmsExportService : SmsExportService() { ) } + override fun prepareForExport() { + SignalDatabase.sms.clearInsecureMessageExportedErrorStatus() + SignalDatabase.mms.clearInsecureMessageExportedErrorStatus() + } + override fun getUnexportedMessageCount(): Int { ensureReader() return reader!!.getCount() @@ -107,6 +113,8 @@ class SignalSmsExportService : SmsExportService() { SignalDatabase.mmsSms.updateMessageExportState(exportableMessage.getMessageId()) { it.toBuilder().setProgress(MessageExportState.Progress.INIT).build() } + + SignalDatabase.mmsSms.markMessageExportFailed(exportableMessage.getMessageId()) } override fun onMessageIdCreated(exportableMessage: ExportableMessage, messageId: Long) { @@ -153,6 +161,7 @@ class SignalSmsExportService : SmsExportService() { } } + @Throws(IOException::class) override fun getInputStream(part: ExportableMessage.Mms.Part): InputStream { return SignalDatabase.attachments.getAttachmentStream(JsonUtils.fromJson(part.contentId, AttachmentId::class.java), 0) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/exporter/flow/ExportSmsCompleteFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/exporter/flow/ExportSmsCompleteFragment.kt index 1b89359f1..cd4e96dcb 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/exporter/flow/ExportSmsCompleteFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/exporter/flow/ExportSmsCompleteFragment.kt @@ -14,12 +14,13 @@ import org.thoughtcrime.securesms.util.navigation.safeNavigate */ class ExportSmsCompleteFragment : Fragment(R.layout.export_sms_complete_fragment) { - val args: ExportSmsCompleteFragmentArgs by navArgs() + private val args: ExportSmsCompleteFragmentArgs by navArgs() override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - val binding = ExportSmsCompleteFragmentBinding.bind(view) + val exportSuccessCount = args.exportMessageCount - args.exportMessageFailureCount + val binding = ExportSmsCompleteFragmentBinding.bind(view) binding.exportCompleteNext.setOnClickListener { findNavController().safeNavigate(ExportSmsCompleteFragmentDirections.actionExportingSmsMessagesFragmentToChooseANewDefaultSmsAppFragment()) } - binding.exportCompleteStatus.text = resources.getQuantityString(R.plurals.ExportSmsCompleteFragment__d_of_d_messages_exported, args.exportMessageCount, args.exportMessageCount, args.exportMessageCount) + binding.exportCompleteStatus.text = resources.getQuantityString(R.plurals.ExportSmsCompleteFragment__d_of_d_messages_exported, args.exportMessageCount, exportSuccessCount, args.exportMessageCount) } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/exporter/flow/ExportYourSmsMessagesFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/exporter/flow/ExportYourSmsMessagesFragment.kt index dd4914f41..11a195938 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/exporter/flow/ExportYourSmsMessagesFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/exporter/flow/ExportYourSmsMessagesFragment.kt @@ -47,7 +47,7 @@ class ExportYourSmsMessagesFragment : Fragment(R.layout.export_your_sms_messages .observeOn(AndroidSchedulers.mainThread()) .subscribe { if (it is SmsExportProgress.Done) { - findNavController().safeNavigate(SmsExportDirections.actionDirectToExportSmsCompleteFragment(it.progress)) + findNavController().safeNavigate(SmsExportDirections.actionDirectToExportSmsCompleteFragment(it.errorCount, it.total)) } else if (it is SmsExportProgress.InProgress) { findNavController().safeNavigate(ExportYourSmsMessagesFragmentDirections.actionExportYourSmsMessagesFragmentToExportingSmsMessagesFragment()) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/exporter/flow/ExportingSmsMessagesFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/exporter/flow/ExportingSmsMessagesFragment.kt index c18685426..6ea9e050d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/exporter/flow/ExportingSmsMessagesFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/exporter/flow/ExportingSmsMessagesFragment.kt @@ -39,7 +39,7 @@ class ExportingSmsMessagesFragment : Fragment(R.layout.exporting_sms_messages_fr .observeOn(AndroidSchedulers.mainThread()) .subscribe { if (it is SmsExportProgress.Done) { - findNavController().safeNavigate(ExportingSmsMessagesFragmentDirections.actionExportingSmsMessagesFragmentToExportSmsCompleteFragment(it.progress)) + findNavController().safeNavigate(ExportingSmsMessagesFragmentDirections.actionExportingSmsMessagesFragmentToExportSmsCompleteFragment(it.total, it.errorCount)) } } } diff --git a/app/src/main/res/navigation/sms_export.xml b/app/src/main/res/navigation/sms_export.xml index e7d553985..c6cfa78cb 100644 --- a/app/src/main/res/navigation/sms_export.xml +++ b/app/src/main/res/navigation/sms_export.xml @@ -19,7 +19,11 @@ app:popExitAnim="@anim/fragment_close_exit" /> + app:destination="@id/setSignalAsDefaultSmsAppFragment" + app:enterAnim="@anim/fragment_open_enter" + app:exitAnim="@anim/fragment_open_exit" + app:popEnterAnim="@anim/fragment_close_enter" + app:popExitAnim="@anim/fragment_close_exit" /> - + + + app:popUpToInclusive="true" /> \ No newline at end of file diff --git a/core-util/src/main/java/org/signal/core/util/SQLiteDatabaseExtensions.kt b/core-util/src/main/java/org/signal/core/util/SQLiteDatabaseExtensions.kt index ead9993b5..b45e15751 100644 --- a/core-util/src/main/java/org/signal/core/util/SQLiteDatabaseExtensions.kt +++ b/core-util/src/main/java/org/signal/core/util/SQLiteDatabaseExtensions.kt @@ -229,6 +229,7 @@ class UpdateBuilderPart3( private val where: String, private val whereArgs: Array ) { + @JvmOverloads fun run(conflictStrategy: Int = SQLiteDatabase.CONFLICT_NONE): Int { return db.update(tableName, conflictStrategy, values, where, whereArgs) } diff --git a/core-util/src/main/java/org/signal/core/util/Serializer.kt b/core-util/src/main/java/org/signal/core/util/Serializer.kt index 6ab30e9b2..07d7ee476 100644 --- a/core-util/src/main/java/org/signal/core/util/Serializer.kt +++ b/core-util/src/main/java/org/signal/core/util/Serializer.kt @@ -10,6 +10,8 @@ interface Serializer { interface StringSerializer : Serializer +interface IntSerializer : Serializer + interface LongSerializer : Serializer interface ByteSerializer : Serializer diff --git a/core-util/src/main/java/org/signal/core/util/SqlUtil.kt b/core-util/src/main/java/org/signal/core/util/SqlUtil.kt index 3af308683..687b466a1 100644 --- a/core-util/src/main/java/org/signal/core/util/SqlUtil.kt +++ b/core-util/src/main/java/org/signal/core/util/SqlUtil.kt @@ -63,7 +63,7 @@ object SqlUtil { return objects.map { when (it) { null -> throw NullPointerException("Cannot have null arg!") - is DatabaseId -> (it as DatabaseId?)!!.serialize() + is DatabaseId -> it.serialize() else -> it.toString() } }.toTypedArray() diff --git a/sms-exporter/lib/src/main/java/org/signal/smsexporter/SmsExportProgress.kt b/sms-exporter/lib/src/main/java/org/signal/smsexporter/SmsExportProgress.kt index 001ce7e62..c23050a76 100644 --- a/sms-exporter/lib/src/main/java/org/signal/smsexporter/SmsExportProgress.kt +++ b/sms-exporter/lib/src/main/java/org/signal/smsexporter/SmsExportProgress.kt @@ -19,11 +19,12 @@ sealed class SmsExportProgress { */ data class InProgress( val progress: Int, + val errorCount: Int, val total: Int ) : SmsExportProgress() /** * All done. */ - data class Done(val progress: Int) : SmsExportProgress() + data class Done(val errorCount: Int, val total: Int) : SmsExportProgress() } diff --git a/sms-exporter/lib/src/main/java/org/signal/smsexporter/SmsExportService.kt b/sms-exporter/lib/src/main/java/org/signal/smsexporter/SmsExportService.kt index 86d53670a..093b73bfa 100644 --- a/sms-exporter/lib/src/main/java/org/signal/smsexporter/SmsExportService.kt +++ b/sms-exporter/lib/src/main/java/org/signal/smsexporter/SmsExportService.kt @@ -14,6 +14,7 @@ import org.signal.smsexporter.internal.mms.ExportMmsPartsUseCase import org.signal.smsexporter.internal.mms.ExportMmsRecipientsUseCase import org.signal.smsexporter.internal.mms.GetOrCreateMmsThreadIdsUseCase import org.signal.smsexporter.internal.sms.ExportSmsMessagesUseCase +import java.io.FileNotFoundException import java.io.InputStream import java.util.concurrent.Executor import java.util.concurrent.Executors @@ -60,31 +61,39 @@ abstract class SmsExportService : Service() { progressState.onNext(SmsExportProgress.Starting) var progress = 0 + var errorCount = 0 executor.execute { + prepareForExport() val totalCount = getUnexportedMessageCount() getUnexportedMessages().forEach { message -> val exportState = message.exportState if (exportState.progress != SmsExportState.Progress.COMPLETED) { - when (message) { + val successful = when (message) { is ExportableMessage.Sms<*> -> exportSms(exportState, message) is ExportableMessage.Mms<*> -> exportMms(exportState, message) } + if (!successful) { + errorCount++ + } + progress++ if (progress == 1 || progress.mod(100) == 0) { updateNotification(progress, totalCount) } - progressState.onNext(SmsExportProgress.InProgress(progress, totalCount)) + progressState.onNext(SmsExportProgress.InProgress(progress, errorCount, totalCount)) } } onExportPassCompleted() - progressState.onNext(SmsExportProgress.Done(progress)) + progressState.onNext(SmsExportProgress.Done(errorCount, progress)) getExportCompleteNotification()?.let { notification -> NotificationManagerCompat.from(this).notify(notification.id, notification.notification) } + Log.d(TAG, "Export complete") + stopForeground(true) isStarted = false } @@ -110,6 +119,9 @@ abstract class SmsExportService : Service() { */ protected abstract fun getExportCompleteNotification(): ExportNotification? + /** Called prior to starting export for any task setup that may need to occur. */ + protected open fun prepareForExport() = Unit + /** * Gets the total number of messages to process. This is only used for the notification and * progress events. @@ -192,17 +204,19 @@ abstract class SmsExportService : Service() { startForeground(exportNotification.id, exportNotification.notification) } - private fun exportSms(smsExportState: SmsExportState, sms: ExportableMessage.Sms<*>) { + private fun exportSms(smsExportState: SmsExportState, sms: ExportableMessage.Sms<*>): Boolean { onMessageExportStarted(sms) val mayAlreadyExist = smsExportState.progress == SmsExportState.Progress.STARTED - ExportSmsMessagesUseCase.execute(this, sms, mayAlreadyExist).either(onSuccess = { + return ExportSmsMessagesUseCase.execute(this, sms, mayAlreadyExist).either(onSuccess = { onMessageExportSucceeded(sms) + true }, onFailure = { onMessageExportFailed(sms) + false }) } - private fun exportMms(smsExportState: SmsExportState, mms: ExportableMessage.Mms<*>) { + private fun exportMms(smsExportState: SmsExportState, mms: ExportableMessage.Mms<*>): Boolean { onMessageExportStarted(mms) val threadIdOutput: GetOrCreateMmsThreadIdsUseCase.Output? = getThreadId(mms) val exportMmsOutput: ExportMmsMessagesUseCase.Output? = threadIdOutput?.let { exportMms(smsExportState, it) } @@ -210,15 +224,17 @@ abstract class SmsExportService : Service() { val writeMmsPartsOutput: List>? = exportMmsPartsOutput?.filterNotNull()?.map { writeAttachmentToDisk(smsExportState, it) } val exportMmsRecipients: List? = exportMmsOutput?.let { exportMmsRecipients(smsExportState, it) } - if (threadIdOutput != null && + return if (threadIdOutput != null && exportMmsOutput != null && exportMmsPartsOutput != null && !exportMmsPartsOutput.contains(null) && - writeMmsPartsOutput != null && writeMmsPartsOutput.all { it is Result.Success } && + writeMmsPartsOutput != null && writeMmsPartsOutput.all { it is Result.Success || (it is Result.Failure && (it.failure.cause ?: it.failure) is FileNotFoundException) } && exportMmsRecipients != null && !exportMmsRecipients.contains(null) ) { onMessageExportSucceeded(mms) + true } else { onMessageExportFailed(mms) + false } }