Move session restore prompt from message level to conversation level.

This commit is contained in:
Mikunj 2019-12-06 13:00:08 +11:00
parent 0caeb3a109
commit 97ffea040f
20 changed files with 232 additions and 124 deletions

View File

@ -51,6 +51,13 @@
android:inflatedId="@+id/unverified_banner" android:inflatedId="@+id/unverified_banner"
android:layout="@layout/conversation_activity_unverified_banner_stub" /> android:layout="@layout/conversation_activity_unverified_banner_stub" />
<ViewStub
android:id="@+id/session_restore_banner_stub"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inflatedId="@+id/session_restore_banner"
android:layout="@layout/conversation_activity_unverified_banner_stub" />
<ViewStub <ViewStub
android:id="@+id/reminder_stub" android:id="@+id/reminder_stub"
android:layout_width="match_parent" android:layout_width="match_parent"

View File

@ -74,15 +74,6 @@
android:textColor="?conversation_item_update_text_color" android:textColor="?conversation_item_update_text_color"
tools:text="30 min ago" /> tools:text="30 min ago" />
<Button
android:id="@+id/conversation_update_button"
style="@style/Button.Borderless"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="Button"
android:visibility="gone" />
</LinearLayout> </LinearLayout>
</org.thoughtcrime.securesms.conversation.ConversationUpdateItem> </org.thoughtcrime.securesms.conversation.ConversationUpdateItem>

View File

@ -0,0 +1,66 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/session_restore_banner"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/core_grey_60"
android:focusable="true"
android:nextFocusDown="@+id/cancel"
android:orientation="vertical"
android:paddingStart="8dp"
android:paddingTop="24dp"
android:paddingEnd="8dp"
android:paddingBottom="8dp"
android:visibility="gone"
tools:visibility="visible">
<TextView
android:id="@+id/restoreTitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
android:layout_marginBottom="4dp"
android:textColor="@color/white"
android:textSize="18sp"
tools:text="@string/session_restore_banner_title" />
<TextView
android:id="@+id/restoreText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
android:fontFamily="sans-serif-light"
android:textColor="@color/white"
android:textSize="16sp"
tools:text="@string/session_restore_banner_message" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:gravity="right"
android:orientation="horizontal">
<Button
android:id="@+id/dismissButton"
style="@style/Widget.AppCompat.Button.Borderless"
android:layout_width="wrap_content"
android:layout_height="36dp"
android:text="@string/session_restore_banner_dismiss_button_title" />
<android.support.v4.widget.Space
android:layout_width="8dp"
android:layout_height="wrap_content" />
<Button
android:id="@+id/restoreButton"
style="@style/Button.Borderless"
android:layout_width="wrap_content"
android:layout_height="36dp"
android:text="@string/session_restore_banner_restore_button_title" />
</LinearLayout>
</LinearLayout>

View File

@ -735,9 +735,7 @@
<!-- MessageDisplayHelper --> <!-- MessageDisplayHelper -->
<string name="MessageDisplayHelper_bad_encrypted_message">Bad encrypted message</string> <string name="MessageDisplayHelper_bad_encrypted_message">Bad encrypted message</string>
<string name="MessageDisplayHelper_message_encrypted_for_non_existing_session">Message encrypted for non-existing session</string> <string name="MessageDisplayHelper_message_encrypted_for_non_existing_session">Message encrypted for non-existing session</string>
<string name="MessageRecord_session_restore_required">Could not decrypt an incoming message. Would you like to start a new session with %s?</string>
<string name="MessageRecord_session_restore_sent">You have sent a session restore request to %s</string> <string name="MessageRecord_session_restore_sent">You have sent a session restore request to %s</string>
<string name="MessageRecord_session_restore_button_title">Restore session</string>
<!-- MmsMessageRecord --> <!-- MmsMessageRecord -->
<string name="MmsMessageRecord_bad_encrypted_mms_message">Bad encrypted MMS message</string> <string name="MmsMessageRecord_bad_encrypted_mms_message">Bad encrypted MMS message</string>
@ -1654,5 +1652,9 @@
<!-- Device unlink dialog --> <!-- Device unlink dialog -->
<string name="dialog_device_unlink_title">Device unlinked</string> <string name="dialog_device_unlink_title">Device unlinked</string>
<string name="dialog_device_unlink_message">This device has been successfully unlinked</string> <string name="dialog_device_unlink_message">This device has been successfully unlinked</string>
<!-- Session restore banner -->
<string name="session_restore_banner_title">Could not decrypt an incoming message.</string>
<string name="session_restore_banner_message">Would you like to start a new session with %s?</string>
<string name="session_restore_banner_restore_button_title">Restore session</string>
<string name="session_restore_banner_dismiss_button_title">Dismiss</string>
</resources> </resources>

View File

@ -139,6 +139,7 @@ import org.thoughtcrime.securesms.database.MessagingDatabase.MarkedMessageInfo;
import org.thoughtcrime.securesms.database.MmsSmsColumns.Types; import org.thoughtcrime.securesms.database.MmsSmsColumns.Types;
import org.thoughtcrime.securesms.database.RecipientDatabase; import org.thoughtcrime.securesms.database.RecipientDatabase;
import org.thoughtcrime.securesms.database.RecipientDatabase.RegisteredState; import org.thoughtcrime.securesms.database.RecipientDatabase.RegisteredState;
import org.thoughtcrime.securesms.database.SmsDatabase;
import org.thoughtcrime.securesms.database.ThreadDatabase; import org.thoughtcrime.securesms.database.ThreadDatabase;
import org.thoughtcrime.securesms.database.identity.IdentityRecordList; import org.thoughtcrime.securesms.database.identity.IdentityRecordList;
import org.thoughtcrime.securesms.database.model.MessageRecord; import org.thoughtcrime.securesms.database.model.MessageRecord;
@ -154,7 +155,6 @@ import org.thoughtcrime.securesms.linkpreview.LinkPreviewRepository;
import org.thoughtcrime.securesms.linkpreview.LinkPreviewUtil; import org.thoughtcrime.securesms.linkpreview.LinkPreviewUtil;
import org.thoughtcrime.securesms.linkpreview.LinkPreviewViewModel; import org.thoughtcrime.securesms.linkpreview.LinkPreviewViewModel;
import org.thoughtcrime.securesms.logging.Log; import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.loki.ConversationUpdateItemViewDelegate;
import org.thoughtcrime.securesms.loki.FriendRequestViewDelegate; import org.thoughtcrime.securesms.loki.FriendRequestViewDelegate;
import org.thoughtcrime.securesms.loki.LokiAPIUtilities; import org.thoughtcrime.securesms.loki.LokiAPIUtilities;
import org.thoughtcrime.securesms.loki.LokiThreadDatabase; import org.thoughtcrime.securesms.loki.LokiThreadDatabase;
@ -163,6 +163,7 @@ import org.thoughtcrime.securesms.loki.LokiThreadDatabaseDelegate;
import org.thoughtcrime.securesms.loki.LokiUserDatabase; import org.thoughtcrime.securesms.loki.LokiUserDatabase;
import org.thoughtcrime.securesms.loki.MentionCandidateSelectionView; import org.thoughtcrime.securesms.loki.MentionCandidateSelectionView;
import org.thoughtcrime.securesms.loki.MultiDeviceUtilities; import org.thoughtcrime.securesms.loki.MultiDeviceUtilities;
import org.thoughtcrime.securesms.loki.SessionRestoreBannerView;
import org.thoughtcrime.securesms.mediasend.Media; import org.thoughtcrime.securesms.mediasend.Media;
import org.thoughtcrime.securesms.mediasend.MediaSendActivity; import org.thoughtcrime.securesms.mediasend.MediaSendActivity;
import org.thoughtcrime.securesms.mms.AttachmentManager; import org.thoughtcrime.securesms.mms.AttachmentManager;
@ -242,6 +243,7 @@ import java.util.Date;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Set;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
@ -249,7 +251,6 @@ import java.util.concurrent.atomic.AtomicInteger;
import kotlin.Unit; import kotlin.Unit;
import network.loki.messenger.R; import network.loki.messenger.R;
import static nl.komponents.kovenant.KovenantApi.task;
import static org.thoughtcrime.securesms.TransportOption.Type; import static org.thoughtcrime.securesms.TransportOption.Type;
import static org.thoughtcrime.securesms.database.GroupDatabase.GroupRecord; import static org.thoughtcrime.securesms.database.GroupDatabase.GroupRecord;
import static org.whispersystems.libsignal.SessionCipher.SESSION_LOCK; import static org.whispersystems.libsignal.SessionCipher.SESSION_LOCK;
@ -273,8 +274,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
ConversationSearchBottomBar.EventListener, ConversationSearchBottomBar.EventListener,
StickerKeyboardProvider.StickerEventListener, StickerKeyboardProvider.StickerEventListener,
LokiThreadDatabaseDelegate, LokiThreadDatabaseDelegate,
FriendRequestViewDelegate, FriendRequestViewDelegate
ConversationUpdateItemViewDelegate
{ {
private static final String TAG = ConversationActivity.class.getSimpleName(); private static final String TAG = ConversationActivity.class.getSimpleName();
@ -358,6 +358,9 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
// Multi Device // Multi Device
private boolean isFriendsWithAnyDevice = false; private boolean isFriendsWithAnyDevice = false;
// Restoration
protected Stub<SessionRestoreBannerView> sessionRestoreBannerView;
@Override @Override
protected void onPreCreate() { protected void onPreCreate() {
dynamicTheme.onCreate(this); dynamicTheme.onCreate(this);
@ -378,7 +381,6 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
fragment = initFragment(R.id.fragment_content, new ConversationFragment(), dynamicLanguage.getCurrentLocale()); fragment = initFragment(R.id.fragment_content, new ConversationFragment(), dynamicLanguage.getCurrentLocale());
fragment.friendRequestViewDelegate = this; fragment.friendRequestViewDelegate = this;
fragment.conversationUpdateItemViewDelegate = this;
initializeReceivers(); initializeReceivers();
initializeActionBar(); initializeActionBar();
@ -1492,6 +1494,16 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
} }
} }
protected void updateSessionRestoreBanner() {
Set<String> devices = DatabaseFactory.getLokiThreadDatabase(this).getSessionRestoreDevices(threadId);
SessionRestoreBannerView view = sessionRestoreBannerView.get();
if (devices.size() > 0) {
view.show();
} else {
view.hide();
}
}
private void updateDefaultSubscriptionId(Optional<Integer> defaultSubscriptionId) { private void updateDefaultSubscriptionId(Optional<Integer> defaultSubscriptionId) {
Log.i(TAG, "updateDefaultSubscriptionId(" + defaultSubscriptionId.orNull() + ")"); Log.i(TAG, "updateDefaultSubscriptionId(" + defaultSubscriptionId.orNull() + ")");
sendButton.setDefaultSubscriptionId(defaultSubscriptionId); sendButton.setDefaultSubscriptionId(defaultSubscriptionId);
@ -1588,6 +1600,18 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
inputPanel = ViewUtil.findById(this, R.id.bottom_panel); inputPanel = ViewUtil.findById(this, R.id.bottom_panel);
searchNav = ViewUtil.findById(this, R.id.conversation_search_nav); searchNav = ViewUtil.findById(this, R.id.conversation_search_nav);
mentionCandidateSelectionView = ViewUtil.findById(this, R.id.userSelectionView); 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;
});
ImageButton quickCameraToggle = ViewUtil.findById(this, R.id.quick_camera_toggle); ImageButton quickCameraToggle = ViewUtil.findById(this, R.id.quick_camera_toggle);
ImageButton inlineAttachmentButton = ViewUtil.findById(this, R.id.inline_attachment_button); ImageButton inlineAttachmentButton = ViewUtil.findById(this, R.id.inline_attachment_button);
@ -2206,6 +2230,13 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
this.updateInputPanel(); this.updateInputPanel();
} }
@Override
public void handleSessionRestoreDevicesChanged(long threadId) {
if (threadId == this.threadId) {
updateSessionRestoreBanner();
}
}
private void updateInputPanel() { private void updateInputPanel() {
/* /*
isFriendsWithAnyDevice caches whether we are friends with any of the other users device. isFriendsWithAnyDevice caches whether we are friends with any of the other users device.
@ -3083,14 +3114,19 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
} }
// endregion // endregion
@Override public void restoreSession() {
public void updateItemButtonPressed(@NonNull MessageRecord messageRecord) {
// Loki - User clicked restore session // Loki - User clicked restore session
Recipient recipient = messageRecord.getRecipient(); if (!recipient.isGroupRecipient()) {
if (!recipient.isGroupRecipient() && messageRecord.isNoRemoteSession() && !messageRecord.isLokiSessionRestoreSent()) { LokiThreadDatabase lokiThreadDatabase = DatabaseFactory.getLokiThreadDatabase(this);
MessageSender.sendRestoreSessionMessage(this, recipient.getAddress().serialize()); SmsDatabase database = DatabaseFactory.getSmsDatabase(this);
DatabaseFactory.getSmsDatabase(this).markAsLokiSessionRestoreSent(messageRecord.id); Set<String> devices = lokiThreadDatabase.getSessionRestoreDevices(threadId);
TextSecurePreferences.setShowingSessionRestorePrompt(this, messageRecord.getIndividualRecipient().getAddress().serialize(), false); for (String device : devices) { MessageSender.sendRestoreSessionMessage(this, device); }
long messageId = database.insertMessageOutbox(threadId, new OutgoingTextMessage(recipient,"", 0, 0), false, System.currentTimeMillis(), null);
if (messageId > -1) {
database.markAsLokiSessionRestoreSent(messageId);
}
lokiThreadDatabase.removeAllSessionRestoreDevices(threadId);
updateSessionRestoreBanner();
} }
} }
} }

View File

@ -41,7 +41,6 @@ import org.thoughtcrime.securesms.database.MmsSmsDatabase;
import org.thoughtcrime.securesms.database.model.MessageRecord; import org.thoughtcrime.securesms.database.model.MessageRecord;
import org.thoughtcrime.securesms.database.model.MmsMessageRecord; import org.thoughtcrime.securesms.database.model.MmsMessageRecord;
import org.thoughtcrime.securesms.logging.Log; import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.loki.ConversationUpdateItemViewDelegate;
import org.thoughtcrime.securesms.loki.FriendRequestViewDelegate; import org.thoughtcrime.securesms.loki.FriendRequestViewDelegate;
import org.thoughtcrime.securesms.mms.GlideRequests; import org.thoughtcrime.securesms.mms.GlideRequests;
import org.thoughtcrime.securesms.mms.SlideDeck; import org.thoughtcrime.securesms.mms.SlideDeck;
@ -109,7 +108,6 @@ public class ConversationAdapter <V extends View & BindableConversationItem>
private String searchQuery; private String searchQuery;
public FriendRequestViewDelegate friendRequestViewDelegate; // Loki public FriendRequestViewDelegate friendRequestViewDelegate; // Loki
public ConversationUpdateItemViewDelegate conversationUpdateItemViewDelegate;
protected static class ViewHolder extends RecyclerView.ViewHolder { protected static class ViewHolder extends RecyclerView.ViewHolder {
public <V extends View & BindableConversationItem> ViewHolder(final @NonNull V itemView) { public <V extends View & BindableConversationItem> ViewHolder(final @NonNull V itemView) {
@ -206,9 +204,8 @@ public class ConversationAdapter <V extends View & BindableConversationItem>
BindableConversationItem conversationItem = viewHolder.getView(); BindableConversationItem conversationItem = viewHolder.getView();
if (conversationItem instanceof ConversationItem) { if (conversationItem instanceof ConversationItem) {
((ConversationItem)conversationItem).friendRequestViewDelegate = this.friendRequestViewDelegate; ((ConversationItem)conversationItem).friendRequestViewDelegate = this.friendRequestViewDelegate;
} else if (conversationItem instanceof ConversationUpdateItem) {
((ConversationUpdateItem)conversationItem).delegate = this.conversationUpdateItemViewDelegate;
} }
conversationItem.bind(messageRecord, conversationItem.bind(messageRecord,
Optional.fromNullable(previousRecord), Optional.fromNullable(previousRecord),
Optional.fromNullable(nextRecord), Optional.fromNullable(nextRecord),

View File

@ -79,7 +79,6 @@ import org.thoughtcrime.securesms.database.model.MmsMessageRecord;
import org.thoughtcrime.securesms.jobs.DirectoryRefreshJob; import org.thoughtcrime.securesms.jobs.DirectoryRefreshJob;
import org.thoughtcrime.securesms.linkpreview.LinkPreview; import org.thoughtcrime.securesms.linkpreview.LinkPreview;
import org.thoughtcrime.securesms.logging.Log; import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.loki.ConversationUpdateItemViewDelegate;
import org.thoughtcrime.securesms.loki.FriendRequestViewDelegate; import org.thoughtcrime.securesms.loki.FriendRequestViewDelegate;
import org.thoughtcrime.securesms.longmessage.LongMessageActivity; import org.thoughtcrime.securesms.longmessage.LongMessageActivity;
import org.thoughtcrime.securesms.mediasend.Media; import org.thoughtcrime.securesms.mediasend.Media;
@ -153,7 +152,6 @@ public class ConversationFragment extends Fragment
private View scrollToBottomButton; private View scrollToBottomButton;
private TextView scrollDateHeader; private TextView scrollDateHeader;
public FriendRequestViewDelegate friendRequestViewDelegate; // Loki public FriendRequestViewDelegate friendRequestViewDelegate; // Loki
public ConversationUpdateItemViewDelegate conversationUpdateItemViewDelegate;
@Override @Override
public void onCreate(Bundle icicle) { public void onCreate(Bundle icicle) {
@ -362,8 +360,7 @@ public class ConversationFragment extends Fragment
if (messageRecord.isGroupAction() || messageRecord.isCallLog() || if (messageRecord.isGroupAction() || messageRecord.isCallLog() ||
messageRecord.isJoined() || messageRecord.isExpirationTimerUpdate() || messageRecord.isJoined() || messageRecord.isExpirationTimerUpdate() ||
messageRecord.isEndSession() || messageRecord.isIdentityUpdate() || messageRecord.isEndSession() || messageRecord.isIdentityUpdate() ||
messageRecord.isIdentityVerified() || messageRecord.isIdentityDefault() || messageRecord.isIdentityVerified() || messageRecord.isIdentityDefault() || messageRecord.isLokiSessionRestoreSent())
messageRecord.isNoRemoteSession() || messageRecord.isLokiSessionRestoreSent())
{ {
actionMessage = true; actionMessage = true;
} }
@ -708,7 +705,6 @@ public class ConversationFragment extends Fragment
return; return;
} }
adapter.friendRequestViewDelegate = this.friendRequestViewDelegate; adapter.friendRequestViewDelegate = this.friendRequestViewDelegate;
adapter.conversationUpdateItemViewDelegate = this.conversationUpdateItemViewDelegate;
if (cursor.getCount() >= PARTIAL_CONVERSATION_LIMIT && loader.hasLimit()) { if (cursor.getCount() >= PARTIAL_CONVERSATION_LIMIT && loader.hasLimit()) {
adapter.setFooterView(topLoadMoreView); adapter.setFooterView(topLoadMoreView);

View File

@ -87,7 +87,6 @@ import org.thoughtcrime.securesms.jobs.SmsSendJob;
import org.thoughtcrime.securesms.linkpreview.LinkPreview; import org.thoughtcrime.securesms.linkpreview.LinkPreview;
import org.thoughtcrime.securesms.linkpreview.LinkPreviewUtil; import org.thoughtcrime.securesms.linkpreview.LinkPreviewUtil;
import org.thoughtcrime.securesms.logging.Log; import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.loki.ConversationUpdateItemViewDelegate;
import org.thoughtcrime.securesms.loki.FriendRequestView; import org.thoughtcrime.securesms.loki.FriendRequestView;
import org.thoughtcrime.securesms.loki.FriendRequestViewDelegate; import org.thoughtcrime.securesms.loki.FriendRequestViewDelegate;
import org.thoughtcrime.securesms.loki.LokiMessageDatabase; import org.thoughtcrime.securesms.loki.LokiMessageDatabase;

View File

@ -9,7 +9,6 @@ import android.support.annotation.NonNull;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import android.util.AttributeSet; import android.util.AttributeSet;
import android.view.View; import android.view.View;
import android.widget.Button;
import android.widget.ImageView; import android.widget.ImageView;
import android.widget.LinearLayout; import android.widget.LinearLayout;
import android.widget.TextView; import android.widget.TextView;
@ -22,7 +21,6 @@ import org.thoughtcrime.securesms.database.IdentityDatabase;
import org.thoughtcrime.securesms.database.IdentityDatabase.IdentityRecord; import org.thoughtcrime.securesms.database.IdentityDatabase.IdentityRecord;
import org.thoughtcrime.securesms.database.model.MessageRecord; import org.thoughtcrime.securesms.database.model.MessageRecord;
import org.thoughtcrime.securesms.logging.Log; import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.loki.ConversationUpdateItemViewDelegate;
import org.thoughtcrime.securesms.mms.GlideRequests; import org.thoughtcrime.securesms.mms.GlideRequests;
import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientModifiedListener; import org.thoughtcrime.securesms.recipients.RecipientModifiedListener;
@ -49,13 +47,10 @@ public class ConversationUpdateItem extends LinearLayout
private TextView title; private TextView title;
private TextView body; private TextView body;
private TextView date; private TextView date;
private Button button;
private Recipient sender; private Recipient sender;
private MessageRecord messageRecord; private MessageRecord messageRecord;
private Locale locale; private Locale locale;
public ConversationUpdateItemViewDelegate delegate;
public ConversationUpdateItem(Context context) { public ConversationUpdateItem(Context context) {
super(context); super(context);
} }
@ -72,12 +67,6 @@ public class ConversationUpdateItem extends LinearLayout
this.title = findViewById(R.id.conversation_update_title); this.title = findViewById(R.id.conversation_update_title);
this.body = findViewById(R.id.conversation_update_body); this.body = findViewById(R.id.conversation_update_body);
this.date = findViewById(R.id.conversation_update_date); this.date = findViewById(R.id.conversation_update_date);
this.button = findViewById(R.id.conversation_update_button);
this.button.setOnClickListener(view -> {
if (delegate != null && messageRecord != null) {
delegate.updateItemButtonPressed(messageRecord);
}
});
this.setOnClickListener(new InternalClickListener(null)); this.setOnClickListener(new InternalClickListener(null));
} }
@ -123,8 +112,7 @@ public class ConversationUpdateItem extends LinearLayout
else if (messageRecord.isIdentityUpdate()) setIdentityRecord(messageRecord); else if (messageRecord.isIdentityUpdate()) setIdentityRecord(messageRecord);
else if (messageRecord.isIdentityVerified() || else if (messageRecord.isIdentityVerified() ||
messageRecord.isIdentityDefault()) setIdentityVerifyUpdate(messageRecord); messageRecord.isIdentityDefault()) setIdentityVerifyUpdate(messageRecord);
else if (messageRecord.isNoRemoteSession() || else if (messageRecord.isLokiSessionRestoreSent()) setTextMessageRecord(messageRecord);
messageRecord.isLokiSessionRestoreSent()) setTextMessageRecord(messageRecord);
else throw new AssertionError("Neither group nor log nor joined."); else throw new AssertionError("Neither group nor log nor joined.");
if (batchSelected.contains(messageRecord)) setSelected(true); if (batchSelected.contains(messageRecord)) setSelected(true);
@ -217,12 +205,6 @@ public class ConversationUpdateItem extends LinearLayout
private void setTextMessageRecord(MessageRecord messageRecord) { private void setTextMessageRecord(MessageRecord messageRecord) {
body.setText(messageRecord.getDisplayBody(getContext())); body.setText(messageRecord.getDisplayBody(getContext()));
if (messageRecord.isNoRemoteSession() && !messageRecord.isLokiSessionRestoreSent()) {
button.setVisibility(VISIBLE);
button.setText(R.string.MessageRecord_session_restore_button_title);
} else {
button.setVisibility(GONE);
}
icon.setVisibility(GONE); icon.setVisibility(GONE);
title.setVisibility(GONE); title.setVisibility(GONE);

View File

@ -103,11 +103,7 @@ public abstract class DisplayRecord {
return SmsDatabase.Types.isKeyExchangeType(type); return SmsDatabase.Types.isKeyExchangeType(type);
} }
public boolean isEndSession() { public boolean isEndSession() { return SmsDatabase.Types.isEndSessionType(type); }
return SmsDatabase.Types.isEndSessionType(type);
}
public boolean isNoRemoteSession() { return SmsDatabase.Types.isNoRemoteSessionType(type); }
public boolean isLokiSessionRestoreSent() { return SmsDatabase.Types.isLokiSessionRestoreSentType(type); } public boolean isLokiSessionRestoreSent() { return SmsDatabase.Types.isLokiSessionRestoreSentType(type); }

View File

@ -180,7 +180,7 @@ public abstract class MessageRecord extends DisplayRecord {
public boolean isUpdate() { public boolean isUpdate() {
return isGroupAction() || isJoined() || isExpirationTimerUpdate() || isCallLog() || return isGroupAction() || isJoined() || isExpirationTimerUpdate() || isCallLog() ||
isEndSession() || isIdentityUpdate() || isIdentityVerified() || isIdentityDefault() || isNoRemoteSession() || isLokiSessionRestoreSent(); isEndSession() || isIdentityUpdate() || isIdentityVerified() || isIdentityDefault() || isLokiSessionRestoreSent();
} }
public boolean isMediaPending() { public boolean isMediaPending() {

View File

@ -99,14 +99,10 @@ public class SmsMessageRecord extends MessageRecord {
return emphasisAdded(context.getString(R.string.ConversationItem_received_key_exchange_message_tap_to_process)); return emphasisAdded(context.getString(R.string.ConversationItem_received_key_exchange_message_tap_to_process));
} else if (SmsDatabase.Types.isDuplicateMessageType(type)) { } else if (SmsDatabase.Types.isDuplicateMessageType(type)) {
return emphasisAdded(context.getString(R.string.SmsMessageRecord_duplicate_message)); return emphasisAdded(context.getString(R.string.SmsMessageRecord_duplicate_message));
} else if (SmsDatabase.Types.isNoRemoteSessionType(type)) {
return emphasisAdded(context.getString(R.string.MessageDisplayHelper_message_encrypted_for_non_existing_session));
} else if (isLokiSessionRestoreSent()) { } else if (isLokiSessionRestoreSent()) {
return emphasisAdded(context.getString(R.string.MessageRecord_session_restore_sent, recipient.toShortString())); return emphasisAdded(context.getString(R.string.MessageRecord_session_restore_sent, recipient.toShortString()));
} else if (isNoRemoteSession()) {
if (recipient.isGroupRecipient()) {
return emphasisAdded(context.getString(R.string.MessageDisplayHelper_message_encrypted_for_non_existing_session));
} else {
return emphasisAdded(context.getString(R.string.MessageRecord_session_restore_required, recipient.toShortString()));
}
} else if (isEndSession() && isOutgoing()) { } else if (isEndSession() && isOutgoing()) {
return emphasisAdded(context.getString(R.string.SmsMessageRecord_secure_session_reset)); return emphasisAdded(context.getString(R.string.SmsMessageRecord_secure_session_reset));
} else if (isEndSession()) { } else if (isEndSession()) {

View File

@ -80,14 +80,10 @@ public class ThreadRecord extends DisplayRecord {
return emphasisAdded(context.getString(R.string.ConversationListItem_key_exchange_message)); return emphasisAdded(context.getString(R.string.ConversationListItem_key_exchange_message));
} else if (SmsDatabase.Types.isFailedDecryptType(type)) { } else if (SmsDatabase.Types.isFailedDecryptType(type)) {
return emphasisAdded(context.getString(R.string.MessageDisplayHelper_bad_encrypted_message)); return emphasisAdded(context.getString(R.string.MessageDisplayHelper_bad_encrypted_message));
} else if (SmsDatabase.Types.isNoRemoteSessionType(type)) {
return emphasisAdded(context.getString(R.string.MessageDisplayHelper_message_encrypted_for_non_existing_session));
} else if (isLokiSessionRestoreSent()) { } else if (isLokiSessionRestoreSent()) {
return emphasisAdded(context.getString(R.string.MessageRecord_session_restore_sent, recipient.toShortString())); return emphasisAdded(context.getString(R.string.MessageRecord_session_restore_sent, recipient.toShortString()));
} else if (isNoRemoteSession()) {
if (recipient.isGroupRecipient()) {
return emphasisAdded(context.getString(R.string.MessageDisplayHelper_message_encrypted_for_non_existing_session));
} else {
return emphasisAdded(context.getString(R.string.MessageRecord_session_restore_required, recipient.toShortString()));
}
} else if (SmsDatabase.Types.isEndSessionType(type)) { } else if (SmsDatabase.Types.isEndSessionType(type)) {
return emphasisAdded(context.getString(R.string.ThreadRecord_secure_session_reset)); return emphasisAdded(context.getString(R.string.ThreadRecord_secure_session_reset));
} else if (MmsSmsColumns.Types.isLegacyType(type)) { } else if (MmsSmsColumns.Types.isLegacyType(type)) {

View File

@ -42,6 +42,7 @@ import org.thoughtcrime.securesms.crypto.storage.SignalProtocolStoreImpl;
import org.thoughtcrime.securesms.crypto.storage.TextSecureSessionStore; import org.thoughtcrime.securesms.crypto.storage.TextSecureSessionStore;
import org.thoughtcrime.securesms.database.Address; import org.thoughtcrime.securesms.database.Address;
import org.thoughtcrime.securesms.database.AttachmentDatabase; import org.thoughtcrime.securesms.database.AttachmentDatabase;
import org.thoughtcrime.securesms.database.Database;
import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.GroupDatabase; import org.thoughtcrime.securesms.database.GroupDatabase;
import org.thoughtcrime.securesms.database.GroupReceiptDatabase; import org.thoughtcrime.securesms.database.GroupReceiptDatabase;
@ -74,7 +75,6 @@ import org.thoughtcrime.securesms.loki.LokiPreKeyBundleDatabase;
import org.thoughtcrime.securesms.loki.LokiPreKeyRecordDatabase; import org.thoughtcrime.securesms.loki.LokiPreKeyRecordDatabase;
import org.thoughtcrime.securesms.loki.LokiThreadDatabase; import org.thoughtcrime.securesms.loki.LokiThreadDatabase;
import org.thoughtcrime.securesms.loki.MultiDeviceUtilities; import org.thoughtcrime.securesms.loki.MultiDeviceUtilities;
import org.thoughtcrime.securesms.loki.DebouncerCache;
import org.thoughtcrime.securesms.mms.IncomingMediaMessage; import org.thoughtcrime.securesms.mms.IncomingMediaMessage;
import org.thoughtcrime.securesms.mms.MmsException; import org.thoughtcrime.securesms.mms.MmsException;
import org.thoughtcrime.securesms.mms.OutgoingExpirationUpdateMessage; import org.thoughtcrime.securesms.mms.OutgoingExpirationUpdateMessage;
@ -95,7 +95,6 @@ import org.thoughtcrime.securesms.sms.OutgoingEncryptedMessage;
import org.thoughtcrime.securesms.sms.OutgoingEndSessionMessage; import org.thoughtcrime.securesms.sms.OutgoingEndSessionMessage;
import org.thoughtcrime.securesms.sms.OutgoingTextMessage; import org.thoughtcrime.securesms.sms.OutgoingTextMessage;
import org.thoughtcrime.securesms.stickers.StickerLocator; import org.thoughtcrime.securesms.stickers.StickerLocator;
import org.thoughtcrime.securesms.util.Debouncer;
import org.thoughtcrime.securesms.util.GroupUtil; import org.thoughtcrime.securesms.util.GroupUtil;
import org.thoughtcrime.securesms.util.Hex; import org.thoughtcrime.securesms.util.Hex;
import org.thoughtcrime.securesms.util.IdentityUtil; import org.thoughtcrime.securesms.util.IdentityUtil;
@ -276,9 +275,11 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
LokiServiceCipher cipher = new LokiServiceCipher(localAddress, axolotlStore, lokiThreadDatabase, lokiPreKeyRecordDatabase, UnidentifiedAccessUtil.getCertificateValidator()); LokiServiceCipher cipher = new LokiServiceCipher(localAddress, axolotlStore, lokiThreadDatabase, lokiPreKeyRecordDatabase, UnidentifiedAccessUtil.getCertificateValidator());
// Loki - Handle session reset logic // Loki - Handle session reset logic
/*
if (!envelope.isFriendRequest() && cipher.getSessionStatus(envelope) == null && envelope.isPreKeySignalMessage()) { if (!envelope.isFriendRequest() && cipher.getSessionStatus(envelope) == null && envelope.isPreKeySignalMessage()) {
cipher.validateBackgroundMessage(envelope, envelope.getContent()); cipher.validateBackgroundMessage(envelope, envelope.getContent());
} }
*/
// Loki - Ignore any friend requests that we got before restoration // Loki - Ignore any friend requests that we got before restoration
if (envelope.isFriendRequest() && envelope.getTimestamp() < TextSecurePreferences.getRestorationTime(context)) { if (envelope.isFriendRequest() && envelope.getTimestamp() < TextSecurePreferences.getRestorationTime(context)) {
@ -1394,51 +1395,38 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
if (insertResult.isPresent()) { if (insertResult.isPresent()) {
smsDatabase.markAsDecryptFailed(insertResult.get().getMessageId()); smsDatabase.markAsDecryptFailed(insertResult.get().getMessageId());
MessageNotifier.updateNotification(context, insertResult.get().getThreadId()); // MessageNotifier.updateNotification(context, insertResult.get().getThreadId());
} }
} else { } else {
smsDatabase.markAsDecryptFailed(smsMessageId.get()); smsDatabase.markAsDecryptFailed(smsMessageId.get());
} }
triggerSessionRestorePrompt(sender);
} }
private void handleNoSessionMessage(@NonNull String sender, int senderDevice, long timestamp, private void handleNoSessionMessage(@NonNull String sender, int senderDevice, long timestamp,
@NonNull Optional<Long> smsMessageId) @NonNull Optional<Long> smsMessageId)
{ {
Recipient recipient = Recipient.from(context, Address.fromSerialized(sender), false);
if (recipient.isGroupRecipient()) { return; }
long threadID = DatabaseFactory.getThreadDatabase(context).getThreadIdIfExistsFor(recipient);
LokiThreadFriendRequestStatus friendRequestStatus = DatabaseFactory.getLokiThreadDatabase(context).getFriendRequestStatus(threadID);
/*
If we are friends with the user or we sent a friend request to them and we got a message back with no session then we want to try and restore the session automatically.
otherwise if we're not friends or our friend request expired then we need to prompt the user for action
*/
if (friendRequestStatus == LokiThreadFriendRequestStatus.FRIENDS || friendRequestStatus == LokiThreadFriendRequestStatus.REQUEST_SENT) {
autoRestoreSession(sender);
} else if (friendRequestStatus == LokiThreadFriendRequestStatus.NONE || friendRequestStatus == LokiThreadFriendRequestStatus.REQUEST_EXPIRED) {
SmsDatabase smsDatabase = DatabaseFactory.getSmsDatabase(context); SmsDatabase smsDatabase = DatabaseFactory.getSmsDatabase(context);
if (!smsMessageId.isPresent()) { if (!smsMessageId.isPresent()) {
if (!TextSecurePreferences.isShowingSessionRestorePrompt(context, sender)) {
Optional<InsertResult> insertResult = insertPlaceholder(sender, senderDevice, timestamp); Optional<InsertResult> insertResult = insertPlaceholder(sender, senderDevice, timestamp);
if (insertResult.isPresent()) { if (insertResult.isPresent()) {
smsDatabase.markAsNoSession(insertResult.get().getMessageId()); smsDatabase.markAsNoSession(insertResult.get().getMessageId());
TextSecurePreferences.setShowingSessionRestorePrompt(context, sender, true);
// MessageNotifier.updateNotification(context, insertResult.get().getThreadId()); // MessageNotifier.updateNotification(context, insertResult.get().getThreadId());
} }
}
} else { } else {
smsDatabase.markAsNoSession(smsMessageId.get()); smsDatabase.markAsNoSession(smsMessageId.get());
} }
} triggerSessionRestorePrompt(sender);
} }
private void autoRestoreSession(@NonNull String sender) { private void triggerSessionRestorePrompt(@NonNull String sender) {
// We don't want to keep spamming the user for an auto restore Recipient primaryRecipient = getPrimaryDeviceRecipient(sender);
String key = "restore_session_" + sender; if (!primaryRecipient.isGroupRecipient()) {
Debouncer debouncer = DebouncerCache.getDebouncer(key, 10000); long threadID = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(primaryRecipient);
debouncer.publish(() -> MessageSender.sendRestoreSessionMessage(context, sender)); DatabaseFactory.getLokiThreadDatabase(context).addSessionRestoreDevice(threadID, sender);
}
} }
private void handleLegacyMessage(@NonNull String sender, int senderDevice, long timestamp, private void handleLegacyMessage(@NonNull String sender, int senderDevice, long timestamp,

View File

@ -1,7 +0,0 @@
package org.thoughtcrime.securesms.loki
import org.thoughtcrime.securesms.database.model.MessageRecord
interface ConversationUpdateItemViewDelegate {
fun updateItemButtonPressed(message: MessageRecord)
}

View File

@ -29,12 +29,7 @@ object FriendRequestHandler {
DatabaseFactory.getLokiThreadDatabase(context).setFriendRequestStatus(threadId, threadFriendStatus) DatabaseFactory.getLokiThreadDatabase(context).setFriendRequestStatus(threadId, threadFriendStatus)
// If we sent a friend request then we need to hide the session restore prompt // If we sent a friend request then we need to hide the session restore prompt
if (type == ActionType.Sent) { if (type == ActionType.Sent) {
val smsDatabase = DatabaseFactory.getSmsDatabase(context) // TODO: Hide prompt
smsDatabase.getMessages(threadId)
.filter { it.isNoRemoteSession && !it.isLokiSessionRestoreSent }
.forEach {
smsDatabase.markAsLokiSessionRestoreSent(it.id)
}
} }
} }

View File

@ -8,6 +8,7 @@ import org.thoughtcrime.securesms.database.Database
import org.thoughtcrime.securesms.database.DatabaseFactory import org.thoughtcrime.securesms.database.DatabaseFactory
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper
import org.thoughtcrime.securesms.recipients.Recipient import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.util.TextSecurePreferences
import org.whispersystems.signalservice.internal.util.JsonUtil import org.whispersystems.signalservice.internal.util.JsonUtil
import org.whispersystems.signalservice.loki.api.LokiPublicChat import org.whispersystems.signalservice.loki.api.LokiPublicChat
import org.whispersystems.signalservice.loki.messaging.LokiThreadDatabaseProtocol import org.whispersystems.signalservice.loki.messaging.LokiThreadDatabaseProtocol
@ -140,4 +141,23 @@ class LokiThreadDatabase(context: Context, helper: SQLCipherOpenHelper) : Databa
override fun removePublicChat(threadID: Long) { override fun removePublicChat(threadID: Long) {
databaseHelper.writableDatabase.delete(publicChatTableName, "${Companion.threadID} = ?", arrayOf( threadID.toString() )) databaseHelper.writableDatabase.delete(publicChatTableName, "${Companion.threadID} = ?", arrayOf( threadID.toString() ))
} }
// region Session Restore
fun addSessionRestoreDevice(threadID: Long, hexEncodedPublicKey: String) {
val devices = getSessionRestoreDevices(threadID).toMutableSet()
if (devices.add(hexEncodedPublicKey)) {
TextSecurePreferences.setStringPreference(context, "session_restore_devices_$threadID", devices.joinToString(","))
delegate?.handleSessionRestoreDevicesChanged(threadID)
}
}
fun getSessionRestoreDevices(threadID: Long): Set<String> {
return TextSecurePreferences.getStringPreference(context, "session_restore_devices_$threadID", "").split(",").toSet()
}
fun removeAllSessionRestoreDevices(threadID: Long) {
TextSecurePreferences.setStringPreference(context, "session_restore_devices_$threadID", "")
delegate?.handleSessionRestoreDevicesChanged(threadID)
}
// endregion
} }

View File

@ -3,4 +3,5 @@ package org.thoughtcrime.securesms.loki
interface LokiThreadDatabaseDelegate { interface LokiThreadDatabaseDelegate {
fun handleThreadFriendRequestStatusChanged(threadID: Long) fun handleThreadFriendRequestStatusChanged(threadID: Long)
fun handleSessionRestoreDevicesChanged(threadID: Long)
} }

View File

@ -0,0 +1,55 @@
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
lateinit var recipient: Recipient
var onDismiss: (() -> Unit)? = null
var onRestore: (() -> Unit)? = null
constructor(context: Context) : this(context, null)
private constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0)
init {
LayoutInflater.from(context).inflate(R.layout.session_restore_banner, this, true)
restoreButton.setOnClickListener { onRestore?.invoke() }
dismissButton.setOnClickListener { onDismiss?.invoke() }
}
fun update(recipient: Recipient) {
this.recipient = recipient
restoreText.text = context.getString(R.string.session_restore_banner_message, recipient.toShortString())
}
fun show() {
container!!.visibility = View.VISIBLE;
}
fun hide() {
container!!.visibility = View.GONE
}
}

View File

@ -1228,14 +1228,6 @@ public class TextSecurePreferences {
public static long getRestorationTime(Context context) { public static long getRestorationTime(Context context) {
return getLongPreference(context, "restoration_time", 0); return getLongPreference(context, "restoration_time", 0);
} }
public static void setShowingSessionRestorePrompt(Context context, String sender, boolean showingPrompt) {
setBooleanPreference(context, sender + "_showing_session_reset", showingPrompt);
}
public static boolean isShowingSessionRestorePrompt(Context context, String sender) {
return getBooleanPreference(context, sender + "_showing_session_reset", false);
}
// endregion // endregion
public static void clearAll(Context context) { public static void clearAll(Context context) {