mirror of
https://github.com/oxen-io/session-android.git
synced 2025-02-25 20:37:20 +00:00
Merge pull request #55 from loki-project/session-restore
Session restore
This commit is contained in:
commit
2781c30d7a
@ -104,6 +104,11 @@
|
|||||||
android:indeterminate="false"
|
android:indeterminate="false"
|
||||||
android:progress="80" />
|
android:progress="80" />
|
||||||
|
|
||||||
|
<org.thoughtcrime.securesms.loki.SessionRestoreBannerView
|
||||||
|
android:id="@+id/sessionRestoreBannerView"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content" />
|
||||||
|
|
||||||
<ViewStub
|
<ViewStub
|
||||||
android:id="@+id/group_share_profile_view_stub"
|
android:id="@+id/group_share_profile_view_stub"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
@ -53,7 +53,7 @@
|
|||||||
style="@style/Signal.Text.Preview"
|
style="@style/Signal.Text.Preview"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:autoLink="all"
|
android:autoLink="none"
|
||||||
android:gravity="center"
|
android:gravity="center"
|
||||||
android:linksClickable="true"
|
android:linksClickable="true"
|
||||||
android:textSize="@dimen/small_font_size"
|
android:textSize="@dimen/small_font_size"
|
||||||
|
73
res/layout/session_restore_banner.xml
Normal file
73
res/layout/session_restore_banner.xml
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:id="@+id/sessionRestoreBanner"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="@color/dialog_background"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:elevation="10dp">
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="1px"
|
||||||
|
android:background="@color/separator" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:padding="@dimen/medium_spacing"
|
||||||
|
android:gravity="center_horizontal">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Session Out of Sync"
|
||||||
|
android:textColor="@color/text"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:textSize="@dimen/medium_font_size" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/messageTextView"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="@dimen/small_spacing"
|
||||||
|
android:text="@string/session_restore_banner_message"
|
||||||
|
android:textColor="@color/text"
|
||||||
|
android:textSize="@dimen/small_font_size"
|
||||||
|
android:textAlignment="center" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="@dimen/small_spacing"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<Button
|
||||||
|
style="@style/UnimportantDialogButton"
|
||||||
|
android:id="@+id/dismissButton"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="@dimen/small_button_height"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:text="@string/session_restore_banner_dismiss_button_title" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
style="@style/ProminentDialogButton"
|
||||||
|
android:id="@+id/restoreButton"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="@dimen/small_button_height"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:layout_marginLeft="@dimen/medium_spacing"
|
||||||
|
android:text="@string/session_restore_banner_restore_button_title" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="1px"
|
||||||
|
android:background="@color/separator" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
@ -735,6 +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_sent">You have sent a session restoration request to %s</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>
|
||||||
@ -1651,6 +1652,10 @@
|
|||||||
<!-- 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_message">Would you like to restore your session with %s?</string>
|
||||||
|
<string name="session_restore_banner_dismiss_button_title">Dismiss</string>
|
||||||
|
<string name="session_restore_banner_restore_button_title">Restore</string>
|
||||||
|
|
||||||
<!-- Loki -->
|
<!-- Loki -->
|
||||||
|
|
||||||
|
@ -141,6 +141,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;
|
||||||
@ -162,6 +163,7 @@ import org.thoughtcrime.securesms.loki.LokiThreadDatabase;
|
|||||||
import org.thoughtcrime.securesms.loki.LokiThreadDatabaseDelegate;
|
import org.thoughtcrime.securesms.loki.LokiThreadDatabaseDelegate;
|
||||||
import org.thoughtcrime.securesms.loki.LokiUserDatabase;
|
import org.thoughtcrime.securesms.loki.LokiUserDatabase;
|
||||||
import org.thoughtcrime.securesms.loki.MultiDeviceUtilities;
|
import org.thoughtcrime.securesms.loki.MultiDeviceUtilities;
|
||||||
|
import org.thoughtcrime.securesms.loki.SessionRestoreBannerView;
|
||||||
import org.thoughtcrime.securesms.loki.redesign.activities.HomeActivity;
|
import org.thoughtcrime.securesms.loki.redesign.activities.HomeActivity;
|
||||||
import org.thoughtcrime.securesms.loki.redesign.views.FriendRequestViewDelegate;
|
import org.thoughtcrime.securesms.loki.redesign.views.FriendRequestViewDelegate;
|
||||||
import org.thoughtcrime.securesms.loki.redesign.views.MentionCandidateSelectionView;
|
import org.thoughtcrime.securesms.loki.redesign.views.MentionCandidateSelectionView;
|
||||||
@ -248,6 +250,7 @@ import java.util.HashSet;
|
|||||||
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;
|
||||||
@ -372,6 +375,9 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
|||||||
// Multi Device
|
// Multi Device
|
||||||
private boolean isFriendsWithAnyDevice = false;
|
private boolean isFriendsWithAnyDevice = false;
|
||||||
|
|
||||||
|
// Restoration
|
||||||
|
protected SessionRestoreBannerView sessionRestoreBannerView;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onPreCreate() {
|
protected void onPreCreate() {
|
||||||
dynamicTheme.onCreate(this);
|
dynamicTheme.onCreate(this);
|
||||||
@ -446,6 +452,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);
|
LokiAPIUtilities.INSTANCE.populateUserHexEncodedPublicKeyCacheIfNeeded(threadId, this);
|
||||||
|
|
||||||
LokiPublicChat publicChat = DatabaseFactory.getLokiThreadDatabase(this).getPublicChat(threadId);
|
LokiPublicChat publicChat = DatabaseFactory.getLokiThreadDatabase(this).getPublicChat(threadId);
|
||||||
@ -539,6 +556,8 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
|||||||
DatabaseFactory.getLokiThreadDatabase(this).setDelegate(this);
|
DatabaseFactory.getLokiThreadDatabase(this).setDelegate(this);
|
||||||
updateInputPanel();
|
updateInputPanel();
|
||||||
|
|
||||||
|
updateSessionRestoreBanner();
|
||||||
|
|
||||||
Log.i(TAG, "onResume() Finished: " + (System.currentTimeMillis() - getIntent().getLongExtra(TIMING_EXTRA, 0)));
|
Log.i(TAG, "onResume() Finished: " + (System.currentTimeMillis() - getIntent().getLongExtra(TIMING_EXTRA, 0)));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1542,6 +1561,16 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected void updateSessionRestoreBanner() {
|
||||||
|
Set<String> devices = DatabaseFactory.getLokiThreadDatabase(this).getSessionRestoreDevices(threadId);
|
||||||
|
if (devices.size() > 0) {
|
||||||
|
sessionRestoreBannerView.update(recipient);
|
||||||
|
sessionRestoreBannerView.show();
|
||||||
|
} else {
|
||||||
|
sessionRestoreBannerView.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);
|
||||||
@ -1639,6 +1668,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
|||||||
searchNav = ViewUtil.findById(this, R.id.conversation_search_nav);
|
searchNav = ViewUtil.findById(this, R.id.conversation_search_nav);
|
||||||
mentionCandidateSelectionViewContainer = ViewUtil.findById(this, R.id.mentionCandidateSelectionViewContainer);
|
mentionCandidateSelectionViewContainer = ViewUtil.findById(this, R.id.mentionCandidateSelectionViewContainer);
|
||||||
mentionCandidateSelectionView = ViewUtil.findById(this, R.id.userSelectionView);
|
mentionCandidateSelectionView = ViewUtil.findById(this, R.id.userSelectionView);
|
||||||
|
sessionRestoreBannerView = ViewUtil.findById(this, R.id.sessionRestoreBannerView);
|
||||||
messageStatusProgressBar = ViewUtil.findById(this, R.id.messageStatusProgressBar);
|
messageStatusProgressBar = ViewUtil.findById(this, R.id.messageStatusProgressBar);
|
||||||
muteIndicatorImageView = ViewUtil.findById(this, R.id.muteIndicatorImageView);
|
muteIndicatorImageView = ViewUtil.findById(this, R.id.muteIndicatorImageView);
|
||||||
actionBarSubtitleTextView = ViewUtil.findById(this, R.id.subtitleTextView);
|
actionBarSubtitleTextView = ViewUtil.findById(this, R.id.subtitleTextView);
|
||||||
@ -2257,6 +2287,13 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
|||||||
this.updateInputPanel();
|
this.updateInputPanel();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handleSessionRestoreDevicesChanged(long threadId) {
|
||||||
|
if (threadId == this.threadId) {
|
||||||
|
runOnUiThread(this::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.
|
||||||
@ -3262,4 +3299,19 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
|||||||
return TextSecurePreferences.getLocalNumber(this).equals(recipient.getAddress().serialize());
|
return TextSecurePreferences.getLocalNumber(this).equals(recipient.getAddress().serialize());
|
||||||
}
|
}
|
||||||
// endregion
|
// endregion
|
||||||
|
|
||||||
|
public void restoreSession() {
|
||||||
|
// Loki - User clicked restore session
|
||||||
|
if (recipient.isGroupRecipient()) { return; }
|
||||||
|
LokiThreadDatabase lokiThreadDatabase = DatabaseFactory.getLokiThreadDatabase(this);
|
||||||
|
SmsDatabase smsDatabase = DatabaseFactory.getSmsDatabase(this);
|
||||||
|
Set<String> devices = lokiThreadDatabase.getSessionRestoreDevices(threadId);
|
||||||
|
for (String device : devices) { MessageSender.sendRestoreSessionMessage(this, device); }
|
||||||
|
long messageId = smsDatabase.insertMessageOutbox(threadId, new OutgoingTextMessage(recipient,"", 0, 0), false, System.currentTimeMillis(), null);
|
||||||
|
if (messageId > -1) {
|
||||||
|
smsDatabase.markAsLokiSessionRestoreSent(messageId);
|
||||||
|
}
|
||||||
|
lokiThreadDatabase.removeAllSessionRestoreDevices(threadId);
|
||||||
|
updateSessionRestoreBanner();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -361,7 +361,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())
|
||||||
{
|
{
|
||||||
actionMessage = true;
|
actionMessage = true;
|
||||||
}
|
}
|
||||||
|
@ -113,6 +113,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.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);
|
||||||
@ -203,6 +204,15 @@ public class ConversationUpdateItem extends LinearLayout
|
|||||||
date.setVisibility(GONE);
|
date.setVisibility(GONE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void setTextMessageRecord(MessageRecord messageRecord) {
|
||||||
|
body.setText(messageRecord.getDisplayBody(getContext()));
|
||||||
|
|
||||||
|
icon.setVisibility(GONE);
|
||||||
|
title.setVisibility(GONE);
|
||||||
|
body.setVisibility(VISIBLE);
|
||||||
|
date.setVisibility(GONE);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onModified(Recipient recipient) {
|
public void onModified(Recipient recipient) {
|
||||||
Util.runOnMain(() -> bind(messageRecord, locale));
|
Util.runOnMain(() -> bind(messageRecord, locale));
|
||||||
|
@ -82,6 +82,9 @@ public interface MmsSmsColumns {
|
|||||||
protected static final long ENCRYPTION_REMOTE_DUPLICATE_BIT = 0x04000000;
|
protected static final long ENCRYPTION_REMOTE_DUPLICATE_BIT = 0x04000000;
|
||||||
protected static final long ENCRYPTION_REMOTE_LEGACY_BIT = 0x02000000;
|
protected static final long ENCRYPTION_REMOTE_LEGACY_BIT = 0x02000000;
|
||||||
|
|
||||||
|
// Loki
|
||||||
|
protected static final long ENCRYPTION_LOKI_SESSION_RESTORE_SENT_BIT = 0x01000000;
|
||||||
|
|
||||||
public static boolean isDraftMessageType(long type) {
|
public static boolean isDraftMessageType(long type) {
|
||||||
return (type & BASE_TYPE_MASK) == BASE_DRAFT_TYPE;
|
return (type & BASE_TYPE_MASK) == BASE_DRAFT_TYPE;
|
||||||
}
|
}
|
||||||
@ -230,6 +233,10 @@ public interface MmsSmsColumns {
|
|||||||
return (type & ENCRYPTION_REMOTE_NO_SESSION_BIT) != 0;
|
return (type & ENCRYPTION_REMOTE_NO_SESSION_BIT) != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static boolean isLokiSessionRestoreSentType(long type) {
|
||||||
|
return (type & ENCRYPTION_LOKI_SESSION_RESTORE_SENT_BIT) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
public static boolean isLegacyType(long type) {
|
public static boolean isLegacyType(long type) {
|
||||||
return (type & ENCRYPTION_REMOTE_LEGACY_BIT) != 0 ||
|
return (type & ENCRYPTION_REMOTE_LEGACY_BIT) != 0 ||
|
||||||
(type & ENCRYPTION_REMOTE_BIT) != 0;
|
(type & ENCRYPTION_REMOTE_BIT) != 0;
|
||||||
|
@ -47,6 +47,7 @@ import org.whispersystems.libsignal.util.guava.Optional;
|
|||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.security.SecureRandom;
|
import java.security.SecureRandom;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@ -251,6 +252,10 @@ public class SmsDatabase extends MessagingDatabase {
|
|||||||
updateTypeBitmask(id, Types.ENCRYPTION_MASK, Types.ENCRYPTION_REMOTE_NO_SESSION_BIT);
|
updateTypeBitmask(id, Types.ENCRYPTION_MASK, Types.ENCRYPTION_REMOTE_NO_SESSION_BIT);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void markAsLokiSessionRestoreSent(long id) {
|
||||||
|
updateTypeBitmask(id, Types.ENCRYPTION_MASK, Types.ENCRYPTION_LOKI_SESSION_RESTORE_SENT_BIT);
|
||||||
|
}
|
||||||
|
|
||||||
public void markAsLegacyVersion(long id) {
|
public void markAsLegacyVersion(long id) {
|
||||||
updateTypeBitmask(id, Types.ENCRYPTION_MASK, Types.ENCRYPTION_REMOTE_LEGACY_BIT);
|
updateTypeBitmask(id, Types.ENCRYPTION_MASK, Types.ENCRYPTION_REMOTE_LEGACY_BIT);
|
||||||
}
|
}
|
||||||
|
@ -103,9 +103,9 @@ 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 isLokiSessionRestoreSent() { return SmsDatabase.Types.isLokiSessionRestoreSentType(type); }
|
||||||
|
|
||||||
public boolean isGroupUpdate() {
|
public boolean isGroupUpdate() {
|
||||||
return SmsDatabase.Types.isGroupUpdate(type);
|
return SmsDatabase.Types.isGroupUpdate(type);
|
||||||
|
@ -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();
|
isEndSession() || isIdentityUpdate() || isIdentityVerified() || isIdentityDefault() || isLokiSessionRestoreSent();
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isMediaPending() {
|
public boolean isMediaPending() {
|
||||||
|
@ -82,6 +82,7 @@ public class SmsMessageRecord extends MessageRecord {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public SpannableString getDisplayBody(@NonNull Context context) {
|
public SpannableString getDisplayBody(@NonNull Context context) {
|
||||||
|
Recipient recipient = getRecipient();
|
||||||
if (SmsDatabase.Types.isFailedDecryptType(type)) {
|
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 (isCorruptedKeyExchange()) {
|
} else if (isCorruptedKeyExchange()) {
|
||||||
@ -100,6 +101,8 @@ public class SmsMessageRecord extends MessageRecord {
|
|||||||
return emphasisAdded(context.getString(R.string.SmsMessageRecord_duplicate_message));
|
return emphasisAdded(context.getString(R.string.SmsMessageRecord_duplicate_message));
|
||||||
} else if (SmsDatabase.Types.isNoRemoteSessionType(type)) {
|
} else if (SmsDatabase.Types.isNoRemoteSessionType(type)) {
|
||||||
return emphasisAdded(context.getString(R.string.MessageDisplayHelper_message_encrypted_for_non_existing_session));
|
return emphasisAdded(context.getString(R.string.MessageDisplayHelper_message_encrypted_for_non_existing_session));
|
||||||
|
} else if (isLokiSessionRestoreSent()) {
|
||||||
|
return emphasisAdded(context.getString(R.string.MessageRecord_session_restore_sent, 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()) {
|
||||||
|
@ -71,6 +71,7 @@ public class ThreadRecord extends DisplayRecord {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public SpannableString getDisplayBody(@NonNull Context context) {
|
public SpannableString getDisplayBody(@NonNull Context context) {
|
||||||
|
Recipient recipient = getRecipient();
|
||||||
if (isGroupUpdate()) {
|
if (isGroupUpdate()) {
|
||||||
return emphasisAdded(context.getString(R.string.ThreadRecord_group_updated));
|
return emphasisAdded(context.getString(R.string.ThreadRecord_group_updated));
|
||||||
} else if (isGroupQuit()) {
|
} else if (isGroupQuit()) {
|
||||||
@ -81,6 +82,8 @@ public class ThreadRecord extends DisplayRecord {
|
|||||||
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)) {
|
} else if (SmsDatabase.Types.isNoRemoteSessionType(type)) {
|
||||||
return emphasisAdded(context.getString(R.string.MessageDisplayHelper_message_encrypted_for_non_existing_session));
|
return emphasisAdded(context.getString(R.string.MessageDisplayHelper_message_encrypted_for_non_existing_session));
|
||||||
|
} else if (isLokiSessionRestoreSent()) {
|
||||||
|
return emphasisAdded(context.getString(R.string.MessageRecord_session_restore_sent, 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)) {
|
||||||
|
@ -56,6 +56,7 @@ import org.thoughtcrime.securesms.database.StickerDatabase;
|
|||||||
import org.thoughtcrime.securesms.database.ThreadDatabase;
|
import org.thoughtcrime.securesms.database.ThreadDatabase;
|
||||||
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.database.model.SmsMessageRecord;
|
||||||
import org.thoughtcrime.securesms.database.model.StickerRecord;
|
import org.thoughtcrime.securesms.database.model.StickerRecord;
|
||||||
import org.thoughtcrime.securesms.dependencies.InjectableType;
|
import org.thoughtcrime.securesms.dependencies.InjectableType;
|
||||||
import org.thoughtcrime.securesms.groups.GroupMessageProcessor;
|
import org.thoughtcrime.securesms.groups.GroupMessageProcessor;
|
||||||
@ -278,6 +279,12 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
|
|||||||
cipher.validateBackgroundMessage(envelope, envelope.getContent());
|
cipher.validateBackgroundMessage(envelope, envelope.getContent());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Loki - Ignore any friend requests that we got before restoration
|
||||||
|
if (envelope.isFriendRequest() && envelope.getTimestamp() < TextSecurePreferences.getRestorationTime(context)) {
|
||||||
|
Log.d("Loki", "Ignoring friend request received before restoration.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
SignalServiceContent content = cipher.decrypt(envelope);
|
SignalServiceContent content = cipher.decrypt(envelope);
|
||||||
|
|
||||||
if (shouldIgnore(content)) {
|
if (shouldIgnore(content)) {
|
||||||
@ -331,6 +338,8 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
|
|||||||
MultiDeviceUtilities.checkForRevocation(context);
|
MultiDeviceUtilities.checkForRevocation(context);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
// Loki - Don't process session restore message any further
|
||||||
|
if (message.isSessionRestore()) { return; }
|
||||||
if (message.isEndSession()) handleEndSessionMessage(content, smsMessageId);
|
if (message.isEndSession()) handleEndSessionMessage(content, smsMessageId);
|
||||||
else if (message.isGroupUpdate()) handleGroupMessage(content, message, smsMessageId);
|
else if (message.isGroupUpdate()) handleGroupMessage(content, message, smsMessageId);
|
||||||
else if (message.isExpirationUpdate())
|
else if (message.isExpirationUpdate())
|
||||||
@ -530,21 +539,24 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (threadId != null) {
|
if (threadId != null) {
|
||||||
|
resetSession(content.getSender(), threadId);
|
||||||
|
MessageNotifier.updateNotification(context, threadId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void resetSession(String hexEncodedPublicKey, long threadId) {
|
||||||
TextSecureSessionStore sessionStore = new TextSecureSessionStore(context);
|
TextSecureSessionStore sessionStore = new TextSecureSessionStore(context);
|
||||||
LokiThreadDatabase lokiThreadDatabase = DatabaseFactory.getLokiThreadDatabase(context);
|
LokiThreadDatabase lokiThreadDatabase = DatabaseFactory.getLokiThreadDatabase(context);
|
||||||
|
|
||||||
Log.d("Loki", "Received a session reset request from: " + content.getSender() + "; archiving the session.");
|
Log.d("Loki", "Received a session reset request from: " + hexEncodedPublicKey + "; archiving the session.");
|
||||||
|
|
||||||
sessionStore.archiveAllSessions(content.getSender());
|
sessionStore.archiveAllSessions(hexEncodedPublicKey);
|
||||||
lokiThreadDatabase.setSessionResetStatus(threadId, LokiThreadSessionResetStatus.REQUEST_RECEIVED);
|
lokiThreadDatabase.setSessionResetStatus(threadId, LokiThreadSessionResetStatus.REQUEST_RECEIVED);
|
||||||
|
|
||||||
Log.d("Loki", "Sending a ping back to " + content.getSender() + ".");
|
Log.d("Loki", "Sending a ping back to " + hexEncodedPublicKey + ".");
|
||||||
String contactID = DatabaseFactory.getThreadDatabase(context).getRecipientForThreadId(threadId).getAddress().toString();
|
MessageSender.sendBackgroundMessage(context, hexEncodedPublicKey);
|
||||||
MessageSender.sendBackgroundMessage(context, contactID);
|
|
||||||
|
|
||||||
SecurityEvent.broadcastSecurityUpdateEvent(context);
|
SecurityEvent.broadcastSecurityUpdateEvent(context);
|
||||||
MessageNotifier.updateNotification(context, threadId);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private long handleSynchronizeSentEndSessionMessage(@NonNull SentTranscriptMessage message)
|
private long handleSynchronizeSentEndSessionMessage(@NonNull SentTranscriptMessage message)
|
||||||
@ -1177,17 +1189,30 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void storePreKeyBundleIfNeeded(@NonNull SignalServiceEnvelope envelope, @NonNull SignalServiceContent content) {
|
private void storePreKeyBundleIfNeeded(@NonNull SignalServiceEnvelope envelope, @NonNull SignalServiceContent content) {
|
||||||
if (content.lokiServiceMessage.isPresent()) {
|
Recipient sender = Recipient.from(context, Address.fromSerialized(content.getSender()), false);
|
||||||
|
if (!sender.isGroupRecipient() && content.lokiServiceMessage.isPresent()) {
|
||||||
LokiServiceMessage lokiMessage = content.lokiServiceMessage.get();
|
LokiServiceMessage lokiMessage = content.lokiServiceMessage.get();
|
||||||
if (lokiMessage.getPreKeyBundleMessage() != null) {
|
if (lokiMessage.getPreKeyBundleMessage() != null) {
|
||||||
int registrationID = TextSecurePreferences.getLocalRegistrationId(context);
|
int registrationID = TextSecurePreferences.getLocalRegistrationId(context);
|
||||||
LokiPreKeyBundleDatabase lokiPreKeyBundleDatabase = DatabaseFactory.getLokiPreKeyBundleDatabase(context);
|
LokiPreKeyBundleDatabase lokiPreKeyBundleDatabase = DatabaseFactory.getLokiPreKeyBundleDatabase(context);
|
||||||
|
ThreadDatabase threadDatabase = DatabaseFactory.getThreadDatabase(context);
|
||||||
|
LokiThreadDatabase lokiThreadDatabase = DatabaseFactory.getLokiThreadDatabase(context);
|
||||||
|
|
||||||
// Store the latest PreKeyBundle
|
// Loki - Store the latest pre key bundle
|
||||||
if (registrationID > 0) {
|
if (registrationID > 0) {
|
||||||
Log.d("Loki", "Received a pre key bundle from: " + envelope.getSource() + ".");
|
Log.d("Loki", "Received a pre key bundle from: " + content.getSender() + ".");
|
||||||
PreKeyBundle preKeyBundle = lokiMessage.getPreKeyBundleMessage().getPreKeyBundle(registrationID);
|
PreKeyBundle preKeyBundle = lokiMessage.getPreKeyBundleMessage().getPreKeyBundle(registrationID);
|
||||||
lokiPreKeyBundleDatabase.setPreKeyBundle(envelope.getSource(), preKeyBundle);
|
lokiPreKeyBundleDatabase.setPreKeyBundle(content.getSender(), preKeyBundle);
|
||||||
|
|
||||||
|
// Loki - If we received a friend request, but we were already friends with this user, then reset the session
|
||||||
|
if (envelope.isFriendRequest()) {
|
||||||
|
long threadID = threadDatabase.getThreadIdIfExistsFor(sender);
|
||||||
|
if (lokiThreadDatabase.getFriendRequestStatus(threadID) == LokiThreadFriendRequestStatus.FRIENDS) {
|
||||||
|
resetSession(content.getSender(), threadID);
|
||||||
|
// Let our other devices know that we have reset the session
|
||||||
|
MessageSender.syncContact(context, sender.getAddress());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1357,21 +1382,40 @@ 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,
|
private void handleCorruptMessage(@NonNull String sender, int senderDevice, long timestamp,
|
||||||
@NonNull Optional<Long> smsMessageId)
|
@NonNull Optional<Long> smsMessageId)
|
||||||
{
|
{
|
||||||
SmsDatabase smsDatabase = DatabaseFactory.getSmsDatabase(context);
|
SmsDatabase smsDatabase = DatabaseFactory.getSmsDatabase(context);
|
||||||
|
|
||||||
if (!smsMessageId.isPresent()) {
|
if (!smsMessageId.isPresent()) {
|
||||||
|
SmsMessageRecord lastMessage = getLastMessage(sender);
|
||||||
|
if (lastMessage == null || !SmsDatabase.Types.isFailedDecryptType(lastMessage.getType())) {
|
||||||
Optional<InsertResult> insertResult = insertPlaceholder(sender, senderDevice, timestamp);
|
Optional<InsertResult> insertResult = insertPlaceholder(sender, senderDevice, timestamp);
|
||||||
|
|
||||||
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,
|
||||||
@ -1380,15 +1424,27 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
|
|||||||
SmsDatabase smsDatabase = DatabaseFactory.getSmsDatabase(context);
|
SmsDatabase smsDatabase = DatabaseFactory.getSmsDatabase(context);
|
||||||
|
|
||||||
if (!smsMessageId.isPresent()) {
|
if (!smsMessageId.isPresent()) {
|
||||||
|
SmsMessageRecord lastMessage = getLastMessage(sender);
|
||||||
|
if (lastMessage == null || !SmsDatabase.Types.isNoRemoteSessionType(lastMessage.getType())) {
|
||||||
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());
|
||||||
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 triggerSessionRestorePrompt(@NonNull String sender) {
|
||||||
|
Recipient primaryRecipient = getPrimaryDeviceRecipient(sender);
|
||||||
|
if (!primaryRecipient.isGroupRecipient()) {
|
||||||
|
long threadID = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(primaryRecipient);
|
||||||
|
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,
|
||||||
@ -1779,7 +1835,7 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private boolean isGroupChatMessage(SignalServiceContent content) {
|
private boolean isGroupChatMessage(SignalServiceContent content) {
|
||||||
return content.getDataMessage().isPresent() && content.getDataMessage().get().isGroupUpdate();
|
return content.getDataMessage().isPresent() && content.getDataMessage().get().getGroupInfo().isPresent();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void resetRecipientToPush(@NonNull Recipient recipient) {
|
private void resetRecipientToPush(@NonNull Recipient recipient) {
|
||||||
|
@ -26,7 +26,12 @@ object FriendRequestHandler {
|
|||||||
ActionType.Failed -> LokiThreadFriendRequestStatus.NONE
|
ActionType.Failed -> LokiThreadFriendRequestStatus.NONE
|
||||||
ActionType.Sent -> LokiThreadFriendRequestStatus.REQUEST_SENT
|
ActionType.Sent -> LokiThreadFriendRequestStatus.REQUEST_SENT
|
||||||
}
|
}
|
||||||
DatabaseFactory.getLokiThreadDatabase(context).setFriendRequestStatus(threadId, threadFriendStatus)
|
val database = DatabaseFactory.getLokiThreadDatabase(context)
|
||||||
|
database.setFriendRequestStatus(threadId, threadFriendStatus)
|
||||||
|
// If we sent a friend request then we need to hide the session restore prompt
|
||||||
|
if (type == ActionType.Sent) {
|
||||||
|
database.removeAllSessionRestoreDevices(threadId)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update message status
|
// Update message status
|
||||||
@ -56,6 +61,8 @@ object FriendRequestHandler {
|
|||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun updateLastFriendRequestMessage(context: Context, threadId: Long, status: LokiMessageFriendRequestStatus) {
|
fun updateLastFriendRequestMessage(context: Context, threadId: Long, status: LokiMessageFriendRequestStatus) {
|
||||||
if (threadId < 0) { return }
|
if (threadId < 0) { return }
|
||||||
|
val recipient = DatabaseFactory.getThreadDatabase(context).getRecipientForThreadId(threadId) ?: return
|
||||||
|
if (!recipient.address.isPhone) { return }
|
||||||
|
|
||||||
val messages = DatabaseFactory.getSmsDatabase(context).getAllMessageIDs(threadId)
|
val messages = DatabaseFactory.getSmsDatabase(context).getAllMessageIDs(threadId)
|
||||||
val lokiMessageDatabase = DatabaseFactory.getLokiMessageDatabase(context)
|
val lokiMessageDatabase = DatabaseFactory.getLokiMessageDatabase(context)
|
||||||
|
@ -8,11 +8,13 @@ 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
|
||||||
import org.whispersystems.signalservice.loki.messaging.LokiThreadFriendRequestStatus
|
import org.whispersystems.signalservice.loki.messaging.LokiThreadFriendRequestStatus
|
||||||
import org.whispersystems.signalservice.loki.messaging.LokiThreadSessionResetStatus
|
import org.whispersystems.signalservice.loki.messaging.LokiThreadSessionResetStatus
|
||||||
|
import org.whispersystems.signalservice.loki.utilities.PublicKeyValidation
|
||||||
|
|
||||||
class LokiThreadDatabase(context: Context, helper: SQLCipherOpenHelper) : Database(context, helper), LokiThreadDatabaseProtocol {
|
class LokiThreadDatabase(context: Context, helper: SQLCipherOpenHelper) : Database(context, helper), LokiThreadDatabaseProtocol {
|
||||||
var delegate: LokiThreadDatabaseDelegate? = null
|
var delegate: LokiThreadDatabaseDelegate? = null
|
||||||
@ -140,4 +142,26 @@ 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(",")
|
||||||
|
.filter { PublicKeyValidation.isValid(it) }
|
||||||
|
.toSet()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun removeAllSessionRestoreDevices(threadID: Long) {
|
||||||
|
TextSecurePreferences.setStringPreference(context, "session_restore_devices_$threadID", "")
|
||||||
|
delegate?.handleSessionRestoreDevicesChanged(threadID)
|
||||||
|
}
|
||||||
|
// endregion
|
||||||
}
|
}
|
@ -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)
|
||||||
}
|
}
|
@ -15,28 +15,34 @@ import org.whispersystems.signalservice.internal.util.JsonUtil
|
|||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
data class BackgroundMessage private constructor(val recipient: String, val body: String?, val friendRequest: Boolean, val unpairingRequest: Boolean) {
|
data class BackgroundMessage private constructor(val data: Map<String, Any>) {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun create(recipient: String) = BackgroundMessage(recipient, null, false, false)
|
fun create(recipient: String) = BackgroundMessage(mapOf("recipient" to recipient))
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun createFriendRequest(recipient: String, messageBody: String) = BackgroundMessage(recipient, messageBody, true, false)
|
fun createFriendRequest(recipient: String, messageBody: String) = BackgroundMessage(mapOf( "recipient" to recipient, "body" to messageBody, "friendRequest" to true ))
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun createUnpairingRequest(recipient: String) = BackgroundMessage(recipient, null, false, true)
|
fun createUnpairingRequest(recipient: String) = BackgroundMessage(mapOf( "recipient" to recipient, "unpairingRequest" to true ))
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun createSessionRestore(recipient: String) = BackgroundMessage(mapOf( "recipient" to recipient, "friendRequest" to true, "sessionRestore" to true ))
|
||||||
|
|
||||||
internal fun parse(serialized: String): BackgroundMessage {
|
internal fun parse(serialized: String): BackgroundMessage {
|
||||||
val node = JsonUtil.fromJson(serialized)
|
val data = JsonUtil.fromJson(serialized, Map::class.java) as? Map<String, Any> ?: throw AssertionError("JSON parsing failed")
|
||||||
val recipient = node.get("recipient").asText()
|
return BackgroundMessage(data)
|
||||||
val body = if (node.hasNonNull("body")) node.get("body").asText() else null
|
|
||||||
val friendRequest = node.get("friendRequest").asBoolean(false)
|
|
||||||
val unpairingRequest = node.get("unpairingRequest").asBoolean(false)
|
|
||||||
return BackgroundMessage(recipient, body, friendRequest, unpairingRequest)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun <T> get(key: String, defaultValue: T): T {
|
||||||
|
return data[key] as? T ?: defaultValue
|
||||||
|
}
|
||||||
|
|
||||||
fun serialize(): String {
|
fun serialize(): String {
|
||||||
val map = mapOf("recipient" to recipient, "body" to body, "friendRequest" to friendRequest, "unpairingRequest" to unpairingRequest)
|
return JsonUtil.toJson(data)
|
||||||
return JsonUtil.toJson(map)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -71,24 +77,31 @@ class PushBackgroundMessageSendJob private constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
public override fun onRun() {
|
public override fun onRun() {
|
||||||
|
val recipient = message.get<String?>("recipient", null) ?: throw IllegalStateException()
|
||||||
val dataMessage = SignalServiceDataMessage.newBuilder()
|
val dataMessage = SignalServiceDataMessage.newBuilder()
|
||||||
.withTimestamp(System.currentTimeMillis())
|
.withTimestamp(System.currentTimeMillis())
|
||||||
.withBody(message.body)
|
.withBody(message.get<String?>("body", null))
|
||||||
|
|
||||||
if (message.friendRequest) {
|
if (message.get("friendRequest", false)) {
|
||||||
val bundle = DatabaseFactory.getLokiPreKeyBundleDatabase(context).generatePreKeyBundle(message.recipient)
|
val bundle = DatabaseFactory.getLokiPreKeyBundleDatabase(context).generatePreKeyBundle(recipient)
|
||||||
dataMessage.withPreKeyBundle(bundle)
|
dataMessage.withPreKeyBundle(bundle)
|
||||||
.asFriendRequest(true)
|
.asFriendRequest(true)
|
||||||
} else if (message.unpairingRequest) {
|
}
|
||||||
|
|
||||||
|
if (message.get("unpairingRequest", false)) {
|
||||||
dataMessage.asUnpairingRequest(true)
|
dataMessage.asUnpairingRequest(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (message.get("sessionRestore", false)) {
|
||||||
|
dataMessage.asSessionRestore(true)
|
||||||
|
}
|
||||||
|
|
||||||
val messageSender = ApplicationContext.getInstance(context).communicationModule.provideSignalMessageSender()
|
val messageSender = ApplicationContext.getInstance(context).communicationModule.provideSignalMessageSender()
|
||||||
val address = SignalServiceAddress(message.recipient)
|
val address = SignalServiceAddress(recipient)
|
||||||
try {
|
try {
|
||||||
messageSender.sendMessage(-1, address, Optional.absent<UnidentifiedAccessPair>(), dataMessage.build()) // The message ID doesn't matter
|
messageSender.sendMessage(-1, address, Optional.absent<UnidentifiedAccessPair>(), dataMessage.build()) // The message ID doesn't matter
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.d("Loki", "Failed to send background message to: ${message.recipient}.")
|
Log.d("Loki", "Failed to send background message to: ${recipient}.")
|
||||||
throw e
|
throw e
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -206,6 +206,9 @@ class SeedActivity : BaseActionBarActivity(), DeviceLinkingDelegate, ScanListene
|
|||||||
DatabaseFactory.getIdentityDatabase(this).saveIdentity(Address.fromSerialized(userHexEncodedPublicKey), keyPair.publicKey,
|
DatabaseFactory.getIdentityDatabase(this).saveIdentity(Address.fromSerialized(userHexEncodedPublicKey), keyPair.publicKey,
|
||||||
IdentityDatabase.VerifiedStatus.VERIFIED, true, System.currentTimeMillis(), true)
|
IdentityDatabase.VerifiedStatus.VERIFIED, true, System.currentTimeMillis(), true)
|
||||||
TextSecurePreferences.setLocalNumber(this, userHexEncodedPublicKey)
|
TextSecurePreferences.setLocalNumber(this, userHexEncodedPublicKey)
|
||||||
|
if (mode == Mode.Restore) {
|
||||||
|
TextSecurePreferences.setRestorationTime(this, System.currentTimeMillis())
|
||||||
|
}
|
||||||
when (mode) {
|
when (mode) {
|
||||||
Mode.Register -> Analytics.shared.track("Seed Created")
|
Mode.Register -> Analytics.shared.track("Seed Created")
|
||||||
Mode.Restore -> Analytics.shared.track("Seed Restored")
|
Mode.Restore -> Analytics.shared.track("Seed Restored")
|
||||||
|
@ -0,0 +1,40 @@
|
|||||||
|
package org.thoughtcrime.securesms.loki
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.util.AttributeSet
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.widget.LinearLayout
|
||||||
|
import kotlinx.android.synthetic.main.session_restore_banner.view.*
|
||||||
|
|
||||||
|
import network.loki.messenger.R
|
||||||
|
import org.thoughtcrime.securesms.recipients.Recipient
|
||||||
|
|
||||||
|
class SessionRestoreBannerView : LinearLayout {
|
||||||
|
lateinit var recipient: Recipient
|
||||||
|
var onDismiss: (() -> Unit)? = null
|
||||||
|
var onRestore: (() -> Unit)? = null
|
||||||
|
|
||||||
|
constructor(context: Context) : super(context, null)
|
||||||
|
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs, 0)
|
||||||
|
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)
|
||||||
|
|
||||||
|
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
|
||||||
|
messageTextView.text = context.getString(R.string.session_restore_banner_message, recipient.toShortString())
|
||||||
|
}
|
||||||
|
|
||||||
|
fun show() {
|
||||||
|
sessionRestoreBanner.visibility = View.VISIBLE
|
||||||
|
}
|
||||||
|
|
||||||
|
fun hide() {
|
||||||
|
sessionRestoreBanner.visibility = View.GONE
|
||||||
|
}
|
||||||
|
}
|
@ -129,6 +129,7 @@ class RegisterActivity : BaseActionBarActivity() {
|
|||||||
IdentityKeyUtil.getIdentityKeyPair(this).publicKey, IdentityDatabase.VerifiedStatus.VERIFIED,
|
IdentityKeyUtil.getIdentityKeyPair(this).publicKey, IdentityDatabase.VerifiedStatus.VERIFIED,
|
||||||
true, System.currentTimeMillis(), true)
|
true, System.currentTimeMillis(), true)
|
||||||
TextSecurePreferences.setLocalNumber(this, userHexEncodedPublicKey)
|
TextSecurePreferences.setLocalNumber(this, userHexEncodedPublicKey)
|
||||||
|
TextSecurePreferences.setRestorationTime(this, 0)
|
||||||
TextSecurePreferences.setHasViewedSeed(this, false)
|
TextSecurePreferences.setHasViewedSeed(this, false)
|
||||||
val intent = Intent(this, DisplayNameActivity::class.java)
|
val intent = Intent(this, DisplayNameActivity::class.java)
|
||||||
push(intent)
|
push(intent)
|
||||||
|
@ -87,6 +87,7 @@ class RestoreActivity : BaseActionBarActivity() {
|
|||||||
IdentityKeyUtil.getIdentityKeyPair(this).publicKey, IdentityDatabase.VerifiedStatus.VERIFIED,
|
IdentityKeyUtil.getIdentityKeyPair(this).publicKey, IdentityDatabase.VerifiedStatus.VERIFIED,
|
||||||
true, System.currentTimeMillis(), true)
|
true, System.currentTimeMillis(), true)
|
||||||
TextSecurePreferences.setLocalNumber(this, userHexEncodedPublicKey)
|
TextSecurePreferences.setLocalNumber(this, userHexEncodedPublicKey)
|
||||||
|
TextSecurePreferences.setRestorationTime(this, System.currentTimeMillis())
|
||||||
TextSecurePreferences.setHasViewedSeed(this, true)
|
TextSecurePreferences.setHasViewedSeed(this, true)
|
||||||
val intent = Intent(this, DisplayNameActivity::class.java)
|
val intent = Intent(this, DisplayNameActivity::class.java)
|
||||||
push(intent)
|
push(intent)
|
||||||
|
@ -139,6 +139,10 @@ public class MessageSender {
|
|||||||
public static void sendUnpairRequest(Context context, String contactHexEncodedPublicKey) {
|
public static void sendUnpairRequest(Context context, String contactHexEncodedPublicKey) {
|
||||||
ApplicationContext.getInstance(context).getJobManager().add(new PushBackgroundMessageSendJob(BackgroundMessage.createUnpairingRequest(contactHexEncodedPublicKey)));
|
ApplicationContext.getInstance(context).getJobManager().add(new PushBackgroundMessageSendJob(BackgroundMessage.createUnpairingRequest(contactHexEncodedPublicKey)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void sendRestoreSessionMessage(Context context, String contactHexEncodedPublicKey) {
|
||||||
|
ApplicationContext.getInstance(context).getJobManager().add(new PushBackgroundMessageSendJob(BackgroundMessage.createSessionRestore(contactHexEncodedPublicKey)));
|
||||||
|
}
|
||||||
// endregion
|
// endregion
|
||||||
|
|
||||||
public static long send(final Context context,
|
public static long send(final Context context,
|
||||||
|
@ -1236,6 +1236,14 @@ public class TextSecurePreferences {
|
|||||||
public static boolean needsRevocationCheck(Context context) {
|
public static boolean needsRevocationCheck(Context context) {
|
||||||
return getBooleanPreference(context, "needs_revocation", false);
|
return getBooleanPreference(context, "needs_revocation", false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void setRestorationTime(Context context, long time) {
|
||||||
|
setLongPreference(context, "restoration_time", time);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static long getRestorationTime(Context context) {
|
||||||
|
return getLongPreference(context, "restoration_time", 0);
|
||||||
|
}
|
||||||
// endregion
|
// endregion
|
||||||
|
|
||||||
public static void clearAll(Context context) {
|
public static void clearAll(Context context) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user