diff --git a/src/org/thoughtcrime/securesms/MessageDetailsActivity.java b/src/org/thoughtcrime/securesms/MessageDetailsActivity.java index c187af3566..3de789d491 100644 --- a/src/org/thoughtcrime/securesms/MessageDetailsActivity.java +++ b/src/org/thoughtcrime/securesms/MessageDetailsActivity.java @@ -23,7 +23,6 @@ import android.content.Context; import android.database.Cursor; import android.graphics.drawable.ColorDrawable; import android.os.AsyncTask; -import android.os.Build; import android.os.Bundle; import android.support.annotation.NonNull; import android.support.annotation.Nullable; @@ -48,6 +47,7 @@ import org.thoughtcrime.securesms.database.SmsDatabase; import org.thoughtcrime.securesms.database.loaders.MessageDetailsLoader; import org.thoughtcrime.securesms.database.model.MessageRecord; import org.thoughtcrime.securesms.logging.Log; +import org.thoughtcrime.securesms.loki.LokiMessageDatabase; import org.thoughtcrime.securesms.mms.GlideApp; import org.thoughtcrime.securesms.mms.GlideRequests; import org.thoughtcrime.securesms.notifications.MessageNotifier; @@ -158,10 +158,6 @@ public class MessageDetailsActivity extends PassphraseRequiredActionBarActivity private void setActionBarColor(MaterialColor color) { assert getSupportActionBar() != null; getSupportActionBar().setBackgroundDrawable(new ColorDrawable(color.toActionBarColor(this))); - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - // getWindow().setStatusBarColor(color.toStatusBarColor(this)); - } } @Override @@ -403,6 +399,12 @@ public class MessageDetailsActivity extends PassphraseRequiredActionBarActivity boolean isGroupNetworkFailure = messageRecord.isFailed() && !messageRecord.getNetworkFailures().isEmpty(); boolean isIndividualNetworkFailure = messageRecord.isFailed() && !isPushGroup && messageRecord.getIdentityKeyMismatches().isEmpty(); + LokiMessageDatabase lokiMessageDatabase = DatabaseFactory.getLokiMessageDatabase(getContext()); + String errorMessage = lokiMessageDatabase.getErrorMessage(messageRecord.id); + if (errorMessage != null) { + errorText.setText(errorMessage); + } + if (isGroupNetworkFailure || isIndividualNetworkFailure) { errorText.setVisibility(View.VISIBLE); resendButton.setVisibility(View.VISIBLE); diff --git a/src/org/thoughtcrime/securesms/conversation/ConversationActivity.java b/src/org/thoughtcrime/securesms/conversation/ConversationActivity.java index 628e8c85ba..8cb1f03570 100644 --- a/src/org/thoughtcrime/securesms/conversation/ConversationActivity.java +++ b/src/org/thoughtcrime/securesms/conversation/ConversationActivity.java @@ -2452,38 +2452,30 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity LokiThreadFriendRequestStatus friendRequestStatus = DatabaseFactory.getLokiThreadDatabase(context).getFriendRequestStatus(threadId); outgoingMessage.isFriendRequest = !isGroupConversation() && friendRequestStatus != LokiThreadFriendRequestStatus.FRIENDS; // Needed for stageOutgoingMessage(...) - Permissions.with(this) - .request(Manifest.permission.SEND_SMS, Manifest.permission.READ_SMS) - .ifNecessary(!isSecureText || forceSms) - .withPermanentDenialDialog(getString(R.string.ConversationActivity_signal_needs_sms_permission_in_order_to_send_an_sms)) - .onAllGranted(() -> { - if (clearComposeBox) { - inputPanel.clearQuote(); - attachmentManager.clear(glideRequests, false); - silentlySetComposeText(""); - } + if (clearComposeBox) { + inputPanel.clearQuote(); + attachmentManager.clear(glideRequests, false); + silentlySetComposeText(""); + } - final long id = fragment.stageOutgoingMessage(outgoingMessage); + final long id = fragment.stageOutgoingMessage(outgoingMessage); - new AsyncTask() { - @Override - protected Long doInBackground(Void... param) { - if (initiating) { - DatabaseFactory.getRecipientDatabase(context).setProfileSharing(recipient, true); - } + new AsyncTask() { + @Override + protected Long doInBackground(Void... param) { + if (initiating) { + DatabaseFactory.getRecipientDatabase(context).setProfileSharing(recipient, true); + } - return MessageSender.send(context, outgoingMessage, threadId, forceSms, () -> fragment.releaseOutgoingMessage(id)); - } + return MessageSender.send(context, outgoingMessage, threadId, forceSms, () -> fragment.releaseOutgoingMessage(id)); + } - @Override - protected void onPostExecute(Long result) { - sendComplete(result); - future.set(null); - } - }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); - }) - .onAnyDenied(() -> future.set(null)) - .execute(); + @Override + protected void onPostExecute(Long result) { + sendComplete(result); + future.set(null); + } + }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); return future; } @@ -2512,32 +2504,24 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity LokiThreadFriendRequestStatus friendRequestStatus = DatabaseFactory.getLokiThreadDatabase(context).getFriendRequestStatus(threadId); message.isFriendRequest = !isGroupConversation() && friendRequestStatus != LokiThreadFriendRequestStatus.FRIENDS; // Needed for stageOutgoingMessage(...) - Permissions.with(this) - .request(Manifest.permission.SEND_SMS) - .ifNecessary(forceSms || !isSecureText) - .withPermanentDenialDialog(getString(R.string.ConversationActivity_signal_needs_sms_permission_in_order_to_send_an_sms)) - .onAllGranted(() -> { - silentlySetComposeText(""); - final long id = fragment.stageOutgoingMessage(message); + silentlySetComposeText(""); + final long id = fragment.stageOutgoingMessage(message); - new AsyncTask() { - @Override - protected Long doInBackground(OutgoingTextMessage... messages) { - if (initiatingConversation) { - DatabaseFactory.getRecipientDatabase(context).setProfileSharing(recipient, true); - } + new AsyncTask() { + @Override + protected Long doInBackground(OutgoingTextMessage... messages) { + if (initiatingConversation) { + DatabaseFactory.getRecipientDatabase(context).setProfileSharing(recipient, true); + } - return MessageSender.send(context, messages[0], threadId, forceSms, () -> fragment.releaseOutgoingMessage(id)); - } + return MessageSender.send(context, messages[0], threadId, forceSms, () -> fragment.releaseOutgoingMessage(id)); + } - @Override - protected void onPostExecute(Long result) { - sendComplete(result); - } - }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, message); - - }) - .execute(); + @Override + protected void onPostExecute(Long result) { + sendComplete(result); + } + }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, message); } private void showDefaultSmsPrompt() { diff --git a/src/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java b/src/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java index 5d06fb1bc6..38ff02ee8a 100644 --- a/src/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java +++ b/src/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java @@ -80,8 +80,9 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper { private static final int lokiV4 = 25; private static final int lokiV5 = 26; private static final int lokiV6 = 27; + private static final int lokiV7 = 28; - private static final int DATABASE_VERSION = lokiV6; // Loki - onUpgrade(...) must be updated to use Loki version numbers if Signal makes any database changes + private static final int DATABASE_VERSION = lokiV7; // Loki - onUpgrade(...) must be updated to use Loki version numbers if Signal makes any database changes private static final String DATABASE_NAME = "signal.db"; private final Context context; @@ -141,6 +142,7 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper { db.execSQL(LokiPreKeyRecordDatabase.getCreateTableCommand()); db.execSQL(LokiMessageDatabase.getCreateMessageFriendRequestTableCommand()); db.execSQL(LokiMessageDatabase.getCreateMessageToThreadMappingTableCommand()); + db.execSQL(LokiMessageDatabase.getCreateErrorMessageTableCommand()); db.execSQL(LokiThreadDatabase.getCreateFriendRequestTableCommand()); db.execSQL(LokiThreadDatabase.getCreateSessionResetTableCommand()); db.execSQL(LokiThreadDatabase.getCreatePublicChatTableCommand()); @@ -570,6 +572,10 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper { db.execSQL("ALTER TABLE groups ADD COLUMN admins TEXT"); } + if (oldVersion < lokiV7) { + db.execSQL(LokiMessageDatabase.getCreateErrorMessageTableCommand()); + } + db.setTransactionSuccessful(); } finally { db.endTransaction(); diff --git a/src/org/thoughtcrime/securesms/jobs/PushMediaSendJob.java b/src/org/thoughtcrime/securesms/jobs/PushMediaSendJob.java index 09bab54caa..088a426e96 100644 --- a/src/org/thoughtcrime/securesms/jobs/PushMediaSendJob.java +++ b/src/org/thoughtcrime/securesms/jobs/PushMediaSendJob.java @@ -22,6 +22,7 @@ import org.thoughtcrime.securesms.jobmanager.Data; import org.thoughtcrime.securesms.jobmanager.Job; import org.thoughtcrime.securesms.jobmanager.JobManager; import org.thoughtcrime.securesms.logging.Log; +import org.thoughtcrime.securesms.loki.LokiMessageDatabase; import org.thoughtcrime.securesms.mms.MmsException; import org.thoughtcrime.securesms.mms.OutgoingMediaMessage; import org.thoughtcrime.securesms.recipients.Recipient; @@ -35,6 +36,7 @@ import org.whispersystems.libsignal.util.guava.Optional; import org.whispersystems.signalservice.api.SignalServiceMessageSender; import org.whispersystems.signalservice.api.crypto.UnidentifiedAccessPair; import org.whispersystems.signalservice.api.crypto.UntrustedIdentityException; +import org.whispersystems.signalservice.api.messages.SendMessageResult; import org.whispersystems.signalservice.api.messages.SignalServiceAttachment; import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage; import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage.Preview; @@ -42,6 +44,7 @@ import org.whispersystems.signalservice.api.messages.multidevice.SignalServiceSy import org.whispersystems.signalservice.api.messages.shared.SharedContact; import org.whispersystems.signalservice.api.push.SignalServiceAddress; import org.whispersystems.signalservice.api.push.exceptions.UnregisteredUserException; +import org.whispersystems.signalservice.loki.api.LokiAPI; import org.whispersystems.signalservice.loki.api.LokiDeviceLinkUtilities; import org.whispersystems.signalservice.loki.messaging.LokiSyncMessage; import org.whispersystems.signalservice.loki.utilities.PromiseUtil; @@ -229,13 +232,17 @@ public class PushMediaSendJob extends PushSendJob implements InjectableType { database.addMismatchedIdentity(messageId, Address.fromSerialized(uie.getE164Number()), uie.getIdentityKey()); database.markAsSentFailed(messageId); } + } catch (LokiAPI.Error e) { + Log.d("Loki", "Couldn't send message due to error: " + e.getDescription()); + if (messageId < 0) { return; } + LokiMessageDatabase lokiMessageDatabase = DatabaseFactory.getLokiMessageDatabase(context); + lokiMessageDatabase.setErrorMessage(messageId, e.getDescription()); + database.markAsSentFailed(messageId); } } @Override public boolean onShouldRetry(@NonNull Exception exception) { - // Loki - Disable since we have our own retrying - // if (exception instanceof RetryLaterException) return true; return false; } @@ -249,7 +256,7 @@ public class PushMediaSendJob extends PushSendJob implements InjectableType { private boolean deliver(OutgoingMediaMessage message) throws RetryLaterException, InsecureFallbackApprovalException, UntrustedIdentityException, - UndeliverableMessageException + UndeliverableMessageException, LokiAPI.Error { try { Recipient recipient = Recipient.from(context, destination, false); @@ -295,7 +302,12 @@ public class PushMediaSendJob extends PushSendJob implements InjectableType { // We also need to use the original message ID and not -1 syncMessage = new LokiSyncMessage(masterAddress, templateMessageId); } - return messageSender.sendMessage(messageId, address, UnidentifiedAccessUtil.getAccessFor(context, recipient), mediaMessage, Optional.fromNullable(syncMessage)).getSuccess().isUnidentified(); + SendMessageResult result = messageSender.sendMessage(messageId, address, UnidentifiedAccessUtil.getAccessFor(context, recipient), mediaMessage, Optional.fromNullable(syncMessage)); + if (result.getLokiAPIError() != null) { + throw result.getLokiAPIError(); + } else { + return result.getSuccess().isUnidentified(); + } } } catch (UnregisteredUserException e) { warn(TAG, e); diff --git a/src/org/thoughtcrime/securesms/jobs/PushTextSendJob.java b/src/org/thoughtcrime/securesms/jobs/PushTextSendJob.java index 7c0f04834a..6d44fc9b5c 100644 --- a/src/org/thoughtcrime/securesms/jobs/PushTextSendJob.java +++ b/src/org/thoughtcrime/securesms/jobs/PushTextSendJob.java @@ -1,6 +1,7 @@ package org.thoughtcrime.securesms.jobs; import android.support.annotation.NonNull; +import android.util.Log; import org.thoughtcrime.securesms.ApplicationContext; import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil; @@ -14,6 +15,7 @@ import org.thoughtcrime.securesms.database.model.SmsMessageRecord; import org.thoughtcrime.securesms.dependencies.InjectableType; import org.thoughtcrime.securesms.jobmanager.Data; import org.thoughtcrime.securesms.jobmanager.Job; +import org.thoughtcrime.securesms.loki.LokiMessageDatabase; import org.thoughtcrime.securesms.notifications.MessageNotifier; import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.service.ExpiringMessageManager; @@ -25,10 +27,12 @@ import org.whispersystems.libsignal.util.guava.Optional; import org.whispersystems.signalservice.api.SignalServiceMessageSender; import org.whispersystems.signalservice.api.crypto.UnidentifiedAccessPair; import org.whispersystems.signalservice.api.crypto.UntrustedIdentityException; +import org.whispersystems.signalservice.api.messages.SendMessageResult; import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage; import org.whispersystems.signalservice.api.messages.multidevice.SignalServiceSyncMessage; import org.whispersystems.signalservice.api.push.SignalServiceAddress; import org.whispersystems.signalservice.api.push.exceptions.UnregisteredUserException; +import org.whispersystems.signalservice.loki.api.LokiAPI; import org.whispersystems.signalservice.loki.api.LokiDeviceLinkUtilities; import org.whispersystems.signalservice.loki.messaging.LokiSyncMessage; import org.whispersystems.signalservice.loki.utilities.PromiseUtil; @@ -170,6 +174,12 @@ public class PushTextSendJob extends PushSendJob implements InjectableType { database.markAsSentFailed(record.getId()); database.markAsPush(record.getId()); } + } catch (LokiAPI.Error e) { + Log.d("Loki", "Couldn't send message due to error: " + e.getDescription()); + if (messageId < 0) { return; } + LokiMessageDatabase lokiMessageDatabase = DatabaseFactory.getLokiMessageDatabase(context); + lokiMessageDatabase.setErrorMessage(record.getId(), e.getDescription()); + database.markAsSentFailed(record.getId()); } } @@ -196,7 +206,7 @@ public class PushTextSendJob extends PushSendJob implements InjectableType { } private boolean deliver(SmsMessageRecord message) - throws UntrustedIdentityException, InsecureFallbackApprovalException, RetryLaterException + throws UntrustedIdentityException, InsecureFallbackApprovalException, RetryLaterException, LokiAPI.Error { try { // rotateSenderCertificateIfNecessary(); @@ -241,7 +251,12 @@ public class PushTextSendJob extends PushSendJob implements InjectableType { // We also need to use the original message ID and not -1 syncMessage = new LokiSyncMessage(masterAddress, templateMessageId); } - return messageSender.sendMessage(messageId, address, unidentifiedAccess, textSecureMessage, Optional.fromNullable(syncMessage)).getSuccess().isUnidentified(); + SendMessageResult result = messageSender.sendMessage(messageId, address, unidentifiedAccess, textSecureMessage, Optional.fromNullable(syncMessage)); + if (result.getLokiAPIError() != null) { + throw result.getLokiAPIError(); + } else { + return result.getSuccess().isUnidentified(); + } } } catch (UnregisteredUserException e) { warn(TAG, "Failure", e); diff --git a/src/org/thoughtcrime/securesms/loki/LokiMessageDatabase.kt b/src/org/thoughtcrime/securesms/loki/LokiMessageDatabase.kt index 1c98d62612..7195e4a2ee 100644 --- a/src/org/thoughtcrime/securesms/loki/LokiMessageDatabase.kt +++ b/src/org/thoughtcrime/securesms/loki/LokiMessageDatabase.kt @@ -8,6 +8,7 @@ import org.thoughtcrime.securesms.database.DatabaseFactory import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper import org.thoughtcrime.securesms.loki.redesign.utilities.get import org.thoughtcrime.securesms.loki.redesign.utilities.getInt +import org.thoughtcrime.securesms.loki.redesign.utilities.getString import org.thoughtcrime.securesms.loki.redesign.utilities.insertOrUpdate import org.whispersystems.signalservice.loki.messaging.LokiMessageDatabaseProtocol import org.whispersystems.signalservice.loki.messaging.LokiMessageFriendRequestStatus @@ -17,12 +18,15 @@ class LokiMessageDatabase(context: Context, helper: SQLCipherOpenHelper) : Datab companion object { private val messageFriendRequestTableName = "loki_message_friend_request_database" private val messageThreadMappingTableName = "loki_message_thread_mapping_database" + private val errorMessageTableName = "loki_error_message_database" private val messageID = "message_id" private val serverID = "server_id" private val friendRequestStatus = "friend_request_status" private val threadID = "thread_id" + private val errorMessage = "error_message" @JvmStatic val createMessageFriendRequestTableCommand = "CREATE TABLE $messageFriendRequestTableName ($messageID INTEGER PRIMARY KEY, $serverID INTEGER DEFAULT 0, $friendRequestStatus INTEGER DEFAULT 0);" @JvmStatic val createMessageToThreadMappingTableCommand = "CREATE TABLE IF NOT EXISTS $messageThreadMappingTableName ($messageID INTEGER PRIMARY KEY, $threadID INTEGER);" + @JvmStatic val createErrorMessageTableCommand = "CREATE TABLE IF NOT EXISTS $errorMessageTableName ($messageID INTEGER PRIMARY KEY, $errorMessage STRING);" } override fun getQuoteServerID(quoteID: Long, quoteeHexEncodedPublicKey: String): Long? { @@ -92,4 +96,19 @@ class LokiMessageDatabase(context: Context, helper: SQLCipherOpenHelper) : Datab fun isFriendRequest(messageID: Long): Boolean { return getFriendRequestStatus(messageID) != LokiMessageFriendRequestStatus.NONE } + + fun getErrorMessage(messageID: Long): String? { + val database = databaseHelper.readableDatabase + return database.get(errorMessageTableName, "${Companion.messageID} = ?", arrayOf( messageID.toString() )) { cursor -> + cursor.getString(errorMessage) + } + } + + fun setErrorMessage(messageID: Long, errorMessage: String) { + val database = databaseHelper.writableDatabase + val contentValues = ContentValues(2) + contentValues.put(Companion.messageID, messageID) + contentValues.put(Companion.errorMessage, errorMessage) + database.insertOrUpdate(errorMessageTableName, contentValues, "${Companion.messageID} = ?", arrayOf( messageID.toString() )) + } } \ No newline at end of file diff --git a/src/org/thoughtcrime/securesms/sms/MessageSender.java b/src/org/thoughtcrime/securesms/sms/MessageSender.java index 7a72ac68d2..438db55116 100644 --- a/src/org/thoughtcrime/securesms/sms/MessageSender.java +++ b/src/org/thoughtcrime/securesms/sms/MessageSender.java @@ -284,10 +284,8 @@ public class MessageSender { sendLocalMediaSelf(context, messageId); } else if (isGroupPushSend(recipient)) { sendGroupPush(context, recipient, messageId, null); - } else if (!forceSms && isPushMediaSend(context, recipient)) { - sendMediaPush(context, recipient, messageId); } else { - sendMms(context, messageId); + sendMediaPush(context, recipient, messageId); } } @@ -297,10 +295,8 @@ public class MessageSender { { if (isLocalSelfSend(context, recipient, forceSms)) { sendLocalTextSelf(context, messageId); - } else if (!forceSms && isPushTextSend(context, recipient, keyExchange)) { - sendTextPush(context, recipient, messageId); } else { - sendSms(context, recipient, messageId); + sendTextPush(context, recipient, messageId); } }