diff --git a/res/layout/conversation_activity.xml b/res/layout/conversation_activity.xml index ef3ca408a1..11b797760d 100644 --- a/res/layout/conversation_activity.xml +++ b/res/layout/conversation_activity.xml @@ -51,12 +51,10 @@ android:inflatedId="@+id/unverified_banner" android:layout="@layout/conversation_activity_unverified_banner_stub" /> - + android:layout_height="wrap_content" /> - - @@ -48,7 +33,8 @@ android:id="@+id/dismissButton" style="@style/Widget.AppCompat.Button.Borderless" android:layout_width="wrap_content" - android:layout_height="36dp" + android:layout_height="wrap_content" + android:gravity="center" android:text="@string/session_restore_banner_dismiss_button_title" /> diff --git a/src/org/thoughtcrime/securesms/conversation/ConversationActivity.java b/src/org/thoughtcrime/securesms/conversation/ConversationActivity.java index 8de7cc8653..16641984dd 100644 --- a/src/org/thoughtcrime/securesms/conversation/ConversationActivity.java +++ b/src/org/thoughtcrime/securesms/conversation/ConversationActivity.java @@ -359,7 +359,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity private boolean isFriendsWithAnyDevice = false; // Restoration - protected Stub sessionRestoreBannerView; + protected SessionRestoreBannerView sessionRestoreBannerView; @Override protected void onPreCreate() { @@ -428,6 +428,17 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity } }); + sessionRestoreBannerView.setOnRestore(() -> { + this.restoreSession(); + return Unit.INSTANCE; + }); + sessionRestoreBannerView.setOnDismiss(() -> { + // TODO: Maybe silence for x minutes? + DatabaseFactory.getLokiThreadDatabase(ConversationActivity.this).removeAllSessionRestoreDevices(threadId); + updateSessionRestoreBanner(); + return Unit.INSTANCE; + }); + LokiAPIUtilities.INSTANCE.populateUserHexEncodedPublicKeyCacheIfNeeded(threadId, this); if (this.recipient.isGroupRecipient()) { @@ -496,6 +507,8 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity DatabaseFactory.getLokiThreadDatabase(this).setDelegate(this); updateInputPanel(); + updateSessionRestoreBanner(); + Log.i(TAG, "onResume() Finished: " + (System.currentTimeMillis() - getIntent().getLongExtra(TIMING_EXTRA, 0))); } @@ -1496,11 +1509,11 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity protected void updateSessionRestoreBanner() { Set devices = DatabaseFactory.getLokiThreadDatabase(this).getSessionRestoreDevices(threadId); - SessionRestoreBannerView view = sessionRestoreBannerView.get(); if (devices.size() > 0) { - view.show(); + sessionRestoreBannerView.update(recipient); + sessionRestoreBannerView.show(); } else { - view.hide(); + sessionRestoreBannerView.hide(); } } @@ -1600,18 +1613,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity inputPanel = ViewUtil.findById(this, R.id.bottom_panel); searchNav = ViewUtil.findById(this, R.id.conversation_search_nav); mentionCandidateSelectionView = ViewUtil.findById(this, R.id.userSelectionView); - sessionRestoreBannerView = ViewUtil.findStubById(this, R.id.session_restore_banner_stub); - sessionRestoreBannerView.get().setRecipient(recipient); - sessionRestoreBannerView.get().setOnRestore(() -> { - this.restoreSession(); - return Unit.INSTANCE; - }); - sessionRestoreBannerView.get().setOnDismiss(() -> { - // TODO: Maybe silence for x minutes? - // TODO: Remove devices? - sessionRestoreBannerView.get().hide(); - return Unit.INSTANCE; - }); + sessionRestoreBannerView = ViewUtil.findById(this, R.id.sessionRestoreBannerView); ImageButton quickCameraToggle = ViewUtil.findById(this, R.id.quick_camera_toggle); ImageButton inlineAttachmentButton = ViewUtil.findById(this, R.id.inline_attachment_button); @@ -2233,7 +2235,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity @Override public void handleSessionRestoreDevicesChanged(long threadId) { if (threadId == this.threadId) { - updateSessionRestoreBanner(); + runOnUiThread(this::updateSessionRestoreBanner); } } diff --git a/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java b/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java index 06b902f3a3..d140c8bf8d 100644 --- a/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java +++ b/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java @@ -58,6 +58,7 @@ import org.thoughtcrime.securesms.database.StickerDatabase; import org.thoughtcrime.securesms.database.ThreadDatabase; import org.thoughtcrime.securesms.database.model.MessageRecord; import org.thoughtcrime.securesms.database.model.MmsMessageRecord; +import org.thoughtcrime.securesms.database.model.SmsMessageRecord; import org.thoughtcrime.securesms.database.model.StickerRecord; import org.thoughtcrime.securesms.dependencies.InjectableType; import org.thoughtcrime.securesms.groups.GroupMessageProcessor; @@ -275,11 +276,9 @@ public class PushDecryptJob extends BaseJob implements InjectableType { LokiServiceCipher cipher = new LokiServiceCipher(localAddress, axolotlStore, lokiThreadDatabase, lokiPreKeyRecordDatabase, UnidentifiedAccessUtil.getCertificateValidator()); // Loki - Handle session reset logic - /* if (!envelope.isFriendRequest() && cipher.getSessionStatus(envelope) == null && envelope.isPreKeySignalMessage()) { cipher.validateBackgroundMessage(envelope, envelope.getContent()); } - */ // Loki - Ignore any friend requests that we got before restoration if (envelope.isFriendRequest() && envelope.getTimestamp() < TextSecurePreferences.getRestorationTime(context)) { @@ -1385,17 +1384,39 @@ public class PushDecryptJob extends BaseJob implements InjectableType { } } + private SmsMessageRecord getLastMessage(String sender) { + try { + SmsDatabase smsDatabase = DatabaseFactory.getSmsDatabase(context); + Recipient recipient = Recipient.from(context, Address.fromSerialized(sender), false); + long threadID = DatabaseFactory.getThreadDatabase(context).getThreadIdIfExistsFor(recipient); + if (threadID < 0) { + return null; + } + int messageCount = smsDatabase.getMessageCountForThread(threadID); + if (messageCount <= 0) { + return null; + } + long lastMessageID = smsDatabase.getIDForMessageAtIndex(threadID, messageCount - 1); + return smsDatabase.getMessage(lastMessageID); + } catch (Exception e) { + return null; + } + } + private void handleCorruptMessage(@NonNull String sender, int senderDevice, long timestamp, @NonNull Optional smsMessageId) { SmsDatabase smsDatabase = DatabaseFactory.getSmsDatabase(context); if (!smsMessageId.isPresent()) { - Optional insertResult = insertPlaceholder(sender, senderDevice, timestamp); + SmsMessageRecord lastMessage = getLastMessage(sender); + if (lastMessage == null || !SmsDatabase.Types.isFailedDecryptType(lastMessage.getType())) { + Optional insertResult = insertPlaceholder(sender, senderDevice, timestamp); - if (insertResult.isPresent()) { - smsDatabase.markAsDecryptFailed(insertResult.get().getMessageId()); - // MessageNotifier.updateNotification(context, insertResult.get().getThreadId()); + if (insertResult.isPresent()) { + smsDatabase.markAsDecryptFailed(insertResult.get().getMessageId()); + MessageNotifier.updateNotification(context, insertResult.get().getThreadId()); + } } } else { smsDatabase.markAsDecryptFailed(smsMessageId.get()); @@ -1409,11 +1430,14 @@ public class PushDecryptJob extends BaseJob implements InjectableType { SmsDatabase smsDatabase = DatabaseFactory.getSmsDatabase(context); if (!smsMessageId.isPresent()) { - Optional insertResult = insertPlaceholder(sender, senderDevice, timestamp); + SmsMessageRecord lastMessage = getLastMessage(sender); + if (lastMessage == null || !SmsDatabase.Types.isNoRemoteSessionType(lastMessage.getType())) { + Optional insertResult = insertPlaceholder(sender, senderDevice, timestamp); - if (insertResult.isPresent()) { - smsDatabase.markAsNoSession(insertResult.get().getMessageId()); - // MessageNotifier.updateNotification(context, insertResult.get().getThreadId()); + if (insertResult.isPresent()) { + smsDatabase.markAsNoSession(insertResult.get().getMessageId()); + MessageNotifier.updateNotification(context, insertResult.get().getThreadId()); + } } } else { smsDatabase.markAsNoSession(smsMessageId.get()); diff --git a/src/org/thoughtcrime/securesms/loki/LokiThreadDatabase.kt b/src/org/thoughtcrime/securesms/loki/LokiThreadDatabase.kt index d98ca1603f..dfc732474c 100644 --- a/src/org/thoughtcrime/securesms/loki/LokiThreadDatabase.kt +++ b/src/org/thoughtcrime/securesms/loki/LokiThreadDatabase.kt @@ -14,6 +14,7 @@ import org.whispersystems.signalservice.loki.api.LokiPublicChat import org.whispersystems.signalservice.loki.messaging.LokiThreadDatabaseProtocol import org.whispersystems.signalservice.loki.messaging.LokiThreadFriendRequestStatus import org.whispersystems.signalservice.loki.messaging.LokiThreadSessionResetStatus +import org.whispersystems.signalservice.loki.utilities.PublicKeyValidation class LokiThreadDatabase(context: Context, helper: SQLCipherOpenHelper) : Database(context, helper), LokiThreadDatabaseProtocol { var delegate: LokiThreadDatabaseDelegate? = null @@ -152,7 +153,10 @@ class LokiThreadDatabase(context: Context, helper: SQLCipherOpenHelper) : Databa } fun getSessionRestoreDevices(threadID: Long): Set { - return TextSecurePreferences.getStringPreference(context, "session_restore_devices_$threadID", "").split(",").toSet() + return TextSecurePreferences.getStringPreference(context, "session_restore_devices_$threadID", "") + .split(",") + .filter { PublicKeyValidation.isValid(it) } + .toSet() } fun removeAllSessionRestoreDevices(threadID: Long) { diff --git a/src/org/thoughtcrime/securesms/loki/SessionRestoreBannerView.kt b/src/org/thoughtcrime/securesms/loki/SessionRestoreBannerView.kt index 862360cdba..c14c64c3d4 100644 --- a/src/org/thoughtcrime/securesms/loki/SessionRestoreBannerView.kt +++ b/src/org/thoughtcrime/securesms/loki/SessionRestoreBannerView.kt @@ -1,38 +1,27 @@ package org.thoughtcrime.securesms.loki -import org.thoughtcrime.securesms.components.reminder.Reminder - -import android.annotation.TargetApi import android.content.Context -import android.os.Build.VERSION_CODES -import android.text.TextUtils import android.util.AttributeSet import android.view.LayoutInflater import android.view.View -import android.view.ViewGroup -import android.widget.ImageButton import android.widget.LinearLayout -import android.widget.TextView import kotlinx.android.synthetic.main.session_restore_banner.view.* import network.loki.messenger.R import org.thoughtcrime.securesms.recipients.Recipient -import org.thoughtcrime.securesms.util.ViewUtil /** * View to display actionable reminders to the user */ -class SessionRestoreBannerView private constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : LinearLayout(context, attrs, defStyleAttr) { - private var container: ViewGroup? = null - private var closeButton: ImageButton? = null - private var title: TextView? = null - private var text: TextView? = null +class SessionRestoreBannerView(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : LinearLayout(context, attrs, defStyleAttr) { lateinit var recipient: Recipient var onDismiss: (() -> Unit)? = null var onRestore: (() -> Unit)? = null + // region Initialization + constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0) constructor(context: Context) : this(context, null) - private constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0) + // endregion init { LayoutInflater.from(context).inflate(R.layout.session_restore_banner, this, true) @@ -46,10 +35,10 @@ class SessionRestoreBannerView private constructor(context: Context, attrs: Attr } fun show() { - container!!.visibility = View.VISIBLE; + sessionRestoreBanner.visibility = View.VISIBLE } fun hide() { - container!!.visibility = View.GONE + sessionRestoreBanner.visibility = View.GONE } }