Fix infinite export loop and improve general error handling.

fork-5.53.8
Cody Henthorne 2022-10-18 16:06:37 -04:00 zatwierdzone przez GitHub
rodzic 0d715d2c18
commit 7fccbd44c0
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: 4AEE18F83AFDEB23
18 zmienionych plików z 155 dodań i 30 usunięć

Wyświetl plik

@ -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;

Wyświetl plik

@ -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();

Wyświetl plik

@ -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<Long> 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);

Wyświetl plik

@ -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()));
}

Wyświetl plik

@ -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<Long> 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);

Wyświetl plik

@ -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

Wyświetl plik

@ -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)")
}
}

Wyświetl plik

@ -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<MessageExportStatus> {
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")
}
}
}
}

Wyświetl plik

@ -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)
}

Wyświetl plik

@ -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)
}
}

Wyświetl plik

@ -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())
}

Wyświetl plik

@ -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))
}
}
}

Wyświetl plik

@ -19,7 +19,11 @@
app:popExitAnim="@anim/fragment_close_exit" />
<action
android:id="@+id/action_exportYourSmsMessagesFragment_to_setSignalAsDefaultSmsAppFragment"
app:destination="@id/setSignalAsDefaultSmsAppFragment" />
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" />
</fragment>
<fragment
@ -58,7 +62,12 @@
android:name="org.thoughtcrime.securesms.exporter.flow.ExportSmsCompleteFragment"
tools:layout="@layout/export_sms_complete_fragment">
<argument android:name="export_message_count"
<argument
android:name="export_message_count"
app:argType="integer" />
<argument
android:name="export_message_failure_count"
app:argType="integer" />
<action
@ -85,6 +94,6 @@
app:popEnterAnim="@anim/fragment_close_enter"
app:popExitAnim="@anim/fragment_close_exit"
app:popUpTo="@+id/exportYourSmsMessagesFragment"
app:popUpToInclusive="true"/>
app:popUpToInclusive="true" />
</navigation>

Wyświetl plik

@ -229,6 +229,7 @@ class UpdateBuilderPart3(
private val where: String,
private val whereArgs: Array<String>
) {
@JvmOverloads
fun run(conflictStrategy: Int = SQLiteDatabase.CONFLICT_NONE): Int {
return db.update(tableName, conflictStrategy, values, where, whereArgs)
}

Wyświetl plik

@ -10,6 +10,8 @@ interface Serializer<T, R> {
interface StringSerializer<T> : Serializer<T, String>
interface IntSerializer<T> : Serializer<T, Int>
interface LongSerializer<T> : Serializer<T, Long>
interface ByteSerializer<T> : Serializer<T, ByteArray>

Wyświetl plik

@ -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()

Wyświetl plik

@ -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()
}

Wyświetl plik

@ -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<Result<Unit, Throwable>>? = exportMmsPartsOutput?.filterNotNull()?.map { writeAttachmentToDisk(smsExportState, it) }
val exportMmsRecipients: List<Unit?>? = 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
}
}